diff --git a/.vpython3 b/.vpython3
index d660802..2ec31ce 100644
--- a/.vpython3
+++ b/.vpython3
@@ -469,7 +469,7 @@
 
 wheel: <
   name: "infra/python/wheels/websockets-py3"
-  version: "version:10.3"
+  version: "version:11.0.3"
 >
 
 # Used by:
diff --git a/BUILD.gn b/BUILD.gn
index b8b5fdd..b512618 100644
--- a/BUILD.gn
+++ b/BUILD.gn
@@ -319,11 +319,14 @@
       deps += [
         "//ui/aura:aura_demo",
         "//ui/aura:aura_unittests",
-        "//ui/webui/examples:webui_examples",
         "//ui/wm:wm_unittests",
       ]
     }
 
+    if (use_aura || is_mac) {
+      deps += [ "//ui/webui/examples:webui_examples" ]
+    }
+
     if (use_ozone) {
       deps += [
         "//ui/ozone",
diff --git a/DEPS b/DEPS
index cbd7179..148bc63 100644
--- a/DEPS
+++ b/DEPS
@@ -249,7 +249,7 @@
   # luci-go CIPD package version.
   # Make sure the revision is uploaded by infra-packagers builder.
   # https://ci.chromium.org/p/infra-internal/g/infra-packagers/console
-  'luci_go': 'git_revision:e99800d3fd42249c9cdda3f58ba31c674b4876f6',
+  'luci_go': 'git_revision:39f255d5875293d3e1d978888b819ac124a8b0cc',
 
   # This can be overridden, e.g. with custom_vars, to build clang from HEAD
   # instead of downloading the prebuilt pinned revision.
@@ -300,19 +300,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': '69c27e547002e7b16fd7e39d9d458672021e53da',
+  'src_internal_revision': '9fe891904b0391a0efc1a720c03c86a5085079cb',
   # 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': 'b06e934cb6374a2a741a5b5c727c2abfad719665',
+  'skia_revision': '8f0ef5e2d300f369b02157e0aa34e49fb88024cb',
   # 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': '10f7ee2375298634126cd353825f2fb3af342194',
+  'v8_revision': '75794115d33cb84b27d698a5a2e00e75772e8baf',
   # 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': 'af1768a4875660cf8b08fcec75066a275ebc33e7',
+  'angle_revision': '97e3851d12193d3195a1dc450915a5451eddccea',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling SwiftShader
   # and whatever else without interference from each other.
@@ -375,7 +375,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': 'c684fe0312182df04f259e1b03a2acb41b34406f',
+  'catapult_revision': 'c87640687ea0887b2dd77cddd62e7d2309fd28da',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling CrossBench
   # and whatever else without interference from each other.
@@ -431,7 +431,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling feed
   # and whatever else without interference from each other.
-  'dawn_revision': '19f92f9cdeab4e4f888d78ad7a3fd5819fdc2a7d',
+  'dawn_revision': 'f1ed14a7455229f773d42d1b6c088898469dc6b8',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling feed
   # and whatever else without interference from each other.
@@ -1208,7 +1208,7 @@
   # Tools used when building Chrome for Chrome OS. This affects both the Simple
   # Chrome workflow, as well as the chromeos-chrome ebuild.
   'src/third_party/chromite': {
-      'url': Var('chromium_git') + '/chromiumos/chromite.git' + '@' + '2b8358b431c655230d9a8d9ef643fc8f6e48c370',
+      'url': Var('chromium_git') + '/chromiumos/chromite.git' + '@' + '9c40958316d552a11a2e7e48fb1ed2bfe3315701',
       'condition': 'checkout_chromeos',
   },
 
@@ -1243,13 +1243,13 @@
   },
 
   'src/third_party/depot_tools':
-    Var('chromium_git') + '/chromium/tools/depot_tools.git' + '@' + 'f407ae35d322e21364ea96a05e0ba4577ffa200d',
+    Var('chromium_git') + '/chromium/tools/depot_tools.git' + '@' + 'defdda0c624127fbde3a5e5d4df7b906cf566779',
 
   'src/third_party/devtools-frontend/src':
     Var('chromium_git') + '/devtools/devtools-frontend' + '@' + Var('devtools_frontend_revision'),
 
   'src/third_party/devtools-frontend-internal': {
-      'url': Var('chrome_git') + '/devtools/devtools-internal.git' + '@' + '78fc3badfe464c86799425f41059de8cc66cc9a5',
+      'url': Var('chrome_git') + '/devtools/devtools-internal.git' + '@' + '0f0a49bcd25cd4a9c3439ed2d8d1e01c53c2a2bf',
     'condition': 'checkout_src_internal',
   },
 
@@ -1705,7 +1705,7 @@
     Var('chromium_git') + '/external/github.com/cisco/openh264' + '@' + '09a4f3ec842a8932341b195c5b01e141c8a16eb7',
 
   'src/third_party/openscreen/src':
-    Var('chromium_git') + '/openscreen' + '@' + 'a3689b39e7d0945d351588f6ccdba01c9ddf533b',
+    Var('chromium_git') + '/openscreen' + '@' + '085a08030e249b40f1ec4155419f600d203cde2a',
 
   'src/third_party/openxr/src': {
     'url': Var('chromium_git') + '/external/github.com/KhronosGroup/OpenXR-SDK' + '@' + '58a00cf85c39ad5ec4dc43a769624e420c06179a',
@@ -1716,7 +1716,7 @@
     Var('pdfium_git') + '/pdfium.git' + '@' +  Var('pdfium_revision'),
 
   'src/third_party/perfetto':
-    Var('android_git') + '/platform/external/perfetto.git' + '@' + 'e314b79bfe9e5d5cd0cf47a146175d8d8e3190b2',
+    Var('android_git') + '/platform/external/perfetto.git' + '@' + '54280e702d09c2d2e92f74961f1dfb4eb6a656d8',
 
   'src/third_party/perl': {
       'url': Var('chromium_git') + '/chromium/deps/perl.git' + '@' + '6f3e5028eb65d0b4c5fdd792106ac4c84eee1eb3',
@@ -1948,7 +1948,7 @@
       'packages': [
         {
           'package': 'skia/tools/goldctl/linux-amd64',
-          'version': 'TCcuUOFbPU3igS_xlEVBu_d1nZ71zngQ3oL-Bdm0GOoC',
+          'version': 'tVDltUu_xw7_tk4k-rnp7Ua8pKhf3EEHmTwX5c6BS4AC',
         },
       ],
       'dep_type': 'cipd',
@@ -1958,7 +1958,7 @@
       'packages': [
         {
           'package': 'skia/tools/goldctl/windows-amd64',
-          'version': 'yDoSOjnzDYE9APn8a3d-FcPJ6wwwq2FXWUTOJpf2__8C',
+          'version': 'yW7os_HVMoE-wq04olb6B7GNgEmaYZYPN0ecTzPPBdkC',
         },
       ],
       'dep_type': 'cipd',
@@ -1969,7 +1969,7 @@
       'packages': [
         {
           'package': 'skia/tools/goldctl/mac-amd64',
-          'version': 'Btg2SHJpGjd1aivMSCt0RT5G6j33TDTYYfLANClZApYC',
+          'version': 'ov_DuOSHCneWQSTW6AZ9gWHEeoJxXEYDX9dl_0rA_d0C',
         },
       ],
       'dep_type': 'cipd',
@@ -1980,7 +1980,7 @@
       'packages': [
         {
           'package': 'skia/tools/goldctl/mac-arm64',
-          'version': 'SW3HcHhCf69xzWwap7_fHNSualUgKeG_3N6xTMmVuVoC',
+          'version': '3wDkZAVD4vV1dNhXfRNMOUBvODbzJ23AVmmsFbTsbkQC',
         },
       ],
       'dep_type': 'cipd',
@@ -2015,7 +2015,7 @@
     'packages': [
       {
         'package': 'chromeos_internal/apps/eche_app/app',
-        'version': 'AGaYoE5HgGxUdavPZXZ_WmzKlTj-huAqcrS67fSFdYwC',
+        'version': 'waK-UatTYqwoGrm083pgKXCnqgZtyr3MhO1cMe_QARUC',
       },
     ],
     'condition': 'checkout_chromeos and checkout_src_internal',
@@ -2037,7 +2037,7 @@
     'packages': [
       {
         'package': 'chromeos_internal/apps/media_app/app',
-        'version': 'rCTSNpbeNxtJLvVp9XGiUP90cuioMBYiLSAPN_q3R-EC',
+        'version': 'eKau9XPOtXGhEKd42DI1UN95PS9S_xRtv0yhT8knGJYC',
       },
     ],
     'condition': 'checkout_chromeos and checkout_src_internal',
@@ -4185,7 +4185,7 @@
 
   'src/ios_internal':  {
       'url': Var('chrome_git') + '/chrome/ios_internal.git' + '@' +
-        '44eb5bd19dfeb447aec9ff5565465134efd2be62',
+        'e0aa1738397afb99c992b92f6572d50a64e6d283',
       'condition': 'checkout_ios and checkout_src_internal',
   },
 
diff --git a/ash/BUILD.gn b/ash/BUILD.gn
index fb58cb0..54961d8 100644
--- a/ash/BUILD.gn
+++ b/ash/BUILD.gn
@@ -734,11 +734,8 @@
     "login/ui/auth_factor_model.h",
     "login/ui/auth_icon_view.cc",
     "login/ui/auth_icon_view.h",
-    "login/ui/auth_panel.cc",
-    "login/ui/auth_panel.h",
     "login/ui/bottom_status_indicator.cc",
     "login/ui/bottom_status_indicator.h",
-    "login/ui/factor_auth_view.h",
     "login/ui/fingerprint_auth_factor_model.cc",
     "login/ui/fingerprint_auth_factor_model.h",
     "login/ui/horizontal_image_sequence_animation_decoder.cc",
@@ -2520,8 +2517,8 @@
     "wm/tablet_mode/scoped_skip_user_session_blocked_check.h",
     "wm/tablet_mode/tablet_mode_controller.cc",
     "wm/tablet_mode/tablet_mode_controller.h",
-    "wm/tablet_mode/tablet_mode_multitask_cue.cc",
-    "wm/tablet_mode/tablet_mode_multitask_cue.h",
+    "wm/tablet_mode/tablet_mode_multitask_cue_controller.cc",
+    "wm/tablet_mode/tablet_mode_multitask_cue_controller.h",
     "wm/tablet_mode/tablet_mode_multitask_menu.cc",
     "wm/tablet_mode/tablet_mode_multitask_menu.h",
     "wm/tablet_mode/tablet_mode_multitask_menu_controller.cc",
@@ -3599,7 +3596,7 @@
     "wm/system_modal_container_layout_manager_unittest.cc",
     "wm/tablet_mode/accelerometer_test_data_literals.cc",
     "wm/tablet_mode/tablet_mode_controller_unittest.cc",
-    "wm/tablet_mode/tablet_mode_multitask_cue_unittest.cc",
+    "wm/tablet_mode/tablet_mode_multitask_cue_controller_unittest.cc",
     "wm/tablet_mode/tablet_mode_multitask_menu_unittest.cc",
     "wm/tablet_mode/tablet_mode_toggle_fullscreen_event_handler_unittest.cc",
     "wm/tablet_mode/tablet_mode_window_manager_unittest.cc",
@@ -4312,6 +4309,7 @@
     "//ui/color:mixers",
     "//ui/compositor:test_support",
     "//ui/display",
+    "//ui/display/fake",
     "//ui/display/types",
     "//ui/events:events_base",
     "//ui/events:test_support",
diff --git a/ash/app_list/views/search_box_view_unittest.cc b/ash/app_list/views/search_box_view_unittest.cc
index 8500f06..0322c712 100644
--- a/ash/app_list/views/search_box_view_unittest.cc
+++ b/ash/app_list/views/search_box_view_unittest.cc
@@ -41,6 +41,7 @@
 #include "components/vector_icons/vector_icons.h"
 #include "ui/base/ime/composition_text.h"
 #include "ui/base/l10n/l10n_util.h"
+#include "ui/color/color_provider_manager.h"
 #include "ui/compositor/layer.h"
 #include "ui/compositor/layer_animator.h"
 #include "ui/compositor/scoped_animation_duration_scale_mode.h"
diff --git a/ash/constants/notifier_catalogs.h b/ash/constants/notifier_catalogs.h
index b7485ea..1d687570e 100644
--- a/ash/constants/notifier_catalogs.h
+++ b/ash/constants/notifier_catalogs.h
@@ -184,7 +184,8 @@
   kHotspot = 169,
   kGeolocationSwitch = 170,
   kMultiCaptureOnLogin = 171,
-  kMaxValue = kMultiCaptureOnLogin
+  kFloatingWorkspace = 172,
+  kMaxValue = kFloatingWorkspace
 };
 
 // A living catalog that registers system nudges.
diff --git a/ash/frame/caption_buttons/frame_size_button_unittest.cc b/ash/frame/caption_buttons/frame_size_button_unittest.cc
index 703fc0a..795097ca 100644
--- a/ash/frame/caption_buttons/frame_size_button_unittest.cc
+++ b/ash/frame/caption_buttons/frame_size_button_unittest.cc
@@ -25,6 +25,7 @@
 #include "chromeos/ui/frame/multitask_menu/multitask_button.h"
 #include "chromeos/ui/frame/multitask_menu/multitask_menu.h"
 #include "chromeos/ui/frame/multitask_menu/multitask_menu_metrics.h"
+#include "chromeos/ui/frame/multitask_menu/multitask_menu_view_test_api.h"
 #include "chromeos/ui/frame/multitask_menu/split_button_view.h"
 #include "chromeos/ui/vector_icons/vector_icons.h"
 #include "chromeos/ui/wm/features.h"
@@ -52,6 +53,7 @@
 using ::chromeos::MultitaskButton;
 using ::chromeos::MultitaskMenu;
 using ::chromeos::MultitaskMenuEntryType;
+using ::chromeos::MultitaskMenuViewTestApi;
 using ::chromeos::SplitButtonView;
 using ::chromeos::WindowStateType;
 
@@ -697,7 +699,8 @@
   ui::test::EventGenerator* generator = GetEventGenerator();
   ShowMultitaskMenu();
   generator->MoveMouseTo(CenterPointInScreen(
-      GetMultitaskMenu()->multitask_menu_view()->float_button_for_testing()));
+      MultitaskMenuViewTestApi(GetMultitaskMenu()->multitask_menu_view())
+          .GetFloatButton()));
   generator->ClickLeftButton();
   EXPECT_TRUE(window_state()->IsFloated());
   histogram_tester.ExpectBucketCount(
@@ -710,10 +713,10 @@
   base::HistogramTester histogram_tester;
   EXPECT_TRUE(window_state()->IsNormalStateType());
   ShowMultitaskMenu();
-  LeftClickOn(GetMultitaskMenu()
-                  ->multitask_menu_view()
-                  ->half_button_for_testing()
-                  ->GetLeftTopButton());
+  LeftClickOn(
+      MultitaskMenuViewTestApi(GetMultitaskMenu()->multitask_menu_view())
+          .GetHalfButton()
+          ->GetLeftTopButton());
   EXPECT_EQ(WindowStateType::kPrimarySnapped, window_state()->GetStateType());
   histogram_tester.ExpectBucketCount(
       chromeos::GetActionTypeHistogramName(),
@@ -728,10 +731,10 @@
   base::i18n::SetRTLForTesting(true);
 
   ShowMultitaskMenu();
-  LeftClickOn(GetMultitaskMenu()
-                  ->multitask_menu_view()
-                  ->half_button_for_testing()
-                  ->GetLeftTopButton());
+  LeftClickOn(
+      MultitaskMenuViewTestApi(GetMultitaskMenu()->multitask_menu_view())
+          .GetHalfButton()
+          ->GetLeftTopButton());
   EXPECT_EQ(WindowStateType::kPrimarySnapped, window_state()->GetStateType());
   EXPECT_EQ(gfx::Rect(400, 552), GetWidget()->GetWindowBoundsInScreen());
 
@@ -739,7 +742,8 @@
   ShowMultitaskMenu();
   PressAndReleaseKey(ui::VKEY_MENU, ui::EF_ALT_DOWN);
   ASSERT_TRUE(
-      GetMultitaskMenu()->multitask_menu_view()->is_reversed_for_testing());
+      MultitaskMenuViewTestApi(GetMultitaskMenu()->multitask_menu_view())
+          .GetIsReversed());
   LeftClickOn(GetMultitaskMenu()
                   ->multitask_menu_view()
                   ->partial_button()
@@ -766,11 +770,11 @@
   // Click on the left side of the half button. It should be in secondary
   // snapped state, because in this orientation secondary snapped is actually
   // physically on the left side.
-  GetEventGenerator()->MoveMouseToInHost(GetMultitaskMenu()
-                                             ->multitask_menu_view()
-                                             ->half_button_for_testing()
-                                             ->GetBoundsInScreen()
-                                             .left_center());
+  GetEventGenerator()->MoveMouseToInHost(
+      MultitaskMenuViewTestApi(GetMultitaskMenu()->multitask_menu_view())
+          .GetHalfButton()
+          ->GetBoundsInScreen()
+          .left_center());
   GetEventGenerator()->ClickLeftButton();
   EXPECT_EQ(WindowStateType::kSecondarySnapped, window_state()->GetStateType());
 }
@@ -830,11 +834,10 @@
 TEST_F(MultitaskMenuTest, TestMultitaskMenuFullFunctionality) {
   base::HistogramTester histogram_tester;
   ASSERT_TRUE(window_state()->IsNormalStateType());
-  ui::test::EventGenerator* generator = GetEventGenerator();
   ShowMultitaskMenu();
-  generator->MoveMouseTo(CenterPointInScreen(
-      GetMultitaskMenu()->multitask_menu_view()->full_button_for_testing()));
-  generator->ClickLeftButton();
+  LeftClickOn(
+      MultitaskMenuViewTestApi(GetMultitaskMenu()->multitask_menu_view())
+          .GetFullButton());
   EXPECT_TRUE(window_state()->IsFullscreen());
   histogram_tester.ExpectBucketCount(
       chromeos::GetActionTypeHistogramName(),
@@ -920,10 +923,10 @@
 TEST_F(MultitaskMenuTest, CloseOnClickOutside) {
   // Snap the window to half so we can click outside the window bounds.
   ShowMultitaskMenu();
-  LeftClickOn(GetMultitaskMenu()
-                  ->multitask_menu_view()
-                  ->half_button_for_testing()
-                  ->GetLeftTopButton());
+  LeftClickOn(
+      MultitaskMenuViewTestApi(GetMultitaskMenu()->multitask_menu_view())
+          .GetHalfButton()
+          ->GetLeftTopButton());
   EXPECT_EQ(WindowStateType::kPrimarySnapped, window_state()->GetStateType());
 
   ShowMultitaskMenu();
@@ -1004,7 +1007,8 @@
   ShowMultitaskMenu();
   PressAndReleaseKey(ui::VKEY_MENU, ui::EF_ALT_DOWN);
   ASSERT_TRUE(
-      GetMultitaskMenu()->multitask_menu_view()->is_reversed_for_testing());
+      MultitaskMenuViewTestApi(GetMultitaskMenu()->multitask_menu_view())
+          .GetIsReversed());
   LeftClickOn(GetMultitaskMenu()
                   ->multitask_menu_view()
                   ->partial_button()
@@ -1018,7 +1022,8 @@
   ShowMultitaskMenu();
   PressAndReleaseKey(ui::VKEY_MENU, ui::EF_ALT_DOWN);
   ASSERT_TRUE(
-      GetMultitaskMenu()->multitask_menu_view()->is_reversed_for_testing());
+      MultitaskMenuViewTestApi(GetMultitaskMenu()->multitask_menu_view())
+          .GetIsReversed());
   LeftClickOn(GetMultitaskMenu()
                   ->multitask_menu_view()
                   ->partial_button()
@@ -1074,9 +1079,10 @@
     // in snapped state.
     MultitaskMenu* multitask_menu = GetMultitaskMenu();
     ASSERT_TRUE(multitask_menu);
-    views::Button* left_half_button = multitask_menu->multitask_menu_view()
-                                          ->half_button_for_testing()
-                                          ->GetLeftTopButton();
+    views::Button* left_half_button =
+        MultitaskMenuViewTestApi(multitask_menu->multitask_menu_view())
+            .GetHalfButton()
+            ->GetLeftTopButton();
     move_to_center(left_half_button, touch);
     EXPECT_EQ(views::Button::STATE_HOVERED, left_half_button->GetState());
     release(touch);
@@ -1098,7 +1104,8 @@
     multitask_menu = GetMultitaskMenu();
     ASSERT_TRUE(multitask_menu);
     views::Button* float_button =
-        multitask_menu->multitask_menu_view()->float_button_for_testing();
+        MultitaskMenuViewTestApi(multitask_menu->multitask_menu_view())
+            .GetFloatButton();
     move_to_center(float_button, touch);
     EXPECT_EQ(views::Button::STATE_HOVERED, float_button->GetState());
     release(touch);
@@ -1121,15 +1128,17 @@
   EXPECT_FALSE(focus_manager->GetFocusedView());
 
   // Press tab. The left button of the half button is focused.
-  views::Button* left_half_button = multitask_menu->multitask_menu_view()
-                                        ->half_button_for_testing()
-                                        ->GetLeftTopButton();
+  views::Button* left_half_button =
+      MultitaskMenuViewTestApi(multitask_menu->multitask_menu_view())
+          .GetHalfButton()
+          ->GetLeftTopButton();
   PressAndReleaseKey(ui::VKEY_TAB);
   EXPECT_EQ(left_half_button, focus_manager->GetFocusedView());
 
   // Press shift+tab. The last button (float button) is focused.
   views::Button* float_button =
-      multitask_menu->multitask_menu_view()->float_button_for_testing();
+      MultitaskMenuViewTestApi(multitask_menu->multitask_menu_view())
+          .GetFloatButton();
   PressAndReleaseKey(ui::VKEY_TAB, ui::EF_SHIFT_DOWN);
   EXPECT_EQ(float_button, focus_manager->GetFocusedView());
 
diff --git a/ash/login/ui/auth_panel.cc b/ash/login/ui/auth_panel.cc
deleted file mode 100644
index f5ad6327..0000000
--- a/ash/login/ui/auth_panel.cc
+++ /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.
-
-#include "ash/login/ui/auth_panel.h"
-#include "base/notreached.h"
-
-namespace ash {
-
-AuthPanel::AuthPanel() = default;
-
-AuthPanel::~AuthPanel() = default;
-
-void AuthPanel::InitializeUi(AuthFactorsSet factors,
-                             AuthHubConnector* connector) {
-  NOTIMPLEMENTED();
-}
-
-void AuthPanel::OnFactorListChanged(FactorsStatusMap factors_with_status) {
-  NOTIMPLEMENTED();
-}
-
-void AuthPanel::OnFactorStatusesChanged(FactorsStatusMap incremental_update) {
-  NOTIMPLEMENTED();
-}
-
-void AuthPanel::OnFactorAuthFailure(AshAuthFactor factor) {
-  NOTIMPLEMENTED();
-}
-
-void AuthPanel::OnFactorAuthSuccess(AshAuthFactor factor) {
-  NOTIMPLEMENTED();
-}
-
-void AuthPanel::OnEndAuthentication() {
-  NOTIMPLEMENTED();
-}
-
-}  // namespace ash
diff --git a/ash/shell.cc b/ash/shell.cc
index 6434cbdc..0f6256f 100644
--- a/ash/shell.cc
+++ b/ash/shell.cc
@@ -325,7 +325,8 @@
   instance_->Init(init_params.context_factory, init_params.local_state,
                   std::move(init_params.keyboard_ui_factory),
                   std::move(init_params.quick_pair_mediator_factory),
-                  init_params.dbus_bus);
+                  init_params.dbus_bus,
+                  std::move(init_params.native_display_delegate));
   return instance_;
 }
 
@@ -1112,7 +1113,9 @@
     std::unique_ptr<keyboard::KeyboardUIFactory> keyboard_ui_factory,
     std::unique_ptr<ash::quick_pair::Mediator::Factory>
         quick_pair_mediator_factory,
-    scoped_refptr<dbus::Bus> dbus_bus) {
+    scoped_refptr<dbus::Bus> dbus_bus,
+    std::unique_ptr<display::NativeDisplayDelegate> native_display_delegate) {
+  native_display_delegate_ = std::move(native_display_delegate);
   login_unlock_throughput_recorder_ =
       std::make_unique<LoginUnlockThroughputRecorder>();
 
@@ -1718,8 +1721,11 @@
 }
 
 void Shell::InitializeDisplayManager() {
-  display_manager_->InitConfigurator(
-      ui::OzonePlatform::GetInstance()->CreateNativeDisplayDelegate());
+  if (!native_display_delegate_) {
+    native_display_delegate_ =
+        ui::OzonePlatform::GetInstance()->CreateNativeDisplayDelegate();
+  }
+  display_manager_->InitConfigurator(std::move(native_display_delegate_));
   display_configuration_controller_ =
       std::make_unique<DisplayConfigurationController>(
           display_manager_.get(), window_tree_host_manager_.get());
diff --git a/ash/shell.h b/ash/shell.h
index dc393a37..661f14f 100644
--- a/ash/shell.h
+++ b/ash/shell.h
@@ -872,12 +872,14 @@
   explicit Shell(std::unique_ptr<ShellDelegate> shell_delegate);
   ~Shell() override;
 
-  void Init(ui::ContextFactory* context_factory,
-            PrefService* local_state,
-            std::unique_ptr<keyboard::KeyboardUIFactory> keyboard_ui_factory,
-            std::unique_ptr<ash::quick_pair::Mediator::Factory>
-                quick_pair_mediator_factory,
-            scoped_refptr<dbus::Bus> dbus_bus);
+  void Init(
+      ui::ContextFactory* context_factory,
+      PrefService* local_state,
+      std::unique_ptr<keyboard::KeyboardUIFactory> keyboard_ui_factory,
+      std::unique_ptr<ash::quick_pair::Mediator::Factory>
+          quick_pair_mediator_factory,
+      scoped_refptr<dbus::Bus> dbus_bus,
+      std::unique_ptr<display::NativeDisplayDelegate> native_display_delegate);
 
   // Initializes the display manager and related components.
   void InitializeDisplayManager();
@@ -1186,6 +1188,8 @@
 
   std::unique_ptr<quick_pair::Mediator> quick_pair_mediator_;
 
+  std::unique_ptr<display::NativeDisplayDelegate> native_display_delegate_;
+
   base::ObserverList<ShellObserver>::Unchecked shell_observers_;
 
   base::WeakPtrFactory<Shell> weak_factory_{this};
diff --git a/ash/shell_init_params.cc b/ash/shell_init_params.cc
index 11c9a90..0961f18 100644
--- a/ash/shell_init_params.cc
+++ b/ash/shell_init_params.cc
@@ -7,6 +7,7 @@
 #include "ash/keyboard/ui/keyboard_ui_factory.h"
 #include "ash/shell_delegate.h"
 #include "base/values.h"
+#include "ui/display/types/native_display_delegate.h"
 
 namespace ash {
 
diff --git a/ash/shell_init_params.h b/ash/shell_init_params.h
index 3ad100f0..f0f82fd 100644
--- a/ash/shell_init_params.h
+++ b/ash/shell_init_params.h
@@ -47,6 +47,9 @@
   // Bus used by dbus clients. May be null in tests or when not running on a
   // device, in which case fake clients will be created.
   scoped_refptr<dbus::Bus> dbus_bus;
+
+  // A native display delegate used in the shell.
+  std::unique_ptr<display::NativeDisplayDelegate> native_display_delegate;
 };
 
 }  // namespace ash
diff --git a/ash/system/holding_space/downloads_section.cc b/ash/system/holding_space/downloads_section.cc
index 7e7cfa7..c03ccfb9 100644
--- a/ash/system/holding_space/downloads_section.cc
+++ b/ash/system/holding_space/downloads_section.cc
@@ -37,9 +37,17 @@
 #include "ui/views/view_class_properties.h"
 
 namespace ash {
-
 namespace {
 
+// Helpers ---------------------------------------------------------------------
+
+std::unique_ptr<views::BoxLayout> WithCrossAxisAlignment(
+    std::unique_ptr<views::BoxLayout> layout,
+    views::BoxLayout::CrossAxisAlignment cross_axis_alignment) {
+  layout->set_cross_axis_alignment(cross_axis_alignment);
+  return layout;
+}
+
 // Header ----------------------------------------------------------------------
 
 class Header : public views::Button {
@@ -52,9 +60,11 @@
             l10n_util::GetStringUTF16(IDS_ASH_HOLDING_SPACE_DOWNLOADS_TITLE))
         .SetCallback(
             base::BindRepeating(&Header::OnPressed, base::Unretained(this)))
-        .SetLayoutManager(std::make_unique<views::BoxLayout>(
-            views::BoxLayout::Orientation::kHorizontal, gfx::Insets(),
-            kHoldingSpaceSectionHeaderSpacing))
+        .SetLayoutManager(WithCrossAxisAlignment(
+            std::make_unique<views::BoxLayout>(
+                views::BoxLayout::Orientation::kHorizontal, gfx::Insets(),
+                kHoldingSpaceSectionHeaderSpacing),
+            views::BoxLayout::CrossAxisAlignment::kCenter))
         .AddChildren(
             holding_space_ui::CreateSectionHeaderLabel(
                 IDS_ASH_HOLDING_SPACE_DOWNLOADS_TITLE)
diff --git a/ash/system/holding_space/holding_space_tray.cc b/ash/system/holding_space/holding_space_tray.cc
index 3362e3c..255e4b3 100644
--- a/ash/system/holding_space/holding_space_tray.cc
+++ b/ash/system/holding_space/holding_space_tray.cc
@@ -40,6 +40,7 @@
 #include "base/functional/callback_helpers.h"
 #include "base/ranges/algorithm.h"
 #include "base/task/sequenced_task_runner.h"
+#include "chromeos/constants/chromeos_features.h"
 #include "components/prefs/pref_change_registrar.h"
 #include "ui/aura/client/drag_drop_client.h"
 #include "ui/base/dragdrop/drag_drop_types.h"
@@ -120,17 +121,49 @@
          !HoldingSpaceItem::IsSuggestionType(item->type());
 }
 
+// Creates a model representing a foreground `tray` image with the specified
+// `vector_icon`. Note that when Jelly is enabled, the image must be repainted
+// on changes to `tray` activation.
+ui::ImageModel CreateForegroundImageModel(const HoldingSpaceTray* tray,
+                                          const gfx::VectorIcon& vector_icon) {
+  // When Jelly is disabled, `tray` activation does not affect color.
+  if (!chromeos::features::IsJellyEnabled()) {
+    return ui::ImageModel::FromVectorIcon(
+        vector_icon, kColorAshIconColorPrimary, kHoldingSpaceTrayIconSize);
+  }
+
+  // When Jelly is enabled, `tray` activation affects color.
+  ui::ImageModel active = ui::ImageModel::FromVectorIcon(
+      vector_icon, cros_tokens::kCrosSysSystemOnPrimaryContainer,
+      kHoldingSpaceTrayIconSize);
+  ui::ImageModel inactive = ui::ImageModel::FromVectorIcon(
+      vector_icon, cros_tokens::kCrosSysOnSurface, kHoldingSpaceTrayIconSize);
+
+  // Create a model which considers `tray` activation during rasterization.
+  return ui::ImageModel::FromImageGenerator(
+      base::BindRepeating(
+          [](const HoldingSpaceTray* tray, const ui::ImageModel& active,
+             const ui::ImageModel& inactive,
+             const ui::ColorProvider* color_provider) {
+            return (tray->is_active() ? active : inactive)
+                .Rasterize(color_provider);
+          },
+          base::Unretained(tray), base::OwnedRef(std::move(active)),
+          base::OwnedRef(std::move(inactive))),
+      gfx::Size(kHoldingSpaceTrayIconSize, kHoldingSpaceTrayIconSize));
+}
+
 // Creates the default tray icon.
-std::unique_ptr<views::ImageView> CreateDefaultTrayIcon() {
+std::unique_ptr<views::ImageView> CreateDefaultTrayIcon(
+    const HoldingSpaceTray* tray) {
   auto icon = std::make_unique<views::ImageView>();
   icon->SetID(kHoldingSpaceTrayDefaultIconId);
   icon->SetPreferredSize(gfx::Size(kTrayItemSize, kTrayItemSize));
   icon->SetPaintToLayer();
   icon->layer()->SetFillsBoundsOpaquely(false);
-  icon->SetImage(ui::ImageModel::FromVectorIcon(
-      features::IsHoldingSpaceRefreshEnabled() ? kHoldingSpaceRefreshIcon
-                                               : kHoldingSpaceIcon,
-      kColorAshIconColorPrimary, kHoldingSpaceTrayIconSize));
+  icon->SetImage(CreateForegroundImageModel(
+      tray, features::IsHoldingSpaceRefreshEnabled() ? kHoldingSpaceRefreshIcon
+                                                     : kHoldingSpaceIcon));
   return icon;
 }
 
@@ -138,7 +171,8 @@
 // Creates the icon to be parented by the drop target overlay to indicate that
 // the parent view is a drop target and is capable of handling the current drag
 // payload.
-std::unique_ptr<views::ImageView> CreateDropTargetIcon() {
+std::unique_ptr<views::ImageView> CreateDropTargetIcon(
+    const HoldingSpaceTray* tray) {
   auto icon = std::make_unique<views::ImageView>();
   icon->SetHorizontalAlignment(views::ImageView::Alignment::kCenter);
   icon->SetVerticalAlignment(views::ImageView::Alignment::kCenter);
@@ -146,8 +180,7 @@
       gfx::Size(kHoldingSpaceIconSize, kHoldingSpaceIconSize));
   icon->SetPaintToLayer();
   icon->layer()->SetFillsBoundsOpaquely(false);
-  icon->SetImage(ui::ImageModel::FromVectorIcon(
-      views::kUnpinIcon, kColorAshIconColorPrimary, kHoldingSpaceIconSize));
+  icon->SetImage(CreateForegroundImageModel(tray, views::kUnpinIcon));
   return icon;
 }
 
@@ -198,7 +231,8 @@
   }
 
   // Default icon.
-  default_tray_icon_ = tray_container()->AddChildView(CreateDefaultTrayIcon());
+  default_tray_icon_ =
+      tray_container()->AddChildView(CreateDefaultTrayIcon(this));
 
   // Previews icon.
   previews_tray_icon_ = tray_container()->AddChildView(
@@ -215,7 +249,7 @@
 
   // Drop target icon.
   drop_target_icon_ =
-      drop_target_overlay_->AddChildView(CreateDropTargetIcon());
+      drop_target_overlay_->AddChildView(CreateDropTargetIcon(this));
 
   // Progress indicator.
   // NOTE: The `progress_indicator_` will only be visible when:
@@ -544,6 +578,12 @@
   return context_menu_model;
 }
 
+// TODO(http://b/287151663): Fix progress indicator.
+void HoldingSpaceTray::UpdateTrayItemColor(bool is_active) {
+  default_tray_icon_->SchedulePaint();
+  drop_target_icon_->SchedulePaint();
+}
+
 void HoldingSpaceTray::OnHoldingSpaceModelAttached(HoldingSpaceModel* model) {
   // When the `model` is attached the session is either being started/unlocked
   // or the active profile is being changed. It's also possible that the status
diff --git a/ash/system/holding_space/holding_space_tray.h b/ash/system/holding_space/holding_space_tray.h
index 76f455c..471b45d0 100644
--- a/ash/system/holding_space/holding_space_tray.h
+++ b/ash/system/holding_space/holding_space_tray.h
@@ -67,9 +67,6 @@
   // TrayBackgroundView:
   void Initialize() override;
   void ClickedOutsideBubble() override;
-  // TODO(http://b/287098833): No need to override since the update will be
-  // handled in the linked bug.
-  void UpdateTrayItemColor(bool is_active) override {}
   std::u16string GetAccessibleNameForTray() override;
   views::View* GetTooltipHandlerForPoint(const gfx::Point& point) override;
   std::u16string GetTooltipText(const gfx::Point& point) const override;
@@ -94,6 +91,7 @@
   void OnThemeChanged() override;
   void OnShouldShowAnimationChanged(bool should_animate) override;
   std::unique_ptr<ui::SimpleMenuModel> CreateContextMenuModel() override;
+  void UpdateTrayItemColor(bool is_active) override;
 
   // Invoke to cause the holding space tray to recalculate and update its
   // visibility. Note that this may or may not result in a visibility change
diff --git a/ash/system/toast/anchored_nudge_manager_impl.cc b/ash/system/toast/anchored_nudge_manager_impl.cc
index f1651f4..ae373fa3 100644
--- a/ash/system/toast/anchored_nudge_manager_impl.cc
+++ b/ash/system/toast/anchored_nudge_manager_impl.cc
@@ -10,6 +10,8 @@
 
 #include "ash/constants/ash_features.h"
 #include "ash/public/cpp/system/anchored_nudge_data.h"
+#include "ash/session/session_controller_impl.h"
+#include "ash/shell.h"
 #include "ash/system/toast/anchored_nudge.h"
 #include "base/containers/contains.h"
 #include "ui/aura/window.h"
@@ -183,10 +185,13 @@
 
 AnchoredNudgeManagerImpl::AnchoredNudgeManagerImpl() {
   DCHECK(features::IsSystemNudgeV2Enabled());
+  Shell::Get()->session_controller()->AddObserver(this);
 }
 
 AnchoredNudgeManagerImpl::~AnchoredNudgeManagerImpl() {
   CloseAllNudges();
+
+  Shell::Get()->session_controller()->RemoveObserver(this);
 }
 
 void AnchoredNudgeManagerImpl::Show(AnchoredNudgeData& nudge_data) {
@@ -234,7 +239,8 @@
   // using the anchor window bounds.
   anchored_nudge_ptr->SizeToContents();
 
-  anchored_nudge_widget->Show();
+  // The widget is not activated so the nudge does not steal focus.
+  anchored_nudge_widget->ShowInactive();
 
   nudge_widget_observers_[id] =
       std::make_unique<NudgeWidgetObserver>(anchored_nudge_ptr, this);
@@ -291,6 +297,11 @@
   }
 }
 
+void AnchoredNudgeManagerImpl::OnSessionStateChanged(
+    session_manager::SessionState state) {
+  CloseAllNudges();
+}
+
 bool AnchoredNudgeManagerImpl::IsNudgeShown(const std::string& id) {
   return base::Contains(shown_nudges_, id);
 }
diff --git a/ash/system/toast/anchored_nudge_manager_impl.h b/ash/system/toast/anchored_nudge_manager_impl.h
index 6e81f49..6dd446ea 100644
--- a/ash/system/toast/anchored_nudge_manager_impl.h
+++ b/ash/system/toast/anchored_nudge_manager_impl.h
@@ -9,6 +9,7 @@
 #include <string>
 
 #include "ash/ash_export.h"
+#include "ash/public/cpp/session/session_observer.h"
 #include "ash/public/cpp/system/anchored_nudge_data.h"
 #include "ash/public/cpp/system/anchored_nudge_manager.h"
 #include "ash/system/toast/anchored_nudge.h"
@@ -25,7 +26,8 @@
 struct AnchoredNudgeData;
 
 // Class managing anchored nudge requests.
-class ASH_EXPORT AnchoredNudgeManagerImpl : public AnchoredNudgeManager {
+class ASH_EXPORT AnchoredNudgeManagerImpl : public AnchoredNudgeManager,
+                                            public SessionObserver {
  public:
   AnchoredNudgeManagerImpl();
   AnchoredNudgeManagerImpl(const AnchoredNudgeManagerImpl&) = delete;
@@ -46,6 +48,9 @@
   // AnchoredNudge::Delegate:
   void OnNudgeHoverStateChanged(const std::string& nudge_id, bool is_hovering);
 
+  // SessionObserver:
+  void OnSessionStateChanged(session_manager::SessionState state) override;
+
   // Returns true if `id` is stored in `shown_nudges_`.
   bool IsNudgeShown(const std::string& id);
 
diff --git a/ash/system/toast/anchored_nudge_manager_impl_unittest.cc b/ash/system/toast/anchored_nudge_manager_impl_unittest.cc
index 4641020..5e83c1b 100644
--- a/ash/system/toast/anchored_nudge_manager_impl_unittest.cc
+++ b/ash/system/toast/anchored_nudge_manager_impl_unittest.cc
@@ -7,6 +7,7 @@
 #include "ash/constants/ash_features.h"
 #include "ash/public/cpp/system/anchored_nudge_data.h"
 #include "ash/root_window_controller.h"
+#include "ash/session/session_controller_impl.h"
 #include "ash/shelf/shelf.h"
 #include "ash/shell.h"
 #include "ash/style/system_toast_style.h"
@@ -21,6 +22,17 @@
 
 namespace ash {
 
+namespace {
+
+void SetLockedState(bool locked) {
+  SessionInfo info;
+  info.state = locked ? session_manager::SessionState::LOCKED
+                      : session_manager::SessionState::ACTIVE;
+  Shell::Get()->session_controller()->SetSessionInfo(info);
+}
+
+}  // namespace
+
 class AnchoredNudgeManagerImplTest : public AshTestBase {
  public:
   AnchoredNudgeManagerImplTest()
@@ -73,6 +85,9 @@
   EXPECT_EQ(text, nudge->GetBodyText());
   EXPECT_EQ(anchor_view, nudge->GetAnchorView());
 
+  // Ensure the nudge widget was not activated when shown.
+  EXPECT_FALSE(nudge->GetWidget()->IsActive());
+
   // Cancel the nudge, expect it to be removed from the shown nudges map.
   CancelNudge(id);
   EXPECT_FALSE(GetShownNudges()[id]);
@@ -377,6 +392,32 @@
   EXPECT_FALSE(GetShownNudges()[id]);
 }
 
+// Tests that nudges are destroyed on session state changes.
+TEST_F(AnchoredNudgeManagerImplTest, NudgeCloses_OnSessionStateChanged) {
+  std::unique_ptr<views::Widget> widget = CreateFramelessTestWidget();
+
+  // Set up nudge data contents.
+  const std::string id = "id";
+  auto* anchor_view = widget->SetContentsView(std::make_unique<views::View>());
+  auto nudge_data = CreateBaseNudgeData(id, anchor_view);
+
+  // Show a nudge.
+  anchored_nudge_manager()->Show(nudge_data);
+  EXPECT_TRUE(GetShownNudges()[id]);
+
+  // Lock screen, nudge should have closed.
+  SetLockedState(true);
+  EXPECT_FALSE(GetShownNudges()[id]);
+
+  // Show a nudge in the locked state.
+  anchored_nudge_manager()->Show(nudge_data);
+  EXPECT_TRUE(GetShownNudges()[id]);
+
+  // Unlock screen, nudge should have closed.
+  SetLockedState(false);
+  EXPECT_FALSE(GetShownNudges()[id]);
+}
+
 // Tests that nudges with `has_infinite_duration` set to true will not expire
 // after the default duration time has passed.
 TEST_F(AnchoredNudgeManagerImplTest, NudgeWithInfiniteDuration) {
diff --git a/ash/test/ash_test_helper.cc b/ash/test/ash_test_helper.cc
index d484301..3ccf65e 100644
--- a/ash/test/ash_test_helper.cc
+++ b/ash/test/ash_test_helper.cc
@@ -60,6 +60,7 @@
 #include "ui/base/ime/ash/mock_input_method_manager_impl.h"
 #include "ui/color/color_provider_manager.h"
 #include "ui/display/display_switches.h"
+#include "ui/display/fake/fake_display_delegate.h"
 #include "ui/display/manager/display_manager.h"
 #include "ui/display/test/display_manager_test_api.h"
 #include "ui/display/util/display_util.h"
@@ -343,6 +344,8 @@
     shell_init_params.quick_pair_mediator_factory =
         std::make_unique<quick_pair::FakeQuickPairMediatorFactory>();
   }
+  shell_init_params.native_display_delegate =
+      std::make_unique<display::FakeDisplayDelegate>();
   Shell::CreateInstance(std::move(shell_init_params));
   Shell* shell = Shell::Get();
 
diff --git a/ash/touch/touch_selection_magnifier_runner_ash.cc b/ash/touch/touch_selection_magnifier_runner_ash.cc
index 1e14170..640967f 100644
--- a/ash/touch/touch_selection_magnifier_runner_ash.cc
+++ b/ash/touch/touch_selection_magnifier_runner_ash.cc
@@ -8,6 +8,7 @@
 #include "ui/aura/window.h"
 #include "ui/chromeos/styles/cros_tokens_color_mappings.h"
 #include "ui/color/color_provider.h"
+#include "ui/color/color_provider_manager.h"
 #include "ui/compositor/layer.h"
 #include "ui/compositor/paint_recorder.h"
 #include "ui/compositor/scoped_layer_animation_settings.h"
diff --git a/ash/wallpaper/online_wallpaper_variant_info_fetcher.cc b/ash/wallpaper/online_wallpaper_variant_info_fetcher.cc
index d0baf56..b41a8fc 100644
--- a/ash/wallpaper/online_wallpaper_variant_info_fetcher.cc
+++ b/ash/wallpaper/online_wallpaper_variant_info_fetcher.cc
@@ -10,11 +10,13 @@
 #include "ash/public/cpp/wallpaper/wallpaper_controller_client.h"
 #include "ash/public/cpp/wallpaper/wallpaper_info.h"
 #include "ash/wallpaper/wallpaper_constants.h"
+#include "ash/wallpaper/wallpaper_metrics_manager.h"
 #include "ash/wallpaper/wallpaper_utils/wallpaper_online_variant_utils.h"
 #include "ash/webui/personalization_app/proto/backdrop_wallpaper.pb.h"
 #include "base/functional/bind.h"
 #include "base/functional/callback.h"
 #include "base/logging.h"
+#include "base/metrics/histogram_functions.h"
 #include "base/ranges/algorithm.h"
 #include "base/strings/string_piece.h"
 #include "base/task/sequenced_task_runner.h"
@@ -165,8 +167,17 @@
     return;
   }
 
-  // For requests from existing WallpaperInfo, location is always populated.
-  DCHECK(!info.location.empty());
+  // For requests from existing WallpaperInfo, location should always be
+  // populated. In the event of very old wallpapers, treat them as failure.
+  if (info.location.empty()) {
+    LOG(WARNING)
+        << "Failed to determine wallpaper url. This should only happen for "
+           "very old wallpapers.";
+    base::UmaHistogramEnumeration("Ash.Wallpaper.Online.Result",
+                                  SetWallpaperResult::kInvalidState);
+    std::move(callback).Run(absl::nullopt);
+    return;
+  }
 
   bool daily = IsDaily(info);
   auto request = std::make_unique<OnlineWallpaperRequest>(
diff --git a/ash/wallpaper/online_wallpaper_variant_info_fetcher_unittest.cc b/ash/wallpaper/online_wallpaper_variant_info_fetcher_unittest.cc
index 88a52d0..9c127f3 100644
--- a/ash/wallpaper/online_wallpaper_variant_info_fetcher_unittest.cc
+++ b/ash/wallpaper/online_wallpaper_variant_info_fetcher_unittest.cc
@@ -8,10 +8,12 @@
 #include "ash/public/cpp/wallpaper/wallpaper_info.h"
 #include "ash/wallpaper/test_wallpaper_controller_client.h"
 #include "ash/wallpaper/wallpaper_constants.h"
+#include "ash/wallpaper/wallpaper_metrics_manager.h"
 #include "base/functional/callback_forward.h"
 #include "base/run_loop.h"
 #include "base/strings/stringprintf.h"
 #include "base/test/gtest_util.h"
+#include "base/test/metrics/histogram_tester.h"
 #include "base/test/task_environment.h"
 #include "base/test/test_future.h"
 #include "components/account_id/account_id.h"
@@ -81,6 +83,7 @@
 
  protected:
   base::test::SingleThreadTaskEnvironment task_environment_;
+  base::HistogramTester histogram_tester_;
 
   TestWallpaperControllerClient client_;
   std::unique_ptr<OnlineWallpaperVariantInfoFetcher> wallpaper_fetcher_;
@@ -345,5 +348,23 @@
   EXPECT_FALSE(test_future.Get());
 }
 
+TEST_F(OnlineWallpaperVariantInfoFetcherTest,
+       FetchOnlineWallpaper_FromInfo_NoLocation) {
+  WallpaperInfo info = WallpaperInfo(/*in_location=*/"",
+                                     WallpaperLayout::WALLPAPER_LAYOUT_CENTER,
+                                     WallpaperType::kOnline, base::Time::Now());
+
+  base::test::TestFuture<absl::optional<OnlineWallpaperParams>> test_future;
+  wallpaper_fetcher_->FetchOnlineWallpaper(
+      kAccount1, info, ScheduleCheckpoint::kSunset, test_future.GetCallback());
+
+  // Callback is called
+  auto result = test_future.Get();
+  EXPECT_FALSE(result);
+
+  histogram_tester_.ExpectBucketCount("Ash.Wallpaper.Online.Result",
+                                      SetWallpaperResult::kInvalidState, 1);
+}
+
 }  // namespace
 }  // namespace ash
diff --git a/ash/webui/diagnostics_ui/resources/realtime_cpu_chart.html b/ash/webui/diagnostics_ui/resources/realtime_cpu_chart.html
index 07390c86..de4c263 100644
--- a/ash/webui/diagnostics_ui/resources/realtime_cpu_chart.html
+++ b/ash/webui/diagnostics_ui/resources/realtime_cpu_chart.html
@@ -7,6 +7,11 @@
     font: var(--cros-annotation-1-font);
   }
 
+  :host-context(body.jelly-enabled) #legend-bar {
+    border-radius: 4px;
+    height: 4px;
+  }
+
   g.tick line {
     shape-rendering: crispEdges;
     stroke: var(--cros-separator-color);
diff --git a/ash/wm/desks/templates/saved_desk_presenter.cc b/ash/wm/desks/templates/saved_desk_presenter.cc
index b366fbf..6cfabcd 100644
--- a/ash/wm/desks/templates/saved_desk_presenter.cc
+++ b/ash/wm/desks/templates/saved_desk_presenter.cc
@@ -455,6 +455,10 @@
         AppendDuplicateNumberToDuplicateName(saved_desk->template_name()));
   }
 
+  // TODO(crbug.com/1442076): Remove after issue is root caused.
+  LOG(ERROR) << "Windows written to file by Ash: \n"
+             << saved_desk->ToDebugString();
+
   // Save or update `desk_template` as an entry in DeskModel.
   GetDeskModel()->AddOrUpdateEntry(
       std::move(saved_desk),
diff --git a/ash/wm/multitask_menu_nudge_controller_unittest.cc b/ash/wm/multitask_menu_nudge_controller_unittest.cc
index f17141eb..073d520 100644
--- a/ash/wm/multitask_menu_nudge_controller_unittest.cc
+++ b/ash/wm/multitask_menu_nudge_controller_unittest.cc
@@ -14,7 +14,7 @@
 #include "ash/wm/multitask_menu_nudge_delegate_ash.h"
 #include "ash/wm/splitview/split_view_controller.h"
 #include "ash/wm/tablet_mode/tablet_mode_controller_test_api.h"
-#include "ash/wm/tablet_mode/tablet_mode_multitask_cue.h"
+#include "ash/wm/tablet_mode/tablet_mode_multitask_cue_controller.h"
 #include "ash/wm/tablet_mode/tablet_mode_multitask_menu_controller.h"
 #include "ash/wm/tablet_mode/tablet_mode_window_manager.h"
 #include "ash/wm/window_state.h"
@@ -27,6 +27,7 @@
 #include "chromeos/ui/frame/immersive/immersive_fullscreen_controller_test_api.h"
 #include "chromeos/ui/frame/multitask_menu/multitask_button.h"
 #include "chromeos/ui/frame/multitask_menu/multitask_menu.h"
+#include "chromeos/ui/frame/multitask_menu/multitask_menu_view_test_api.h"
 #include "chromeos/ui/wm/features.h"
 #include "ui/compositor/scoped_animation_duration_scale_mode.h"
 #include "ui/display/screen.h"
@@ -45,7 +46,7 @@
     return TabletModeControllerTestApi()
         .tablet_mode_window_manager()
         ->tablet_mode_multitask_menu_controller()
-        ->multitask_cue()
+        ->multitask_cue_controller()
         ->nudge_controller_for_testing();
   }
 
@@ -109,8 +110,8 @@
     const auto window_screen_bounds = window->GetBoundsInScreen();
     const int tablet_nudge_y_offset =
         MultitaskMenuNudgeDelegateAsh::kTabletNudgeAdditionalYOffset +
-        TabletModeMultitaskCue::kCueHeight +
-        TabletModeMultitaskCue::kCueYOffset;
+        TabletModeMultitaskCueController::kCueHeight +
+        TabletModeMultitaskCueController::kCueYOffset;
     const gfx::Rect expected_bounds(
         (window_screen_bounds.width() - size.width()) / 2 +
             window_screen_bounds.x(),
@@ -180,7 +181,8 @@
 
   // After floating the window from the multitask menu, there is no crash.
   LeftClickOn(
-      multitask_menu->multitask_menu_view()->float_button_for_testing());
+      chromeos::MultitaskMenuViewTestApi(multitask_menu->multitask_menu_view())
+          .GetFloatButton());
   EXPECT_TRUE(WindowState::Get(window.get())->IsFloated());
 }
 
diff --git a/ash/wm/multitask_menu_nudge_delegate_ash.cc b/ash/wm/multitask_menu_nudge_delegate_ash.cc
index de18787..5b923aa 100644
--- a/ash/wm/multitask_menu_nudge_delegate_ash.cc
+++ b/ash/wm/multitask_menu_nudge_delegate_ash.cc
@@ -7,7 +7,7 @@
 #include "ash/constants/ash_pref_names.h"
 #include "ash/session/session_controller_impl.h"
 #include "ash/shell.h"
-#include "ash/wm/tablet_mode/tablet_mode_multitask_cue.h"
+#include "ash/wm/tablet_mode/tablet_mode_multitask_cue_controller.h"
 #include "components/prefs/pref_service.h"
 
 namespace ash {
@@ -35,8 +35,9 @@
 MultitaskMenuNudgeDelegateAsh::~MultitaskMenuNudgeDelegateAsh() = default;
 
 int MultitaskMenuNudgeDelegateAsh::GetTabletNudgeYOffset() const {
-  return kTabletNudgeAdditionalYOffset + TabletModeMultitaskCue::kCueHeight +
-         TabletModeMultitaskCue::kCueYOffset;
+  return kTabletNudgeAdditionalYOffset +
+         TabletModeMultitaskCueController::kCueHeight +
+         TabletModeMultitaskCueController::kCueYOffset;
 }
 
 void MultitaskMenuNudgeDelegateAsh::GetNudgePreferences(
diff --git a/ash/wm/tablet_mode/tablet_mode_multitask_cue.cc b/ash/wm/tablet_mode/tablet_mode_multitask_cue_controller.cc
similarity index 83%
rename from ash/wm/tablet_mode/tablet_mode_multitask_cue.cc
rename to ash/wm/tablet_mode/tablet_mode_multitask_cue_controller.cc
index d996e02..0bcc074b 100644
--- a/ash/wm/tablet_mode/tablet_mode_multitask_cue.cc
+++ b/ash/wm/tablet_mode/tablet_mode_multitask_cue_controller.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 "ash/wm/tablet_mode/tablet_mode_multitask_cue.h"
+#include "ash/wm/tablet_mode/tablet_mode_multitask_cue_controller.h"
 
 #include "ash/constants/app_types.h"
 #include "ash/shell.h"
@@ -33,7 +33,7 @@
 
 }  // namespace
 
-TabletModeMultitaskCue::TabletModeMultitaskCue() {
+TabletModeMultitaskCueController::TabletModeMultitaskCueController() {
   DCHECK(chromeos::wm::features::IsWindowLayoutMenuEnabled());
   DCHECK(Shell::Get()->IsInTabletMode());
   Shell::Get()->activation_client()->AddObserver(this);
@@ -44,12 +44,13 @@
   }
 }
 
-TabletModeMultitaskCue::~TabletModeMultitaskCue() {
+TabletModeMultitaskCueController::~TabletModeMultitaskCueController() {
   DismissCue();
   Shell::Get()->activation_client()->RemoveObserver(this);
 }
 
-void TabletModeMultitaskCue::MaybeShowCue(aura::Window* active_window) {
+void TabletModeMultitaskCueController::MaybeShowCue(
+    aura::Window* active_window) {
   DCHECK(active_window);
 
   if (!CanShowCue(active_window)) {
@@ -94,13 +95,13 @@
       .SetOpacity(cue_layer_.get(), 1.0f, gfx::Tween::LINEAR);
 
   cue_dismiss_timer_.Start(FROM_HERE, kCueDismissTimeout, this,
-                           &TabletModeMultitaskCue::OnTimerFinished);
+                           &TabletModeMultitaskCueController::OnTimerFinished);
 
   // Show the education nudge a maximum of three times with 24h in between.
   nudge_controller_.MaybeShowNudge(window_);
 }
 
-bool TabletModeMultitaskCue::CanShowCue(aura::Window* window) const {
+bool TabletModeMultitaskCueController::CanShowCue(aura::Window* window) const {
   // Only show or dismiss the cue when activating app windows.
   if (static_cast<AppType>(window->GetProperty(aura::client::kAppType)) ==
       AppType::NON_APP) {
@@ -115,7 +116,7 @@
   return true;
 }
 
-void TabletModeMultitaskCue::DismissCue() {
+void TabletModeMultitaskCueController::DismissCue() {
   cue_dismiss_timer_.Stop();
   window_observation_.Reset();
 
@@ -128,7 +129,7 @@
   nudge_controller_.DismissNudge();
 }
 
-void TabletModeMultitaskCue::ResetPosition() {
+void TabletModeMultitaskCueController::ResetPosition() {
   if (!cue_layer_) {
     return;
   }
@@ -140,18 +141,20 @@
                     gfx::Tween::ACCEL_20_DECEL_100);
 }
 
-void TabletModeMultitaskCue::OnMenuOpened(aura::Window* active_window) {
+void TabletModeMultitaskCueController::OnMenuOpened(
+    aura::Window* active_window) {
   if (cue_layer_ && window_ != active_window) {
     MaybeShowCue(active_window);
   }
   nudge_controller_.OnMenuOpened(/*tablet_mode=*/true);
 }
 
-void TabletModeMultitaskCue::OnWindowDestroying(aura::Window* window) {
+void TabletModeMultitaskCueController::OnWindowDestroying(
+    aura::Window* window) {
   DismissCue();
 }
 
-void TabletModeMultitaskCue::OnWindowBoundsChanged(
+void TabletModeMultitaskCueController::OnWindowBoundsChanged(
     aura::Window* window,
     const gfx::Rect& old_bounds,
     const gfx::Rect& new_bounds,
@@ -159,9 +162,10 @@
   UpdateCueBounds();
 }
 
-void TabletModeMultitaskCue::OnWindowActivated(ActivationReason reason,
-                                               aura::Window* gained_active,
-                                               aura::Window* lost_active) {
+void TabletModeMultitaskCueController::OnWindowActivated(
+    ActivationReason reason,
+    aura::Window* gained_active,
+    aura::Window* lost_active) {
   if (!gained_active) {
     return;
   }
@@ -201,7 +205,7 @@
   MaybeShowCue(gained_active);
 }
 
-void TabletModeMultitaskCue::OnPostWindowStateTypeChange(
+void TabletModeMultitaskCueController::OnPostWindowStateTypeChange(
     WindowState* window_state,
     chromeos::WindowStateType old_type) {
   if (!TabletModeMultitaskMenuController::CanShowMenu(window_state->window())) {
@@ -209,7 +213,7 @@
   }
 }
 
-void TabletModeMultitaskCue::UpdateCueBounds() {
+void TabletModeMultitaskCueController::UpdateCueBounds() {
   // Needed for some edge cases where the cue is dismissed while it is being
   // updated.
   if (!window_) {
@@ -220,7 +224,7 @@
                                   kCueYOffset, kCueWidth, kCueHeight));
 }
 
-void TabletModeMultitaskCue::OnTimerFinished() {
+void TabletModeMultitaskCueController::OnTimerFinished() {
   // If no cue or the animation is already fading out, return.
   if (!cue_layer_ || cue_layer_->GetAnimator()->GetTargetOpacity() == 0.0f) {
     return;
@@ -230,7 +234,7 @@
   views::AnimationBuilder()
       .SetPreemptionStrategy(
           ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET)
-      .OnEnded(base::BindOnce(&TabletModeMultitaskCue::DismissCue,
+      .OnEnded(base::BindOnce(&TabletModeMultitaskCueController::DismissCue,
                               base::Unretained(this)))
       .Once()
       .SetDuration(kFadeDuration)
diff --git a/ash/wm/tablet_mode/tablet_mode_multitask_cue.h b/ash/wm/tablet_mode/tablet_mode_multitask_cue_controller.h
similarity index 84%
rename from ash/wm/tablet_mode/tablet_mode_multitask_cue.h
rename to ash/wm/tablet_mode/tablet_mode_multitask_cue_controller.h
index fed96eb..73df149 100644
--- a/ash/wm/tablet_mode/tablet_mode_multitask_cue.h
+++ b/ash/wm/tablet_mode/tablet_mode_multitask_cue_controller.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 ASH_WM_TABLET_MODE_TABLET_MODE_MULTITASK_CUE_H_
-#define ASH_WM_TABLET_MODE_TABLET_MODE_MULTITASK_CUE_H_
+#ifndef ASH_WM_TABLET_MODE_TABLET_MODE_MULTITASK_CUE_CONTROLLER_H_
+#define ASH_WM_TABLET_MODE_TABLET_MODE_MULTITASK_CUE_CONTROLLER_H_
 
 #include <memory>
 
@@ -22,23 +22,24 @@
 
 // Creates a cue (draggable bar) at the top center of an app window when it is
 // activated in tablet mode. Only one cue exists at a time.
-// TODO(b/279220838): Rename to TabletModeMultitaskCueController for better
-// clarity.
-class ASH_EXPORT TabletModeMultitaskCue : aura::WindowObserver,
-                                          wm::ActivationChangeObserver,
-                                          WindowStateObserver {
+class ASH_EXPORT TabletModeMultitaskCueController
+    : aura::WindowObserver,
+      wm::ActivationChangeObserver,
+      WindowStateObserver {
  public:
   // Cue layout values.
   static constexpr int kCueYOffset = 6;
   static constexpr int kCueWidth = 48;
   static constexpr int kCueHeight = 4;
 
-  TabletModeMultitaskCue();
+  TabletModeMultitaskCueController();
 
-  TabletModeMultitaskCue(const TabletModeMultitaskCue&) = delete;
-  TabletModeMultitaskCue& operator=(const TabletModeMultitaskCue&) = delete;
+  TabletModeMultitaskCueController(const TabletModeMultitaskCueController&) =
+      delete;
+  TabletModeMultitaskCueController& operator=(
+      const TabletModeMultitaskCueController&) = delete;
 
-  ~TabletModeMultitaskCue() override;
+  ~TabletModeMultitaskCueController() override;
 
   ui::Layer* cue_layer() { return cue_layer_.get(); }
 
@@ -116,4 +117,4 @@
 
 }  // namespace ash
 
-#endif  // ASH_WM_TABLET_MODE_TABLET_MODE_MULTITASK_CUE_H_
+#endif  // ASH_WM_TABLET_MODE_TABLET_MODE_MULTITASK_CUE_CONTROLLER_H_
diff --git a/ash/wm/tablet_mode/tablet_mode_multitask_cue_controller_unittest.cc b/ash/wm/tablet_mode/tablet_mode_multitask_cue_controller_unittest.cc
new file mode 100644
index 0000000..f13d825
--- /dev/null
+++ b/ash/wm/tablet_mode/tablet_mode_multitask_cue_controller_unittest.cc
@@ -0,0 +1,181 @@
+// Copyright 2022 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "ash/wm/tablet_mode/tablet_mode_multitask_cue_controller.h"
+
+#include "ash/shell.h"
+#include "ash/test/ash_test_base.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/test/scoped_feature_list.h"
+#include "chromeos/ui/wm/features.h"
+#include "ui/compositor/scoped_animation_duration_scale_mode.h"
+#include "ui/compositor/test/layer_animation_stopped_waiter.h"
+#include "ui/wm/core/window_util.h"
+
+namespace ash {
+
+class TabletModeMultitaskCueControllerTest : public AshTestBase {
+ public:
+  TabletModeMultitaskCueControllerTest()
+      : scoped_feature_list_(chromeos::wm::features::kWindowLayoutMenu) {}
+  TabletModeMultitaskCueControllerTest(
+      const TabletModeMultitaskCueControllerTest&) = delete;
+  TabletModeMultitaskCueControllerTest& operator=(
+      const TabletModeMultitaskCueControllerTest&) = delete;
+  ~TabletModeMultitaskCueControllerTest() override = default;
+
+  TabletModeMultitaskCueController* GetMultitaskCue() {
+    return TabletModeControllerTestApi()
+        .tablet_mode_window_manager()
+        ->tablet_mode_multitask_menu_controller()
+        ->multitask_cue_controller();
+  }
+
+  // AshTestBase:
+  void SetUp() override {
+    AshTestBase::SetUp();
+    TabletModeControllerTestApi().EnterTabletMode();
+  }
+
+ private:
+  base::test::ScopedFeatureList scoped_feature_list_;
+};
+
+// Tests that the cue layer is created properly.
+TEST_F(TabletModeMultitaskCueControllerTest, BasicShowCue) {
+  auto window = CreateAppWindow();
+  gfx::Rect window_bounds = window->bounds();
+
+  auto* multitask_cue_controller = GetMultitaskCue();
+  ASSERT_TRUE(multitask_cue_controller);
+
+  ui::Layer* cue_layer = multitask_cue_controller->cue_layer();
+  ASSERT_TRUE(cue_layer);
+
+  EXPECT_EQ(gfx::Rect((window_bounds.width() -
+                       TabletModeMultitaskCueController::kCueWidth) /
+                          2,
+                      TabletModeMultitaskCueController::kCueYOffset,
+                      TabletModeMultitaskCueController::kCueWidth,
+                      TabletModeMultitaskCueController::kCueHeight),
+            cue_layer->bounds());
+}
+
+// Tests that the cue bounds are updated properly after a window is split.
+TEST_F(TabletModeMultitaskCueControllerTest, SplitCueBounds) {
+  auto* split_view_controller =
+      SplitViewController::Get(Shell::GetPrimaryRootWindow());
+
+  auto window1 = CreateAppWindow();
+
+  split_view_controller->SnapWindow(
+      window1.get(), SplitViewController::SnapPosition::kPrimary);
+
+  gfx::Rect split_bounds((window1->bounds().width() -
+                          TabletModeMultitaskCueController::kCueWidth) /
+                             2,
+                         TabletModeMultitaskCueController::kCueYOffset,
+                         TabletModeMultitaskCueController::kCueWidth,
+                         TabletModeMultitaskCueController::kCueHeight);
+
+  ui::Layer* cue_layer = GetMultitaskCue()->cue_layer();
+  ASSERT_TRUE(cue_layer);
+  EXPECT_EQ(cue_layer->bounds(), split_bounds);
+
+  auto window2 = CreateAppWindow();
+  split_view_controller->SnapWindow(
+      window2.get(), SplitViewController::SnapPosition::kSecondary);
+
+  cue_layer = GetMultitaskCue()->cue_layer();
+  ASSERT_TRUE(cue_layer);
+  EXPECT_EQ(cue_layer->bounds(), split_bounds);
+}
+
+// Tests that the `OneShotTimer` properly dismisses the cue after firing.
+TEST_F(TabletModeMultitaskCueControllerTest, DismissTimerFiring) {
+  ui::ScopedAnimationDurationScaleMode non_zero_duration_mode(
+      ui::ScopedAnimationDurationScaleMode::NON_ZERO_DURATION);
+
+  auto window = CreateAppWindow();
+
+  auto* multitask_cue_controller = GetMultitaskCue();
+  ui::Layer* cue_layer = multitask_cue_controller->cue_layer();
+  ASSERT_TRUE(cue_layer);
+
+  // Wait for fade in to finish.
+  ui::LayerAnimationStoppedWaiter animation_waiter;
+  animation_waiter.Wait(cue_layer);
+
+  multitask_cue_controller->FireCueDismissTimerForTesting();
+
+  // Wait for fade out to finish.
+  animation_waiter.Wait(cue_layer);
+  EXPECT_FALSE(multitask_cue_controller->cue_layer());
+}
+
+// Tests that the cue dismisses properly during the fade out animation.
+TEST_F(TabletModeMultitaskCueControllerTest, DismissEarly) {
+  ui::ScopedAnimationDurationScaleMode non_zero_duration_mode(
+      ui::ScopedAnimationDurationScaleMode::NON_ZERO_DURATION);
+
+  auto window = CreateAppWindow();
+
+  auto* multitask_cue_controller = GetMultitaskCue();
+  ui::Layer* cue_layer = multitask_cue_controller->cue_layer();
+  ASSERT_TRUE(cue_layer);
+
+  // Wait for fade in to finish.
+  ui::LayerAnimationStoppedWaiter().Wait(cue_layer);
+
+  multitask_cue_controller->FireCueDismissTimerForTesting();
+  multitask_cue_controller->DismissCue();
+  EXPECT_FALSE(multitask_cue_controller->cue_layer());
+}
+
+// Tests that the cue dismisses properly when the float keyboard accelerator is
+// pressed.
+TEST_F(TabletModeMultitaskCueControllerTest, FloatWindow) {
+  auto window = CreateAppWindow();
+
+  PressAndReleaseKey(ui::VKEY_F, ui::EF_ALT_DOWN | ui::EF_COMMAND_DOWN);
+
+  auto* multitask_cue_controller = GetMultitaskCue();
+  ASSERT_TRUE(multitask_cue_controller);
+  EXPECT_FALSE(multitask_cue_controller->cue_layer());
+}
+
+TEST_F(TabletModeMultitaskCueControllerTest, TransientChildFocus) {
+  auto window1 = CreateAppWindow();
+
+  // Create a second window with a transient child.
+  auto window2 = CreateAppWindow();
+  auto transient_child2 =
+      CreateTestWindow(gfx::Rect(100, 10), aura::client::WINDOW_TYPE_POPUP);
+  wm::AddTransientChild(window2.get(), transient_child2.get());
+  wm::ActivateWindow(transient_child2.get());
+
+  // Creating an app window shows the cue. Hide it before testing.
+  auto* multitask_cue_controller = GetMultitaskCue();
+  ASSERT_TRUE(multitask_cue_controller->cue_layer());
+  multitask_cue_controller->DismissCue();
+
+  // Activate `window2`. The cue should not show up, since the window with
+  // previous activation was a transient child.
+  wm::ActivateWindow(window2.get());
+  EXPECT_FALSE(multitask_cue_controller->cue_layer());
+
+  // Reactivate the transient. The cue should not show up, since the transient
+  // window is a popup, and cannot change window states.
+  wm::ActivateWindow(transient_child2.get());
+  EXPECT_FALSE(multitask_cue_controller->cue_layer());
+
+  // Activate `window1`. The cue should show up, since the previous activated
+  // window was not associated with it.
+  wm::ActivateWindow(window1.get());
+  EXPECT_TRUE(multitask_cue_controller->cue_layer());
+}
+
+}  // namespace ash
diff --git a/ash/wm/tablet_mode/tablet_mode_multitask_cue_unittest.cc b/ash/wm/tablet_mode/tablet_mode_multitask_cue_unittest.cc
deleted file mode 100644
index 25017f1..0000000
--- a/ash/wm/tablet_mode/tablet_mode_multitask_cue_unittest.cc
+++ /dev/null
@@ -1,177 +0,0 @@
-// Copyright 2022 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "ash/wm/tablet_mode/tablet_mode_multitask_cue.h"
-
-#include "ash/shell.h"
-#include "ash/test/ash_test_base.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/test/scoped_feature_list.h"
-#include "chromeos/ui/wm/features.h"
-#include "ui/compositor/scoped_animation_duration_scale_mode.h"
-#include "ui/compositor/test/layer_animation_stopped_waiter.h"
-#include "ui/wm/core/window_util.h"
-
-namespace ash {
-
-class TabletModeMultitaskCueTest : public AshTestBase {
- public:
-  TabletModeMultitaskCueTest()
-      : scoped_feature_list_(chromeos::wm::features::kWindowLayoutMenu) {}
-  TabletModeMultitaskCueTest(const TabletModeMultitaskCueTest&) = delete;
-  TabletModeMultitaskCueTest& operator=(const TabletModeMultitaskCueTest&) =
-      delete;
-  ~TabletModeMultitaskCueTest() override = default;
-
-  TabletModeMultitaskCue* GetMultitaskCue() {
-    return TabletModeControllerTestApi()
-        .tablet_mode_window_manager()
-        ->tablet_mode_multitask_menu_controller()
-        ->multitask_cue();
-  }
-
-  // AshTestBase:
-  void SetUp() override {
-    AshTestBase::SetUp();
-    TabletModeControllerTestApi().EnterTabletMode();
-  }
-
- private:
-  base::test::ScopedFeatureList scoped_feature_list_;
-};
-
-// Tests that the cue layer is created properly.
-TEST_F(TabletModeMultitaskCueTest, BasicShowCue) {
-  auto window = CreateAppWindow();
-  gfx::Rect window_bounds = window->bounds();
-
-  auto* multitask_cue = GetMultitaskCue();
-  ASSERT_TRUE(multitask_cue);
-
-  ui::Layer* cue_layer = multitask_cue->cue_layer();
-  ASSERT_TRUE(cue_layer);
-
-  EXPECT_EQ(
-      gfx::Rect((window_bounds.width() - TabletModeMultitaskCue::kCueWidth) / 2,
-                TabletModeMultitaskCue::kCueYOffset,
-                TabletModeMultitaskCue::kCueWidth,
-                TabletModeMultitaskCue::kCueHeight),
-      cue_layer->bounds());
-}
-
-// Tests that the cue bounds are updated properly after a window is split.
-TEST_F(TabletModeMultitaskCueTest, SplitCueBounds) {
-  auto* split_view_controller =
-      SplitViewController::Get(Shell::GetPrimaryRootWindow());
-
-  auto window1 = CreateAppWindow();
-
-  split_view_controller->SnapWindow(
-      window1.get(), SplitViewController::SnapPosition::kPrimary);
-
-  gfx::Rect split_bounds(
-      (window1->bounds().width() - TabletModeMultitaskCue::kCueWidth) / 2,
-      TabletModeMultitaskCue::kCueYOffset, TabletModeMultitaskCue::kCueWidth,
-      TabletModeMultitaskCue::kCueHeight);
-
-  ui::Layer* cue_layer = GetMultitaskCue()->cue_layer();
-  ASSERT_TRUE(cue_layer);
-  EXPECT_EQ(cue_layer->bounds(), split_bounds);
-
-  auto window2 = CreateAppWindow();
-  split_view_controller->SnapWindow(
-      window2.get(), SplitViewController::SnapPosition::kSecondary);
-
-  cue_layer = GetMultitaskCue()->cue_layer();
-  ASSERT_TRUE(cue_layer);
-  EXPECT_EQ(cue_layer->bounds(), split_bounds);
-}
-
-// Tests that the `OneShotTimer` properly dismisses the cue after firing.
-TEST_F(TabletModeMultitaskCueTest, DismissTimerFiring) {
-  ui::ScopedAnimationDurationScaleMode non_zero_duration_mode(
-      ui::ScopedAnimationDurationScaleMode::NON_ZERO_DURATION);
-
-  auto window = CreateAppWindow();
-
-  auto* multitask_cue = GetMultitaskCue();
-  ui::Layer* cue_layer = multitask_cue->cue_layer();
-  ASSERT_TRUE(cue_layer);
-
-  // Wait for fade in to finish.
-  ui::LayerAnimationStoppedWaiter animation_waiter;
-  animation_waiter.Wait(cue_layer);
-
-  multitask_cue->FireCueDismissTimerForTesting();
-
-  // Wait for fade out to finish.
-  animation_waiter.Wait(cue_layer);
-  EXPECT_FALSE(multitask_cue->cue_layer());
-}
-
-// Tests that the cue dismisses properly during the fade out animation.
-TEST_F(TabletModeMultitaskCueTest, DismissEarly) {
-  ui::ScopedAnimationDurationScaleMode non_zero_duration_mode(
-      ui::ScopedAnimationDurationScaleMode::NON_ZERO_DURATION);
-
-  auto window = CreateAppWindow();
-
-  auto* multitask_cue = GetMultitaskCue();
-  ui::Layer* cue_layer = multitask_cue->cue_layer();
-  ASSERT_TRUE(cue_layer);
-
-  // Wait for fade in to finish.
-  ui::LayerAnimationStoppedWaiter().Wait(cue_layer);
-
-  multitask_cue->FireCueDismissTimerForTesting();
-  multitask_cue->DismissCue();
-  EXPECT_FALSE(multitask_cue->cue_layer());
-}
-
-// Tests that the cue dismisses properly when the float keyboard accelerator is
-// pressed.
-TEST_F(TabletModeMultitaskCueTest, FloatWindow) {
-  auto window = CreateAppWindow();
-
-  PressAndReleaseKey(ui::VKEY_F, ui::EF_ALT_DOWN | ui::EF_COMMAND_DOWN);
-
-  auto* multitask_cue = GetMultitaskCue();
-  ASSERT_TRUE(multitask_cue);
-  EXPECT_FALSE(multitask_cue->cue_layer());
-}
-
-TEST_F(TabletModeMultitaskCueTest, TransientChildFocus) {
-  auto window1 = CreateAppWindow();
-
-  // Create a second window with a transient child.
-  auto window2 = CreateAppWindow();
-  auto transient_child2 =
-      CreateTestWindow(gfx::Rect(100, 10), aura::client::WINDOW_TYPE_POPUP);
-  wm::AddTransientChild(window2.get(), transient_child2.get());
-  wm::ActivateWindow(transient_child2.get());
-
-  // Creating an app window shows the cue. Hide it before testing.
-  auto* multitask_cue = GetMultitaskCue();
-  ASSERT_TRUE(multitask_cue->cue_layer());
-  multitask_cue->DismissCue();
-
-  // Activate `window2`. The cue should not show up, since the window with
-  // previous activation was a transient child.
-  wm::ActivateWindow(window2.get());
-  EXPECT_FALSE(multitask_cue->cue_layer());
-
-  // Reactivate the transient. The cue should not show up, since the transient
-  // window is a popup, and cannot change window states.
-  wm::ActivateWindow(transient_child2.get());
-  EXPECT_FALSE(multitask_cue->cue_layer());
-
-  // Activate `window1`. The cue should show up, since the previous activated
-  // window was not associated with it.
-  wm::ActivateWindow(window1.get());
-  EXPECT_TRUE(multitask_cue->cue_layer());
-}
-
-}  // namespace ash
diff --git a/ash/wm/tablet_mode/tablet_mode_multitask_menu.cc b/ash/wm/tablet_mode/tablet_mode_multitask_menu.cc
index 90be675d..25d1ea4f 100644
--- a/ash/wm/tablet_mode/tablet_mode_multitask_menu.cc
+++ b/ash/wm/tablet_mode/tablet_mode_multitask_menu.cc
@@ -13,7 +13,7 @@
 #include "ash/style/ash_color_id.h"
 #include "ash/style/system_shadow.h"
 #include "ash/wm/splitview/split_view_controller.h"
-#include "ash/wm/tablet_mode/tablet_mode_multitask_cue.h"
+#include "ash/wm/tablet_mode/tablet_mode_multitask_cue_controller.h"
 #include "ash/wm/tablet_mode/tablet_mode_multitask_menu_controller.h"
 #include "ash/wm/window_state.h"
 #include "chromeos/constants/chromeos_features.h"
@@ -247,7 +247,7 @@
                                0, -menu_view_->GetPreferredSize().height() -
                                       kVerticalPosition),
                     gfx::Tween::ACCEL_20_DECEL_100);
-  ui::Layer* cue_layer = controller_->multitask_cue()->cue_layer();
+  ui::Layer* cue_layer = controller_->multitask_cue_controller()->cue_layer();
   if (cue_layer) {
     animation_builder.GetCurrentSequence().SetTransform(
         cue_layer,
@@ -287,7 +287,8 @@
     initial_y_ = menu_view_->bounds().bottom();
     menu_view_->layer()->SetTransform(
         gfx::Transform::MakeTranslation(0, translation_y));
-    if (ui::Layer* cue_layer = controller_->multitask_cue()->cue_layer()) {
+    if (ui::Layer* cue_layer =
+            controller_->multitask_cue_controller()->cue_layer()) {
       cue_layer->SetTransform(gfx::Transform::MakeTranslation(0, initial_y));
     }
   } else {
@@ -306,7 +307,7 @@
   menu_view_->layer()->SetTransform(
       gfx::Transform::MakeTranslation(0, translation_y));
 
-  if (auto* cue_layer = controller_->multitask_cue()->cue_layer()) {
+  if (auto* cue_layer = controller_->multitask_cue_controller()->cue_layer()) {
     cue_layer->SetTransform(gfx::Transform::MakeTranslation(
         0, menu_view_->GetPreferredSize().height() + kVerticalPosition +
                translation_y));
diff --git a/ash/wm/tablet_mode/tablet_mode_multitask_menu_controller.cc b/ash/wm/tablet_mode/tablet_mode_multitask_menu_controller.cc
index 07fc88f..12f612c 100644
--- a/ash/wm/tablet_mode/tablet_mode_multitask_menu_controller.cc
+++ b/ash/wm/tablet_mode/tablet_mode_multitask_menu_controller.cc
@@ -7,7 +7,7 @@
 #include "ash/accelerators/debug_commands.h"
 #include "ash/shell.h"
 #include "ash/wm/tablet_mode/tablet_mode_controller.h"
-#include "ash/wm/tablet_mode/tablet_mode_multitask_cue.h"
+#include "ash/wm/tablet_mode/tablet_mode_multitask_cue_controller.h"
 #include "ash/wm/tablet_mode/tablet_mode_multitask_menu.h"
 #include "ash/wm/tablet_mode/tablet_mode_window_manager.h"
 #include "ash/wm/window_state.h"
@@ -49,14 +49,15 @@
 }  // namespace
 
 TabletModeMultitaskMenuController::TabletModeMultitaskMenuController()
-    : multitask_cue_(std::make_unique<TabletModeMultitaskCue>()) {
+    : multitask_cue_controller_(
+          std::make_unique<TabletModeMultitaskCueController>()) {
   Shell::Get()->AddPreTargetHandler(this);
 }
 
 TabletModeMultitaskMenuController::~TabletModeMultitaskMenuController() {
   // The cue needs to be destroyed first so that it doesn't do any work when
   // window activation changes as a result of destroying `this`.
-  multitask_cue_.reset();
+  multitask_cue_controller_.reset();
 
   Shell::Get()->RemovePreTargetHandler(this);
 }
@@ -76,7 +77,7 @@
 }
 
 void TabletModeMultitaskMenuController::ResetMultitaskMenu() {
-  multitask_cue_->ResetPosition();
+  multitask_cue_controller_->ResetPosition();
   multitask_menu_.reset();
 }
 
@@ -107,7 +108,7 @@
         // We may need to recreate `multitask_menu_` on the new target window.
         multitask_menu_ =
             std::make_unique<TabletModeMultitaskMenu>(this, window);
-        multitask_cue_->OnMenuOpened(window);
+        multitask_cue_controller_->OnMenuOpened(window);
         multitask_menu_->BeginDrag(window_location.y(), /*down=*/true);
         event->SetHandled();
         is_drag_active_ = true;
@@ -165,7 +166,7 @@
     aura::Window* window) {
   if (!multitask_menu_) {
     multitask_menu_ = std::make_unique<TabletModeMultitaskMenu>(this, window);
-    multitask_cue_->OnMenuOpened(window);
+    multitask_cue_controller_->OnMenuOpened(window);
   }
 }
 
diff --git a/ash/wm/tablet_mode/tablet_mode_multitask_menu_controller.h b/ash/wm/tablet_mode/tablet_mode_multitask_menu_controller.h
index 8b26f3e..a3b3ac3 100644
--- a/ash/wm/tablet_mode/tablet_mode_multitask_menu_controller.h
+++ b/ash/wm/tablet_mode/tablet_mode_multitask_menu_controller.h
@@ -12,8 +12,8 @@
 
 namespace ash {
 
+class TabletModeMultitaskCueController;
 class TabletModeMultitaskMenu;
-class TabletModeMultitaskCue;
 
 // TabletModeMultitaskMenuController handles gestures in tablet mode that may
 // show or hide the multitask menu.
@@ -29,7 +29,9 @@
   static bool CanShowMenu(aura::Window* window);
 
   TabletModeMultitaskMenu* multitask_menu() { return multitask_menu_.get(); }
-  TabletModeMultitaskCue* multitask_cue() { return multitask_cue_.get(); }
+  TabletModeMultitaskCueController* multitask_cue_controller() {
+    return multitask_cue_controller_.get();
+  }
 
   // Creates and shows the menu.
   void ShowMultitaskMenu(aura::Window* window);
@@ -49,7 +51,7 @@
   bool is_drag_active_ = false;
 
   // Creates a draggable bar when app windows are activated.
-  std::unique_ptr<TabletModeMultitaskCue> multitask_cue_;
+  std::unique_ptr<TabletModeMultitaskCueController> multitask_cue_controller_;
 
   std::unique_ptr<TabletModeMultitaskMenu> multitask_menu_;
 };
diff --git a/ash/wm/tablet_mode/tablet_mode_multitask_menu_unittest.cc b/ash/wm/tablet_mode/tablet_mode_multitask_menu_unittest.cc
index 5eeaa3e..8ef2311 100644
--- a/ash/wm/tablet_mode/tablet_mode_multitask_menu_unittest.cc
+++ b/ash/wm/tablet_mode/tablet_mode_multitask_menu_unittest.cc
@@ -12,7 +12,7 @@
 #include "ash/wm/splitview/split_view_controller.h"
 #include "ash/wm/splitview/split_view_divider.h"
 #include "ash/wm/tablet_mode/tablet_mode_controller_test_api.h"
-#include "ash/wm/tablet_mode/tablet_mode_multitask_cue.h"
+#include "ash/wm/tablet_mode/tablet_mode_multitask_cue_controller.h"
 #include "ash/wm/tablet_mode/tablet_mode_multitask_menu.h"
 #include "ash/wm/tablet_mode/tablet_mode_multitask_menu_controller.h"
 #include "ash/wm/tablet_mode/tablet_mode_window_manager.h"
@@ -24,6 +24,7 @@
 #include "chromeos/ui/frame/multitask_menu/multitask_button.h"
 #include "chromeos/ui/frame/multitask_menu/multitask_menu_metrics.h"
 #include "chromeos/ui/frame/multitask_menu/multitask_menu_view.h"
+#include "chromeos/ui/frame/multitask_menu/multitask_menu_view_test_api.h"
 #include "chromeos/ui/frame/multitask_menu/split_button_view.h"
 #include "chromeos/ui/wm/features.h"
 #include "ui/aura/test/test_window_delegate.h"
@@ -90,7 +91,9 @@
     ShowMultitaskMenu(*window);
     auto* multitask_menu_view = GetMultitaskMenuView(GetMultitaskMenu());
     const gfx::Rect half_bounds =
-        multitask_menu_view->half_button_for_testing()->GetBoundsInScreen();
+        chromeos::MultitaskMenuViewTestApi(multitask_menu_view)
+            .GetHalfButton()
+            ->GetBoundsInScreen();
     GetEventGenerator()->GestureTapAt(left ? half_bounds.left_center()
                                            : half_bounds.right_center());
     auto* split_view_controller = SplitViewController::Get(window);
@@ -159,10 +162,11 @@
   chromeos::MultitaskMenuView* multitask_menu_view =
       GetMultitaskMenuView(multitask_menu);
   ASSERT_TRUE(multitask_menu_view);
-  EXPECT_TRUE(multitask_menu_view->half_button_for_testing());
+  chromeos::MultitaskMenuViewTestApi test_api(multitask_menu_view);
+  EXPECT_TRUE(test_api.GetHalfButton());
   EXPECT_TRUE(multitask_menu_view->partial_button());
-  EXPECT_TRUE(multitask_menu_view->full_button_for_testing());
-  EXPECT_TRUE(multitask_menu_view->float_button_for_testing());
+  EXPECT_TRUE(test_api.GetFullButton());
+  EXPECT_TRUE(test_api.GetFloatButton());
 
   // Verify that the menu is horizontally centered.
   EXPECT_EQ(multitask_menu->widget()
@@ -214,8 +218,9 @@
   ShowMultitaskMenu(*window);
 
   // Press and move the touch slightly to mimic a real tap.
-  auto* half_button =
-      GetMultitaskMenuView(GetMultitaskMenu())->half_button_for_testing();
+  auto* half_button = chromeos::MultitaskMenuViewTestApi(
+                          GetMultitaskMenuView(GetMultitaskMenu()))
+                          .GetHalfButton();
   GetEventGenerator()->set_current_screen_location(
       half_button->GetBoundsInScreen().left_center());
   GetEventGenerator()->PressMoveAndReleaseTouchBy(0, 3);
@@ -383,8 +388,9 @@
   auto window = CreateAppWindow();
   GenerateScroll(window->bounds().CenterPoint().x(), 0,
                  /*end_y=*/60);
-  GestureTapOn(
-      GetMultitaskMenuView(GetMultitaskMenu())->float_button_for_testing());
+  GestureTapOn(chromeos::MultitaskMenuViewTestApi(
+                   GetMultitaskMenuView(GetMultitaskMenu()))
+                   .GetFloatButton());
 
   // Wait for the TabletModeMultitaskMenuView layer to fade out.
   ui::LayerAnimationStoppedWaiter().Wait(
@@ -445,8 +451,9 @@
   ShowMultitaskMenu(*window);
 
   // Press the primary half split button.
-  auto* half_button =
-      GetMultitaskMenuView(GetMultitaskMenu())->half_button_for_testing();
+  auto* half_button = chromeos::MultitaskMenuViewTestApi(
+                          GetMultitaskMenuView(GetMultitaskMenu()))
+                          .GetHalfButton();
   GetEventGenerator()->GestureTapAt(
       half_button->GetBoundsInScreen().left_center());
   histogram_tester_.ExpectBucketCount(
@@ -556,7 +563,8 @@
   ASSERT_TRUE(GetMultitaskMenu());
   chromeos::MultitaskMenuView* multitask_menu_view =
       GetMultitaskMenuView(GetMultitaskMenu());
-  ASSERT_TRUE(multitask_menu_view->half_button_for_testing());
+  ASSERT_TRUE(
+      chromeos::MultitaskMenuViewTestApi(multitask_menu_view).GetHalfButton());
   ASSERT_TRUE(multitask_menu_view->partial_button()->GetEnabled());
   ASSERT_FALSE(multitask_menu_view->partial_button()
                    ->GetRightBottomButton()
@@ -569,7 +577,8 @@
       gfx::Size(work_area_bounds.width() * 0.6f, work_area_bounds.height()));
   ShowMultitaskMenu(*window);
   multitask_menu_view = GetMultitaskMenuView(GetMultitaskMenu());
-  EXPECT_FALSE(multitask_menu_view->half_button_for_testing());
+  ASSERT_FALSE(
+      chromeos::MultitaskMenuViewTestApi(multitask_menu_view).GetHalfButton());
   EXPECT_TRUE(multitask_menu_view->partial_button()->GetEnabled());
   ASSERT_FALSE(multitask_menu_view->partial_button()
                    ->GetRightBottomButton()
@@ -582,7 +591,8 @@
       gfx::Size(work_area_bounds.width() * 0.7f, work_area_bounds.height()));
   ShowMultitaskMenu(*window);
   multitask_menu_view = GetMultitaskMenuView(GetMultitaskMenu());
-  EXPECT_FALSE(multitask_menu_view->half_button_for_testing());
+  EXPECT_FALSE(
+      chromeos::MultitaskMenuViewTestApi(multitask_menu_view).GetHalfButton());
   EXPECT_FALSE(multitask_menu_view->partial_button());
 }
 
@@ -607,10 +617,11 @@
   chromeos::MultitaskMenuView* multitask_menu_view =
       GetMultitaskMenuView(multitask_menu);
   ASSERT_TRUE(multitask_menu_view);
-  EXPECT_FALSE(multitask_menu_view->half_button_for_testing());
+  chromeos::MultitaskMenuViewTestApi test_api(multitask_menu_view);
+  EXPECT_FALSE(test_api.GetHalfButton());
   EXPECT_FALSE(multitask_menu_view->partial_button());
-  EXPECT_TRUE(multitask_menu_view->full_button_for_testing());
-  EXPECT_FALSE(multitask_menu_view->float_button_for_testing());
+  EXPECT_TRUE(test_api.GetFullButton());
+  EXPECT_FALSE(test_api.GetFloatButton());
 }
 
 // Tests that the cue is still showing when the menu is opened, and it has been
@@ -618,30 +629,33 @@
 TEST_F(TabletModeMultitaskMenuTest, CueTransformOnShowMenu) {
   auto window = CreateAppWindow();
 
-  auto* multitask_cue = GetMultitaskMenuController()->multitask_cue();
-  ASSERT_TRUE(multitask_cue);
-  EXPECT_TRUE(multitask_cue->cue_layer());
+  auto* multitask_cue_controller =
+      GetMultitaskMenuController()->multitask_cue_controller();
+  ASSERT_TRUE(multitask_cue_controller);
+  EXPECT_TRUE(multitask_cue_controller->cue_layer());
 
   // Cue should still be showing when the menu is activated.
   ShowMultitaskMenu(*window);
-  ASSERT_TRUE(multitask_cue);
-  ui::Layer* cue_layer = multitask_cue->cue_layer();
+  ASSERT_TRUE(multitask_cue_controller);
+  ui::Layer* cue_layer = multitask_cue_controller->cue_layer();
   EXPECT_TRUE(cue_layer);
 
   // Verify cue is transformed to the right position.
   const gfx::Rect expected_bounds(
-      (window->bounds().width() - TabletModeMultitaskCue::kCueWidth) / 2,
+      (window->bounds().width() - TabletModeMultitaskCueController::kCueWidth) /
+          2,
       GetMultitaskMenuView(GetMultitaskMenu())->bounds().bottom() +
-          kVerticalPosition + TabletModeMultitaskCue::kCueYOffset,
-      TabletModeMultitaskCue::kCueWidth, TabletModeMultitaskCue::kCueHeight);
+          kVerticalPosition + TabletModeMultitaskCueController::kCueYOffset,
+      TabletModeMultitaskCueController::kCueWidth,
+      TabletModeMultitaskCueController::kCueHeight);
   EXPECT_EQ(expected_bounds, cue_layer->GetTargetTransform().MapRect(
                                  cue_layer->GetTargetBounds()));
 
   // Cue should still dismiss via timer after menu opened.
-  multitask_cue->FireCueDismissTimerForTesting();
+  multitask_cue_controller->FireCueDismissTimerForTesting();
 
-  ASSERT_TRUE(multitask_cue);
-  EXPECT_FALSE(multitask_cue->cue_layer());
+  ASSERT_TRUE(multitask_cue_controller);
+  EXPECT_FALSE(multitask_cue_controller->cue_layer());
 }
 
 // Tests that the cue appears on the correct window when the multitask menu is
@@ -659,17 +673,19 @@
 
   // Show the menu and cue on the first window.
   ShowMultitaskMenu(*window1);
-  auto* multitask_cue = GetMultitaskMenuController()->multitask_cue();
-  ASSERT_TRUE(multitask_cue);
-  EXPECT_TRUE(multitask_cue->cue_layer());
-  EXPECT_EQ(window1.get(), multitask_cue->window_);
+  auto* multitask_cue_controller =
+      GetMultitaskMenuController()->multitask_cue_controller();
+  ASSERT_TRUE(multitask_cue_controller);
+  EXPECT_TRUE(multitask_cue_controller->cue_layer());
+  EXPECT_EQ(window1.get(), multitask_cue_controller->window_);
 
   // Show the menu and cue on the second window.
   ShowMultitaskMenu(*window2);
-  multitask_cue = GetMultitaskMenuController()->multitask_cue();
-  ASSERT_TRUE(multitask_cue);
-  EXPECT_TRUE(multitask_cue->cue_layer());
-  EXPECT_EQ(window2.get(), multitask_cue->window_);
+  multitask_cue_controller =
+      GetMultitaskMenuController()->multitask_cue_controller();
+  ASSERT_TRUE(multitask_cue_controller);
+  EXPECT_TRUE(multitask_cue_controller->cue_layer());
+  EXPECT_EQ(window2.get(), multitask_cue_controller->window_);
 }
 
 // Tests that the bottom window can open the multitask menu in portrait mode. In
diff --git a/build/config/compiler/BUILD.gn b/build/config/compiler/BUILD.gn
index c6f12474..d751ddb 100644
--- a/build/config/compiler/BUILD.gn
+++ b/build/config/compiler/BUILD.gn
@@ -969,11 +969,6 @@
     # to compile dylibs on Android, such as for constructing unit test APKs.
     "-Cdefault-linker-libraries",
 
-    # Require `unsafe` blocks even in `unsafe` fns. This is intended to become
-    # an error by default eventually; see
-    # https://github.com/rust-lang/rust/issues/71668
-    "-Dunsafe_op_in_unsafe_fn",
-
     # To make Rust .d files compatible with ninja
     "-Zdep-info-omit-d-target",
 
@@ -1783,6 +1778,13 @@
       }
     }
   }
+
+  # Rust warnings
+
+  # Require `unsafe` blocks even in `unsafe` fns. This is intended to become
+  # an error by default eventually; see
+  # https://github.com/rust-lang/rust/issues/71668
+  rustflags = [ "-Dunsafe_op_in_unsafe_fn" ]
 }
 
 # prevent_unsafe_narrowing ----------------------------------------------------
diff --git a/build/fuchsia/linux_internal.sdk.sha1 b/build/fuchsia/linux_internal.sdk.sha1
index b5a8f96..00f4f14 100644
--- a/build/fuchsia/linux_internal.sdk.sha1
+++ b/build/fuchsia/linux_internal.sdk.sha1
@@ -1 +1 @@
-13.20230614.1.1
+13.20230614.2.1
diff --git a/chrome/android/chrome_test_java_sources.gni b/chrome/android/chrome_test_java_sources.gni
index 8a3e52c..af0de6b42 100644
--- a/chrome/android/chrome_test_java_sources.gni
+++ b/chrome/android/chrome_test_java_sources.gni
@@ -84,6 +84,7 @@
   "javatests/src/org/chromium/chrome/browser/bookmarks/BookmarkSaveFlowTest.java",
   "javatests/src/org/chromium/chrome/browser/bookmarks/BookmarkSearchBoxRowRenderTest.java",
   "javatests/src/org/chromium/chrome/browser/bookmarks/BookmarkSearchBoxRowTest.java",
+  "javatests/src/org/chromium/chrome/browser/bookmarks/ImprovedBookmarkFolderRowRenderTest.java",
   "javatests/src/org/chromium/chrome/browser/bookmarks/ImprovedBookmarkFolderSelectRowRenderTest.java",
   "javatests/src/org/chromium/chrome/browser/bookmarks/ImprovedBookmarkFolderViewRenderTest.java",
   "javatests/src/org/chromium/chrome/browser/bookmarks/ImprovedBookmarkRowRenderTest.java",
diff --git a/chrome/android/features/tab_ui/BUILD.gn b/chrome/android/features/tab_ui/BUILD.gn
index 265c8e3e..0bf179f8 100644
--- a/chrome/android/features/tab_ui/BUILD.gn
+++ b/chrome/android/features/tab_ui/BUILD.gn
@@ -39,6 +39,7 @@
     "java/res/drawable/ic_rating_star_half.xml",
     "java/res/drawable/ic_rating_star_outline.xml",
     "java/res/drawable/ic_select_all_24dp.xml",
+    "java/res/drawable/ic_tab_placeholder.xml",
     "java/res/drawable/iph_drag_and_drop_animated_drawable.xml",
     "java/res/drawable/iph_drag_and_drop_drawable.xml",
     "java/res/drawable/price_card_background.xml",
diff --git a/chrome/android/features/tab_ui/java/res/drawable/ic_tab_placeholder.xml b/chrome/android/features/tab_ui/java/res/drawable/ic_tab_placeholder.xml
new file mode 100644
index 0000000..7dec2e3
--- /dev/null
+++ b/chrome/android/features/tab_ui/java/res/drawable/ic_tab_placeholder.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2023 The Chromium Authors. All rights reserved.
+     Use of this source code is governed by a BSD-style license that can be
+     found in the LICENSE file. -->
+
+<vector
+  xmlns:android="http://schemas.android.com/apk/res/android"
+  android:width="64dp"
+  android:height="64dp"
+  android:viewportWidth="68"
+  android:viewportHeight="68"
+  android:tint="@macro/default_icon_color">
+  <group>
+      <clip-path android:pathData="M34,34m-33.75,0a33.75,33.75 0,1 1,67.5 0a33.75,33.75 0,1 1,-67.5 0"/>
+      <path android:fillColor="@android:color/black" android:pathData="M8.688,21.387V0.25H-5.375V73.375H31.188V62.082C31.188,60.552 29.948,59.313 28.418,59.313H25.519C23.99,59.313 22.75,58.073 22.75,56.543V56.457C22.75,54.927 23.99,53.688 25.519,53.688H25.692C30.281,53.688 34,49.968 34,45.38V42.308C34,37.72 30.281,34 25.692,34H22.75V29.695C22.75,26.636 20.27,24.156 17.212,24.156H11.457C9.927,24.156 8.688,22.916 8.688,21.387Z"/>
+      <path android:fillColor="@android:color/black" android:pathData="M31.188,5.962V3.063H73.375V49.469H61.952C55.834,49.469 50.875,44.509 50.875,38.392V36.813H47.976C44.917,36.813 42.438,34.333 42.438,31.274V25.476C42.438,22.417 44.917,19.938 47.976,19.938H56.5V17.038C56.5,13.98 54.02,11.5 50.961,11.5H36.726C33.667,11.5 31.188,9.02 31.188,5.962Z"/>
+  </group>
+  <path
+    android:fillColor="@android:color/transparent"
+    android:pathData="M34,34m-32.063,0a32.063,32.063 0,1 1,64.125 0a32.063,32.063 0,1 1,-64.125 0"
+    android:strokeColor="@android:color/black"
+    android:strokeWidth="3.375"/>
+</vector>
diff --git a/chrome/android/features/tab_ui/java/res/layout/tab_grid_card_item.xml b/chrome/android/features/tab_ui/java/res/layout/tab_grid_card_item.xml
index 82d62c2b..eaeadb8 100644
--- a/chrome/android/features/tab_ui/java/res/layout/tab_grid_card_item.xml
+++ b/chrome/android/features/tab_ui/java/res/layout/tab_grid_card_item.xml
@@ -50,14 +50,14 @@
             <org.chromium.chrome.browser.tasks.tab_management.TabGridThumbnailView
                 android:id="@+id/tab_thumbnail"
                 android:layout_width="match_parent"
-                android:layout_height="wrap_content"
+                android:layout_height="match_parent"
                 android:layout_below="@id/tab_title"
                 android:layout_marginLeft="@dimen/tab_grid_card_thumbnail_margin"
                 android:layout_marginRight="@dimen/tab_grid_card_thumbnail_margin"
                 android:layout_marginBottom="@dimen/tab_grid_card_thumbnail_margin"
                 android:gravity="center_horizontal"
                 android:scaleType="fitCenter"
-                android:adjustViewBounds="true"
+                android:adjustViewBounds="false"
                 android:importantForAccessibility="no"
                 android:src="@color/thumbnail_placeholder_on_primary_bg"
                 app:cornerRadiusTopStart="@dimen/tab_grid_card_thumbnail_corner_radius_top"
diff --git a/chrome/android/features/tab_ui/java/res/values/attrs.xml b/chrome/android/features/tab_ui/java/res/values/attrs.xml
index bf2c455..c015d084 100644
--- a/chrome/android/features/tab_ui/java/res/values/attrs.xml
+++ b/chrome/android/features/tab_ui/java/res/values/attrs.xml
@@ -6,7 +6,7 @@
 -->
 
 <resources>
-    <declare-styleable name="TabThumbnailPlaceHolder">
+    <declare-styleable name="TabThumbnailPlaceholder">
         <attr name="colorTileBase" format="color" />
         <attr name="elevationTileBase" format="dimension" />
     </declare-styleable>
diff --git a/chrome/android/features/tab_ui/java/res/values/colors.xml b/chrome/android/features/tab_ui/java/res/values/colors.xml
index 2ea6f05..7b3658a3 100644
--- a/chrome/android/features/tab_ui/java/res/values/colors.xml
+++ b/chrome/android/features/tab_ui/java/res/values/colors.xml
@@ -23,6 +23,9 @@
     <color name="incognito_tab_bg_color">@color/default_bg_color_dark_elev_4_baseline</color>
     <color name="incognito_tab_bg_selected_color">@color/baseline_primary_80</color>
 
+    <color name="incognito_placeholder_icon_color">@color/baseline_neutral_100</color>
+    <color name="incognito_placeholder_icon_selected_color">@color/baseline_primary_20</color>
+
     <color name="incognito_tab_group_hovered_bg_color">@color/default_bg_color_dark_elev_1_baseline</color>
     <color name="incognito_tab_group_hovered_bg_selected_color">@color/baseline_primary_80_alpha_10</color>
 
diff --git a/chrome/android/features/tab_ui/java/res/values/dimens.xml b/chrome/android/features/tab_ui/java/res/values/dimens.xml
index 490b0c1..48c25d37 100644
--- a/chrome/android/features/tab_ui/java/res/values/dimens.xml
+++ b/chrome/android/features/tab_ui/java/res/values/dimens.xml
@@ -43,6 +43,7 @@
     <dimen name="selection_tab_list_toggle_button_vertical_inset">22dp</dimen>
     <dimen name="tab_carousel_card_width">168dp</dimen>
     <dimen name="tasks_view_items_vertical_spacing">8dp</dimen>
+    <dimen name="tab_thumbnail_placeholder_vertical_offset">2dp</dimen>
 
     <!-- Dimens for incognito reauth promo message icon -->
     <dimen name="incognito_reauth_promo_message_icon_width">52dp</dimen>
diff --git a/chrome/android/features/tab_ui/java/res/values/styles.xml b/chrome/android/features/tab_ui/java/res/values/styles.xml
index 28eaa35..b2476d98 100644
--- a/chrome/android/features/tab_ui/java/res/values/styles.xml
+++ b/chrome/android/features/tab_ui/java/res/values/styles.xml
@@ -13,12 +13,12 @@
         <item name="colorControlHighlight">@color/filled_button_bg_color</item>
     </style>
 
-    <style name="TabThumbnailPlaceHolderStyle">
+    <style name="TabThumbnailPlaceholderStyle">
         <item name="colorTileBase">?attr/colorSurface</item>
         <item name="elevationTileBase">@dimen/default_elevation_1</item>
     </style>
 
-    <style name="TabThumbnailPlaceHolderStyle.Selected">
+    <style name="TabThumbnailPlaceholderStyle.Selected">
         <item name="colorTileBase">?attr/colorOnPrimary</item>
         <item name="elevationTileBase">@dimen/default_elevation_0</item>
     </style>
diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/MultiThumbnailCardProvider.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/MultiThumbnailCardProvider.java
index ec3a3a79..aadfd3d 100644
--- a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/MultiThumbnailCardProvider.java
+++ b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/MultiThumbnailCardProvider.java
@@ -301,11 +301,11 @@
         mEmptyThumbnailPaint.setStyle(Paint.Style.FILL);
         mEmptyThumbnailPaint.setAntiAlias(true);
         mEmptyThumbnailPaint.setColor(
-                TabUiThemeProvider.getMiniThumbnailPlaceHolderColor(context, false, false));
+                TabUiThemeProvider.getMiniThumbnailPlaceholderColor(context, false, false));
 
         mSelectedEmptyThumbnailPaint = new Paint(mEmptyThumbnailPaint);
         mSelectedEmptyThumbnailPaint.setColor(
-                TabUiThemeProvider.getMiniThumbnailPlaceHolderColor(context, false, true));
+                TabUiThemeProvider.getMiniThumbnailPlaceholderColor(context, false, true));
 
         // Paint used to set base for thumbnails, in case mEmptyThumbnailPaint has transparency.
         mThumbnailBasePaint = new Paint(mEmptyThumbnailPaint);
@@ -346,7 +346,7 @@
             @Override
             public void onTabModelSelected(TabModel newModel, TabModel oldModel) {
                 boolean isIncognito = newModel.isIncognito();
-                mEmptyThumbnailPaint.setColor(TabUiThemeProvider.getMiniThumbnailPlaceHolderColor(
+                mEmptyThumbnailPaint.setColor(TabUiThemeProvider.getMiniThumbnailPlaceholderColor(
                         context, isIncognito, false));
                 mTextPaint.setColor(
                         TabUiThemeProvider.getTabGroupNumberTextColor(context, isIncognito, false));
@@ -356,7 +356,7 @@
                         TabUiThemeProvider.getFaviconBackgroundColor(context, isIncognito));
 
                 mSelectedEmptyThumbnailPaint.setColor(
-                        TabUiThemeProvider.getMiniThumbnailPlaceHolderColor(
+                        TabUiThemeProvider.getMiniThumbnailPlaceholderColor(
                                 context, isIncognito, true));
                 mSelectedTextPaint.setColor(
                         TabUiThemeProvider.getTabGroupNumberTextColor(context, isIncognito, true));
diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGridThumbnailView.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGridThumbnailView.java
index 09a065bf..79e97159 100644
--- a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGridThumbnailView.java
+++ b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGridThumbnailView.java
@@ -8,18 +8,22 @@
 import android.content.res.TypedArray;
 import android.graphics.Bitmap;
 import android.graphics.Canvas;
+import android.graphics.Matrix;
 import android.graphics.Paint;
 import android.graphics.Path;
+import android.graphics.PorterDuff;
 import android.graphics.RectF;
 import android.graphics.drawable.Drawable;
 import android.graphics.drawable.GradientDrawable;
 import android.graphics.drawable.Icon;
+import android.graphics.drawable.VectorDrawable;
 import android.net.Uri;
 import android.os.Build;
 import android.util.AttributeSet;
 import android.widget.ImageView;
 
 import androidx.annotation.Nullable;
+import androidx.appcompat.content.res.AppCompatResources;
 import androidx.core.view.ViewCompat;
 
 import org.chromium.chrome.browser.tab.TabUtils;
@@ -38,6 +42,32 @@
     private static final boolean SUPPORTS_ANTI_ALIAS_CLIP =
             Build.VERSION.SDK_INT >= Build.VERSION_CODES.P;
 
+    /**
+     * Placeholder drawable constants.
+     */
+    private static final float SIZE_PERCENTAGE = 0.42f;
+    private static Integer sVerticalOffsetPx;
+
+    /**
+     * To prevent {@link TabGridThumbnailView#updateImage()} from running during inflation.
+     */
+    private boolean mInitialized;
+
+    /**
+     * Placeholder icon drawable to use if there is no thumbnail. This is drawn on-top of the
+     * {@link mBackgroundDrawable} which defines the shape of the thumbnail. There are two
+     * separate layers because the background scales with the thumbnail size whereas the icon
+     * will be the SIZE_PERCENTAGE of the minimum side length of the thumbnail size centered
+     * and adjusted upwards.
+     */
+    private VectorDrawable mIconDrawable;
+    private Matrix mIconMatrix;
+    private int mIconColor;
+
+    /**
+     * Background drawable which is present while in placeholder mode.
+     * Once a thumbnail is set this will be removed.
+     */
     private final GradientDrawable mBackgroundDrawable;
 
     // Pre-allocate to avoid repeat calls during {@link onDraw}.
@@ -54,11 +84,18 @@
 
     public TabGridThumbnailView(Context context, AttributeSet attrs, int defStyle) {
         super(context, attrs, defStyle);
+
+        if (sVerticalOffsetPx == null) {
+            sVerticalOffsetPx = context.getResources().getDimensionPixelSize(
+                    R.dimen.tab_thumbnail_placeholder_vertical_offset);
+        }
+
         mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
         mPaint.setStyle(Paint.Style.STROKE);
         mPaint.setStrokeWidth(1);
         mPath = new Path();
         mRectF = new RectF();
+        mIconMatrix = new Matrix();
         mBackgroundDrawable = new GradientDrawable();
 
         TypedArray a =
@@ -75,25 +112,31 @@
 
         setRoundedCorners(radiusTopStart, radiusTopEnd, radiusBottomStart, radiusBottomEnd);
         setBackground(mBackgroundDrawable);
+        mInitialized = true;
     }
 
     @Override
     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
         super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+        if (!mInitialized) return;
 
         int measuredWidth = getMeasuredWidth();
         int measureHeight = getMeasuredHeight();
 
         // TODO(crbug/1434775): Consider fixing the aspect ratio and cropping/resizing the Drawable
         // to fit.
-        int expectedHeight =
-                (int) (measuredWidth * 1.0 / TabUtils.getTabThumbnailAspectRatio(getContext()));
-        if (isPlaceHolder()) {
+        // Don't force a size if the placeholder drawable is in use.
+        if (isPlaceholder()) {
+            final int expectedHeight =
+                    (int) (measuredWidth * 1.0 / TabUtils.getTabThumbnailAspectRatio(getContext()));
             measureHeight = expectedHeight;
         }
 
         setMeasuredDimension(measuredWidth, measureHeight);
         mRectF.set(0, 0, getMeasuredWidth(), getMeasuredHeight());
+        if (TabUiFeatureUtilities.sThumbnailPlaceholder.isEnabled()) {
+            resizeIconDrawable();
+        }
     }
 
     @Override
@@ -128,6 +171,10 @@
 
     @Override
     public void onDraw(Canvas canvas) {
+        if (!mInitialized) {
+            super.onDraw(canvas);
+            return;
+        }
         mPath.reset();
         mPath.addRoundRect(mRectF, mRadii, Path.Direction.CW);
         canvas.save();
@@ -142,20 +189,53 @@
     }
 
     private void updateImage() {
-        if (isPlaceHolder()) {
-            // If the drawable is empty, display a placeholder.
+        if (!mInitialized) return;
+        // If the drawable is empty, display a placeholder image.
+        if (isPlaceholder()) {
             setBackground(mBackgroundDrawable);
+
+            if (TabUiFeatureUtilities.sThumbnailPlaceholder.isEnabled()) {
+                updateIconDrawable();
+            }
             return;
         }
-        // Remove the background as multi-thumbnails have transparency.
+
+        clearColorFilter();
+        mIconDrawable = null;
+        // Remove the background drawable as mini group thumbnails have a transparent space between
+        // them and normal thumbnails are opaque.
         setBackground(null);
     }
 
+    private void updateIconDrawable() {
+        if (mIconDrawable == null) {
+            mIconDrawable = (VectorDrawable) AppCompatResources.getDrawable(
+                    getContext(), R.drawable.ic_tab_placeholder);
+        }
+        setColorFilter(mIconColor, PorterDuff.Mode.SRC_IN);
+        // External callers either change this or use MATRIX there is no need to reset this.
+        // See {@link TabGridViewBinder#updateThumbnail()}.
+        setScaleType(ImageView.ScaleType.MATRIX);
+        resizeIconDrawable();
+        super.setImageDrawable(mIconDrawable);
+    }
+
     /**
      * @return whether the image drawable is a placeholder.
      */
-    boolean isPlaceHolder() {
-        return getDrawable() == null;
+    boolean isPlaceholder() {
+        if (TabUiFeatureUtilities.sThumbnailPlaceholder.isEnabled()) {
+            // The drawable can only be null if we just removed the drawable and need to set the
+            // mIconDrawable.
+            if (getDrawable() == null) return true;
+
+            // Otherwise there should always be a thumbnail or placeholder drawable.
+            if (mIconDrawable == null) return false;
+            return getDrawable() == mIconDrawable;
+        } else {
+            // There is only a drawable when the thumbnail is set.
+            return getDrawable() == null;
+        }
     }
 
     /**
@@ -163,13 +243,26 @@
      * @param isIncognito Whether the thumbnail is on an incognito tab.
      * @param isSelected Whether the thumbnail is on a selected tab.
      */
-    void setColorThumbnailPlaceHolder(boolean isIncognito, boolean isSelected) {
-        mBackgroundDrawable.setColor(TabUiThemeProvider.getMiniThumbnailPlaceHolderColor(
+    void updateThumbnailPlaceholder(boolean isIncognito, boolean isSelected) {
+        // Step 1: Background color.
+        mBackgroundDrawable.setColor(TabUiThemeProvider.getMiniThumbnailPlaceholderColor(
                 getContext(), isIncognito, isSelected));
-        int oldColor = mPaint.getColor();
-        mPaint.setColor(TabUiThemeProvider.getCardViewBackgroundColor(
-                getContext(), isIncognito, isSelected));
-        if (!SUPPORTS_ANTI_ALIAS_CLIP && oldColor != mPaint.getColor()) {
+        final int oldColor = mPaint.getColor();
+        final int newColor = TabUiThemeProvider.getCardViewBackgroundColor(
+                getContext(), isIncognito, isSelected);
+        mPaint.setColor(newColor);
+
+        // Step 2: Placeholder icon.
+        // Make property changes outside the flag intentionally in the event the flag flips status
+        // these will have no material effect on the UI and are safe.
+        mIconColor = TabUiThemeProvider.getThumbnailPlaceholderIconColor(
+                getContext(), isIncognito, isSelected);
+        if (TabUiFeatureUtilities.sThumbnailPlaceholder.isEnabled() && mIconDrawable != null) {
+            setColorFilter(mIconColor, PorterDuff.Mode.SRC_IN);
+        }
+
+        // Step 3: Invalidate for versions earlier than Android P.
+        if (!SUPPORTS_ANTI_ALIAS_CLIP && !isPlaceholder() && oldColor != newColor) {
             invalidate();
         }
     }
@@ -197,4 +290,26 @@
 
         mBackgroundDrawable.setCornerRadii(mRadii);
     }
+
+    private void resizeIconDrawable() {
+        if (mIconDrawable != null) {
+            final int width = getWidth();
+            final int height = getHeight();
+
+            // Vector graphic is square so width or height doesn't matter.
+            final int vectorEdgeLength = mIconDrawable.getIntrinsicWidth();
+
+            // Shortest edge of thumbnail region * SIZE_PERCENTAGE.
+            final int edgeLength = Math.round(SIZE_PERCENTAGE * Math.min(width, height));
+            final float scale = (float) edgeLength / (float) vectorEdgeLength;
+            mIconMatrix.reset();
+            mIconMatrix.postScale(scale, scale);
+
+            // Center and offset vertically by sVerticalOffsetPx to account for optical illusion of
+            // centering.
+            mIconMatrix.postTranslate((float) (width - edgeLength) / 2f,
+                    (float) (height - edgeLength) / 2f - sVerticalOffsetPx);
+            setImageMatrix(mIconMatrix);
+        }
+    }
 }
diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGridViewBinder.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGridViewBinder.java
index 00e3ca2..b79a496 100644
--- a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGridViewBinder.java
+++ b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGridViewBinder.java
@@ -12,7 +12,6 @@
 import android.util.Size;
 import android.view.View;
 import android.view.ViewGroup;
-import android.view.ViewGroup.LayoutParams;
 import android.widget.ImageView;
 import android.widget.ImageView.ScaleType;
 import android.widget.TextView;
@@ -136,7 +135,6 @@
             view.setLayoutParams(view.getLayoutParams());
             TabGridThumbnailView thumbnail =
                     (TabGridThumbnailView) view.fastFindViewById(R.id.tab_thumbnail);
-            thumbnail.getLayoutParams().height = LayoutParams.MATCH_PARENT;
             updateThumbnail(view, model);
         }
     }
@@ -288,7 +286,7 @@
         final TabListMediator.ThumbnailFetcher fetcher = model.get(TabProperties.THUMBNAIL_FETCHER);
         // To GC on hide remove the thumbnail and set a background color.
         boolean isSelected = model.get(TabProperties.IS_SELECTED);
-        thumbnail.setColorThumbnailPlaceHolder(model.get(TabProperties.IS_INCOGNITO), isSelected);
+        thumbnail.updateThumbnailPlaceholder(model.get(TabProperties.IS_INCOGNITO), isSelected);
         thumbnail.setImageDrawable(null);
         if (fetcher == null) {
             return;
@@ -421,7 +419,7 @@
         titleView.setTextColor(TabUiThemeProvider.getTitleTextColor(
                 titleView.getContext(), isIncognito, isSelected));
 
-        thumbnail.setColorThumbnailPlaceHolder(isIncognito, isSelected);
+        thumbnail.updateThumbnailPlaceholder(isIncognito, isSelected);
 
         if (TabUiFeatureUtilities.isTabGroupsAndroidEnabled(rootView.getContext())) {
             ViewCompat.setBackgroundTintList(backgroundView,
diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabListMediator.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabListMediator.java
index 520d716..f9f1ff6 100644
--- a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabListMediator.java
+++ b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabListMediator.java
@@ -1844,7 +1844,8 @@
         };
 
         if (mActionsOnAllRelatedTabs && relatedTabList.size() > 1) {
-            if (!TabUiFeatureUtilities.isTabGroupsAndroidContinuationEnabled(mContext)) {
+            if (mMode != TabListMode.LIST
+                    || !TabUiFeatureUtilities.isTabGroupsAndroidContinuationEnabled(mContext)) {
                 // For tab group card in grid tab switcher, the favicon is set to be null.
                 mModel.get(modelIndex).model.set(TabProperties.FAVICON, null);
                 mModel.get(modelIndex).model.set(TabProperties.FAVICON_FETCHER, null);
@@ -1859,7 +1860,7 @@
                 urls.add(relatedTabList.get(i).getUrl());
             }
 
-            // For tab group card in grid tab switcher, the favicon is the composed favicon.
+            // For tab group card in list tab switcher, the favicon is the composed favicon.
             mModel.get(modelIndex)
                     .model.set(TabProperties.FAVICON_FETCHER,
                             mTabListFaviconProvider.getComposedFaviconImageFetcher(
diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabSwitcher.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabSwitcher.java
index ea4cc80..4e48fa9 100644
--- a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabSwitcher.java
+++ b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabSwitcher.java
@@ -232,6 +232,12 @@
         int getBitmapFetchCountForTesting();
 
         /**
+         * Reset the current count of thumbnail fetches for testing.
+         */
+        @VisibleForTesting
+        default void resetBitmapFetchCountForTesting(){};
+
+        /**
          * @return The soft cleanup delay for testing.
          */
         @VisibleForTesting
diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabSwitcherCoordinator.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabSwitcherCoordinator.java
index 7e35857..f15fd628 100644
--- a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabSwitcherCoordinator.java
+++ b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabSwitcherCoordinator.java
@@ -591,6 +591,12 @@
 
     @Override
     @VisibleForTesting
+    public void resetBitmapFetchCountForTesting() {
+        TabListMediator.ThumbnailFetcher.sFetchCountForTesting = 0;
+    }
+
+    @Override
+    @VisibleForTesting
     public int getSoftCleanupDelayForTesting() {
         return mMediator.getSoftCleanupDelayForTesting();
     }
diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabUiFeatureUtilities.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabUiFeatureUtilities.java
index 49e3ad96..9d6d7494 100644
--- a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabUiFeatureUtilities.java
+++ b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabUiFeatureUtilities.java
@@ -16,6 +16,7 @@
 import org.chromium.chrome.browser.flags.ChromeFeatureList;
 import org.chromium.chrome.browser.flags.DoubleCachedFieldTrialParameter;
 import org.chromium.chrome.browser.flags.IntCachedFieldTrialParameter;
+import org.chromium.chrome.browser.flags.MutableFlagWithSafeDefault;
 import org.chromium.chrome.browser.tasks.ReturnToChromeUtil;
 import org.chromium.chrome.browser.util.ChromeAccessibilityUtil;
 import org.chromium.ui.base.DeviceFormFactor;
@@ -80,6 +81,9 @@
 
     private static boolean sTabSelectionEditorLongPressEntryEnabled;
 
+    public static final MutableFlagWithSafeDefault sThumbnailPlaceholder =
+            new MutableFlagWithSafeDefault(ChromeFeatureList.THUMBNAIL_PLACEHOLDER, false);
+
     /**
      * Set whether the longpress entry for TabSelectionEditor is enabled. Currently only in tests.
      */
diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabUiThemeProvider.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabUiThemeProvider.java
index 6b83ed1b..c10433ae 100644
--- a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabUiThemeProvider.java
+++ b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabUiThemeProvider.java
@@ -21,6 +21,7 @@
 import org.chromium.chrome.R;
 import org.chromium.chrome.browser.flags.ChromeFeatureList;
 import org.chromium.components.browser_ui.styles.SemanticColorUtils;
+import org.chromium.ui.util.ColorUtils;
 
 /**
  * Utility class that provides theme related attributes for Tab UI.
@@ -171,6 +172,48 @@
     }
 
     /**
+     * Returns the color to use for the thumbnail placeholder icon based on the state of the card.
+     *
+     * @param context {@link Context} to access resources.
+     * @param isIncognito Whether the color is used for incognito mode.
+     * @param isSelected Whether the tab is currently selected.
+     * @return The color to tint the globe icon drawable.
+     */
+    @ColorInt
+    public static int getThumbnailPlaceholderIconColor(
+            Context context, boolean isIncognito, boolean isSelected) {
+        if (isIncognito) {
+            @ColorRes
+            final int colorRes = isSelected ? R.color.incognito_placeholder_icon_selected_color
+                                            : R.color.incognito_placeholder_icon_color;
+            @ColorInt
+            final int color = context.getColor(colorRes);
+            // 40% if selected, 25% if not selected
+            return isSelected ? applyAlpha(0x66, color) : applyAlpha(0x40, color);
+        }
+        final boolean isDarkTheme = ColorUtils.inNightMode(context);
+        if (isDarkTheme) {
+            // colorOnPrimary == default_icon_color_on_accent_1.
+            @ColorInt
+            int color = isSelected ? MaterialColors.getColor(
+                                context, org.chromium.chrome.R.attr.colorOnPrimary, TAG)
+                                   : SemanticColorUtils.getDefaultIconColor(context);
+            // 40% if selected. 25% if not selected.
+            return isSelected ? applyAlpha(0x66, color) : applyAlpha(0x40, color);
+        }
+        @ColorInt
+        int color = isSelected ? SemanticColorUtils.getDefaultIconColorAccent1(context)
+                               : SemanticColorUtils.getDefaultIconColor(context);
+        // 100% if selected, 20% if not selected.
+        return isSelected ? color : applyAlpha(0x33, color);
+    }
+
+    @ColorInt
+    private static int applyAlpha(int alpha, @ColorInt int color) {
+        return Color.argb(alpha, Color.red(color), Color.green(color), Color.blue(color));
+    }
+
+    /**
      * Returns the mini-thumbnail placeholder color for the multi-thumbnail tab grid card based on
      * the incognito mode.
      *
@@ -180,7 +223,7 @@
      * @return The mini-thumbnail placeholder color.
      */
     @ColorInt
-    public static int getMiniThumbnailPlaceHolderColor(
+    public static int getMiniThumbnailPlaceholderColor(
             Context context, boolean isIncognito, boolean isSelected) {
         if (isIncognito) {
             @ColorRes
@@ -193,16 +236,16 @@
                             : R.integer.tab_thumbnail_placeholder_color_alpha);
 
             @StyleRes
-            int styleRes = isSelected ? R.style.TabThumbnailPlaceHolderStyle_Selected
-                                      : R.style.TabThumbnailPlaceHolderStyle;
+            int styleRes = isSelected ? R.style.TabThumbnailPlaceholderStyle_Selected
+                                      : R.style.TabThumbnailPlaceholderStyle;
             TypedArray ta =
-                    context.obtainStyledAttributes(styleRes, R.styleable.TabThumbnailPlaceHolder);
+                    context.obtainStyledAttributes(styleRes, R.styleable.TabThumbnailPlaceholder);
 
             @ColorInt
             int baseColor = ta.getColor(
-                    R.styleable.TabThumbnailPlaceHolder_colorTileBase, Color.TRANSPARENT);
+                    R.styleable.TabThumbnailPlaceholder_colorTileBase, Color.TRANSPARENT);
             float tileSurfaceElevation =
-                    ta.getDimension(R.styleable.TabThumbnailPlaceHolder_elevationTileBase, 0);
+                    ta.getDimension(R.styleable.TabThumbnailPlaceholder_elevationTileBase, 0);
 
             ta.recycle();
             if (tileSurfaceElevation != 0) {
diff --git a/chrome/android/features/tab_ui/javatests/src/org/chromium/chrome/browser/tasks/tab_management/TabGridThumbnailViewRenderTest.java b/chrome/android/features/tab_ui/javatests/src/org/chromium/chrome/browser/tasks/tab_management/TabGridThumbnailViewRenderTest.java
new file mode 100644
index 0000000..3ce56c8e
--- /dev/null
+++ b/chrome/android/features/tab_ui/javatests/src/org/chromium/chrome/browser/tasks/tab_management/TabGridThumbnailViewRenderTest.java
@@ -0,0 +1,245 @@
+// 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.
+
+package org.chromium.chrome.browser.tasks.tab_management;
+
+import android.content.res.ColorStateList;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Matrix;
+import android.graphics.Paint;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.FrameLayout;
+
+import androidx.annotation.ColorInt;
+import androidx.core.view.ViewCompat;
+import androidx.test.filters.MediumTest;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestRule;
+import org.junit.runner.RunWith;
+
+import org.chromium.base.test.BaseActivityTestRule;
+import org.chromium.base.test.params.ParameterAnnotations;
+import org.chromium.base.test.params.ParameterSet;
+import org.chromium.base.test.params.ParameterizedRunner;
+import org.chromium.base.test.util.Batch;
+import org.chromium.base.test.util.Feature;
+import org.chromium.chrome.browser.flags.ChromeFeatureList;
+import org.chromium.chrome.browser.tab.TabUtils;
+import org.chromium.chrome.test.ChromeJUnit4RunnerDelegate;
+import org.chromium.chrome.test.R;
+import org.chromium.chrome.test.util.ChromeRenderTestRule;
+import org.chromium.chrome.test.util.browser.Features;
+import org.chromium.content_public.browser.test.util.TestThreadUtils;
+import org.chromium.ui.test.util.BlankUiTestActivity;
+import org.chromium.ui.test.util.NightModeTestUtils;
+import org.chromium.ui.test.util.RenderTestRule;
+
+import java.io.IOException;
+import java.util.List;
+
+/**
+ * Render tests for the UI elements of the {@link TabGridThumbnailView}.
+ */
+@RunWith(ParameterizedRunner.class)
+@ParameterAnnotations.UseRunnerDelegate(ChromeJUnit4RunnerDelegate.class)
+@Batch(Batch.PER_CLASS)
+public class TabGridThumbnailViewRenderTest {
+    @ParameterAnnotations.ClassParameter
+    public static List<ParameterSet> sClassParams =
+            new NightModeTestUtils.NightModeParams().getParameters();
+
+    @Rule
+    public final ChromeRenderTestRule mRenderTestRule =
+            ChromeRenderTestRule.Builder.withPublicCorpus()
+                    .setBugComponent(RenderTestRule.Component.UI_BROWSER_MOBILE_TAB_SWITCHER_GRID)
+                    .setRevision(2)
+                    .build();
+
+    @Rule
+    public BaseActivityTestRule<BlankUiTestActivity> mActivityTestRule =
+            new BaseActivityTestRule<>(BlankUiTestActivity.class);
+
+    @Rule
+    public TestRule mJunitProcessor = new Features.JUnitProcessor();
+
+    private FrameLayout mContentView;
+    private ViewGroup mTabCard;
+    private TabGridThumbnailView mTabGridThumbnailView;
+    private Bitmap mBitmap;
+
+    public TabGridThumbnailViewRenderTest(boolean nightModeEnabled) {
+        NightModeTestUtils.setUpNightModeForBlankUiTestActivity(nightModeEnabled);
+        mRenderTestRule.setNightModeEnabled(nightModeEnabled);
+    }
+
+    @Before
+    public void setUp() {
+        mActivityTestRule.launchActivity(null);
+        mActivityTestRule.getActivity().setTheme(R.style.Theme_BrowserUI_DayNight);
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            mContentView = new FrameLayout(mActivityTestRule.getActivity());
+            mContentView.setBackgroundColor(Color.WHITE);
+
+            mTabCard = (ViewGroup) mActivityTestRule.getActivity().getLayoutInflater().inflate(
+                    R.layout.closable_tab_grid_card_item, mContentView, false);
+            mTabCard.setVisibility(View.VISIBLE);
+            mContentView.addView(mTabCard);
+
+            mTabGridThumbnailView = mContentView.findViewById(R.id.tab_thumbnail);
+
+            FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(
+                    ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
+            mActivityTestRule.getActivity().setContentView(mContentView, params);
+        });
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            final int cardWidthPx = mContentView.getMeasuredWidth() / 2;
+            final int cardHeightPx =
+                    TabUtils.deriveGridCardHeight(cardWidthPx, mActivityTestRule.getActivity());
+            mTabCard.setMinimumWidth(cardWidthPx);
+            mTabCard.setMinimumHeight(cardHeightPx);
+            mTabCard.getLayoutParams().width = cardWidthPx;
+            mTabCard.getLayoutParams().height = cardHeightPx;
+            mTabCard.setLayoutParams(mTabCard.getLayoutParams());
+        });
+        TestThreadUtils.runOnUiThreadBlocking(() -> { mBitmap = createBitmapFourColor(); });
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        TestThreadUtils.runOnUiThreadBlocking(
+                () -> { NightModeTestUtils.tearDownNightModeForBlankUiTestActivity(); });
+    }
+
+    private Bitmap createBitmapFourColor() {
+        final int width = mTabGridThumbnailView.getMeasuredWidth();
+        final int height = mTabGridThumbnailView.getMeasuredHeight();
+
+        Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
+        Canvas canvas = new Canvas(bitmap);
+        canvas.drawColor(Color.TRANSPARENT);
+
+        Paint paint = new Paint();
+        paint.setStyle(Paint.Style.FILL);
+        paint.setAntiAlias(true);
+        paint.setFilterBitmap(true);
+        paint.setDither(true);
+
+        paint.setColor(Color.RED);
+        final float halfWidth = width / 2.0f;
+        final float halfHeight = height / 2.0f;
+        final float space = 5f;
+        canvas.drawRect(0f, 0f, halfWidth - space, halfHeight - space, paint);
+
+        paint.setColor(Color.GREEN);
+        canvas.drawRect(halfWidth + space, 0f, width, halfHeight - space, paint);
+
+        paint.setColor(Color.BLUE);
+        canvas.drawRect(0f, halfHeight + space, halfWidth - space, height, paint);
+
+        paint.setColor(Color.WHITE);
+        canvas.drawRect(halfWidth + space, halfHeight + space, width, height, paint);
+        return bitmap;
+    }
+
+    @Test
+    @MediumTest
+    @Feature("RenderTest")
+    @Features.EnableFeatures({ChromeFeatureList.THUMBNAIL_PLACEHOLDER})
+    public void testPlaceholderDrawable() throws IOException, InterruptedException {
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            updateColor(/*isIncognito=*/true, /*isSelected=*/false);
+            mTabGridThumbnailView.setImageDrawable(null);
+        });
+        mRenderTestRule.render(mTabCard, "placeholder_incognito_without_thumbnail_deselected");
+
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            updateColor(/*isIncognito=*/false, /*isSelected=*/false);
+            mTabGridThumbnailView.setImageDrawable(null);
+        });
+        mRenderTestRule.render(mTabCard, "placeholder_without_thumbnail_deselected");
+
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            mTabGridThumbnailView.setImageBitmap(mBitmap);
+            mTabGridThumbnailView.setImageMatrix(new Matrix());
+        });
+        mRenderTestRule.render(mTabCard, "placeholder_with_thumbnail_deselected");
+
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            updateColor(/*isIncognito=*/false, /*isSelected=*/true);
+            mTabGridThumbnailView.setImageDrawable(null);
+        });
+        mRenderTestRule.render(mTabCard, "placeholder_without_thumbnail_selected");
+
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            mTabGridThumbnailView.setImageBitmap(mBitmap);
+            mTabGridThumbnailView.setImageMatrix(new Matrix());
+        });
+        mRenderTestRule.render(mTabCard, "placeholder_with_thumbnail_selected");
+
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            updateColor(/*isIncognito=*/true, /*isSelected=*/true);
+            mTabGridThumbnailView.setImageDrawable(null);
+        });
+        mRenderTestRule.render(mTabCard, "placeholder_incognito_without_thumbnail_selected");
+    }
+
+    @Test
+    @MediumTest
+    @Feature("RenderTest")
+    @Features.DisableFeatures({ChromeFeatureList.THUMBNAIL_PLACEHOLDER})
+    public void testNoPlaceholderDrawable() throws IOException, InterruptedException {
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            updateColor(/*isIncognito=*/true, /*isSelected=*/false);
+            mTabGridThumbnailView.setImageDrawable(null);
+        });
+        mRenderTestRule.render(mTabCard, "no_placeholder_incognito_without_thumbnail_deselected");
+
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            updateColor(/*isIncognito=*/false, /*isSelected=*/false);
+            mTabGridThumbnailView.setImageDrawable(null);
+        });
+        mRenderTestRule.render(mTabCard, "no_placeholder_without_thumbnail_deselected");
+
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            mTabGridThumbnailView.setImageBitmap(mBitmap);
+            mTabGridThumbnailView.setImageMatrix(new Matrix());
+        });
+        mRenderTestRule.render(mTabCard, "no_placeholder_with_thumbnail_deselected");
+
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            updateColor(/*isIncognito=*/false, /*isSelected=*/true);
+            mTabGridThumbnailView.setImageDrawable(null);
+        });
+        mRenderTestRule.render(mTabCard, "no_placeholder_without_thumbnail_selected");
+
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            mTabGridThumbnailView.setImageBitmap(mBitmap);
+            mTabGridThumbnailView.setImageMatrix(new Matrix());
+        });
+        mRenderTestRule.render(mTabCard, "no_placeholder_with_thumbnail_selected");
+
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            updateColor(/*isIncognito=*/true, /*isSelected=*/true);
+            mTabGridThumbnailView.setImageDrawable(null);
+        });
+        mRenderTestRule.render(mTabCard, "no_placeholder_incognito_without_thumbnail_selected");
+    }
+
+    private void updateColor(boolean isIncognito, boolean isSelected) {
+        View cardView = mTabCard.findViewById(R.id.card_view);
+        cardView.getBackground().mutate();
+        final @ColorInt int backgroundColor = TabUiThemeProvider.getCardViewBackgroundColor(
+                cardView.getContext(), isIncognito, isSelected);
+        ViewCompat.setBackgroundTintList(cardView, ColorStateList.valueOf(backgroundColor));
+
+        mTabGridThumbnailView.updateThumbnailPlaceholder(isIncognito, isSelected);
+    }
+}
diff --git a/chrome/android/features/tab_ui/javatests/src/org/chromium/chrome/browser/tasks/tab_management/TabListViewHolderTest.java b/chrome/android/features/tab_ui/javatests/src/org/chromium/chrome/browser/tasks/tab_management/TabListViewHolderTest.java
index e1dc17f8..bbf5059b 100644
--- a/chrome/android/features/tab_ui/javatests/src/org/chromium/chrome/browser/tasks/tab_management/TabListViewHolderTest.java
+++ b/chrome/android/features/tab_ui/javatests/src/org/chromium/chrome/browser/tasks/tab_management/TabListViewHolderTest.java
@@ -443,7 +443,7 @@
         TabGridThumbnailView thumbnail = mTabGridView.findViewById(R.id.tab_thumbnail);
         assertNull("Thumbnail should be set to no drawable.", thumbnail.getDrawable());
         assertNotNull("Thumbnail should have a background drawable.", thumbnail.getBackground());
-        assertTrue("Thumbnail should be set to a place holder.", thumbnail.isPlaceHolder());
+        assertTrue("Thumbnail should be set to a place holder.", thumbnail.isPlaceholder());
         mGridModel.set(TabProperties.THUMBNAIL_FETCHER, null);
         Assert.assertNull("Thumbnail should be release when thumbnail fetcher is set to null.",
                 thumbnail.getDrawable());
@@ -453,7 +453,7 @@
         assertThat("Thumbnail should be set.", thumbnail.getDrawable(),
                 instanceOf(BitmapDrawable.class));
         assertNull("Thumbnail should not have a background drawable.", thumbnail.getBackground());
-        assertFalse("Thumbnail should not be set to a place holder.", thumbnail.isPlaceHolder());
+        assertFalse("Thumbnail should not be set to a place holder.", thumbnail.isPlaceholder());
         Assert.assertEquals(2, mThumbnailFetchedCount.get());
     }
 
@@ -465,19 +465,19 @@
         TabGridThumbnailView thumbnail = mTabGridView.findViewById(R.id.tab_thumbnail);
         assertNull("Thumbnail should be set to no drawable.", thumbnail.getDrawable());
         assertNotNull("Thumbnail should have a background drawable.", thumbnail.getBackground());
-        assertTrue("Thumbnail should be set to a place holder.", thumbnail.isPlaceHolder());
+        assertTrue("Thumbnail should be set to a place holder.", thumbnail.isPlaceholder());
 
         mShouldReturnBitmap = true;
         mGridModel.set(TabProperties.THUMBNAIL_FETCHER, mMockThumbnailProvider);
         assertNull("Thumbnail should be set to no drawable.", thumbnail.getDrawable());
         assertNotNull("Thumbnail should have a background drawable.", thumbnail.getBackground());
-        assertTrue("Thumbnail should be set to a place holder.", thumbnail.isPlaceHolder());
+        assertTrue("Thumbnail should be set to a place holder.", thumbnail.isPlaceholder());
         mGridModel.set(TabProperties.GRID_CARD_SIZE, new Size(100, 500));
         mGridModel.set(TabProperties.THUMBNAIL_FETCHER, mMockThumbnailProvider);
         assertThat("Thumbnail should be set.", thumbnail.getDrawable(),
                 instanceOf(BitmapDrawable.class));
         assertNull("Thumbnail should not have a background drawable.", thumbnail.getBackground());
-        assertFalse("Thumbnail should not be set to a place holder.", thumbnail.isPlaceHolder());
+        assertFalse("Thumbnail should not be set to a place holder.", thumbnail.isPlaceholder());
         Assert.assertEquals(2, mThumbnailFetchedCount.get());
     }
 
diff --git a/chrome/android/features/tab_ui/javatests/src/org/chromium/chrome/browser/tasks/tab_management/TabSwitcherMultiWindowTest.java b/chrome/android/features/tab_ui/javatests/src/org/chromium/chrome/browser/tasks/tab_management/TabSwitcherMultiWindowTest.java
index 0a99bd9..6aa55ab 100644
--- a/chrome/android/features/tab_ui/javatests/src/org/chromium/chrome/browser/tasks/tab_management/TabSwitcherMultiWindowTest.java
+++ b/chrome/android/features/tab_ui/javatests/src/org/chromium/chrome/browser/tasks/tab_management/TabSwitcherMultiWindowTest.java
@@ -39,6 +39,7 @@
 import org.chromium.base.test.util.Batch;
 import org.chromium.base.test.util.CommandLineFlags;
 import org.chromium.base.test.util.CriteriaHelper;
+import org.chromium.base.test.util.DisabledTest;
 import org.chromium.base.test.util.Restriction;
 import org.chromium.chrome.browser.ChromeTabbedActivity;
 import org.chromium.chrome.browser.flags.ChromeFeatureList;
@@ -95,6 +96,7 @@
 
     @Test
     @LargeTest
+    @DisabledTest(message = "https://crbug.com/1454902")
     public void testMoveTabsAcrossWindow_GTS_WithoutGroup() {
         // Initially, we have 4 normal tabs (including the one created at activity start) and 3
         // incognito tabs in mCta1.
@@ -178,6 +180,7 @@
     @Test
     @LargeTest
     @Features.EnableFeatures({ChromeFeatureList.TAB_GROUPS_ANDROID})
+    @DisabledTest(message = "https://crbug.com/1454902")
     public void testMoveTabsAcrossWindow_GTS_WithGroup() {
         // Initially, we have 5 normal tabs (including the one created at activity start) and 5
         // incognito tabs in mCta1.
diff --git a/chrome/android/features/tab_ui/javatests/src/org/chromium/chrome/browser/tasks/tab_management/TabSwitcherTabletTest.java b/chrome/android/features/tab_ui/javatests/src/org/chromium/chrome/browser/tasks/tab_management/TabSwitcherTabletTest.java
index 488c9fa..b2776f4d 100644
--- a/chrome/android/features/tab_ui/javatests/src/org/chromium/chrome/browser/tasks/tab_management/TabSwitcherTabletTest.java
+++ b/chrome/android/features/tab_ui/javatests/src/org/chromium/chrome/browser/tasks/tab_management/TabSwitcherTabletTest.java
@@ -146,6 +146,7 @@
         TestThreadUtils.runOnUiThreadBlocking(() -> {
             activity.getLayoutManagerSupplier().removeObserver(mLayoutManagerCallback);
         });
+        mTabListDelegate.resetBitmapFetchCountForTesting();
     }
 
     @Test
diff --git a/chrome/android/features/tab_ui/javatests/src/org/chromium/chrome/browser/tasks/tab_management/TabUiTestHelper.java b/chrome/android/features/tab_ui/javatests/src/org/chromium/chrome/browser/tasks/tab_management/TabUiTestHelper.java
index 234121f..34d14c87 100644
--- a/chrome/android/features/tab_ui/javatests/src/org/chromium/chrome/browser/tasks/tab_management/TabUiTestHelper.java
+++ b/chrome/android/features/tab_ui/javatests/src/org/chromium/chrome/browser/tasks/tab_management/TabUiTestHelper.java
@@ -589,7 +589,7 @@
                 // Some items may not be cards or may not have thumbnails.
                 if (thumbnail == null) continue;
 
-                if (thumbnail.isPlaceHolder()
+                if (thumbnail.isPlaceholder()
                         || !(thumbnail.getDrawable() instanceof BitmapDrawable)
                         || ((BitmapDrawable) thumbnail.getDrawable()).getBitmap() == null) {
                     allFetched = false;
diff --git a/chrome/android/features/tab_ui/junit/src/org/chromium/chrome/browser/tasks/tab_management/TabGridViewBinderUnitTest.java b/chrome/android/features/tab_ui/junit/src/org/chromium/chrome/browser/tasks/tab_management/TabGridViewBinderUnitTest.java
index 4ffa4212..9b4bbfd4 100644
--- a/chrome/android/features/tab_ui/junit/src/org/chromium/chrome/browser/tasks/tab_management/TabGridViewBinderUnitTest.java
+++ b/chrome/android/features/tab_ui/junit/src/org/chromium/chrome/browser/tasks/tab_management/TabGridViewBinderUnitTest.java
@@ -119,7 +119,7 @@
         TabGridViewBinder.bindClosableTab(mModel, mViewGroup, TabProperties.GRID_CARD_SIZE);
 
         verify(mViewGroup).setMinimumWidth(updatedCardWidth);
-        verify(mThumbnailView).setColorThumbnailPlaceHolder(false, true);
+        verify(mThumbnailView).updateThumbnailPlaceholder(false, true);
         verify(mThumbnailView).setImageDrawable(null);
         assertThat(mLayoutParams.width, equalTo(updatedCardWidth));
 
@@ -130,7 +130,6 @@
         verify(mThumbnailView).setImageBitmap(mBitmap);
         ArgumentCaptor<Matrix> matrixCaptor = ArgumentCaptor.forClass(Matrix.class);
         verify(mThumbnailView).setImageMatrix(matrixCaptor.capture());
-        verify(mThumbnailView).getLayoutParams();
         verifyNoMoreInteractions(mThumbnailView);
 
         // Verify metrics scale + translate.
@@ -153,7 +152,7 @@
         TabGridViewBinder.bindClosableTab(mModel, mViewGroup, TabProperties.GRID_CARD_SIZE);
 
         verify(mViewGroup).setMinimumWidth(updatedCardWidth);
-        verify(mThumbnailView).setColorThumbnailPlaceHolder(false, false);
+        verify(mThumbnailView).updateThumbnailPlaceholder(false, false);
         verify(mThumbnailView).setImageDrawable(null);
         assertThat(mLayoutParams.width, equalTo(updatedCardWidth));
 
@@ -164,7 +163,6 @@
         verify(mThumbnailView).setImageBitmap(mBitmap);
         ArgumentCaptor<Matrix> matrixCaptor = ArgumentCaptor.forClass(Matrix.class);
         verify(mThumbnailView).setImageMatrix(matrixCaptor.capture());
-        verify(mThumbnailView).getLayoutParams();
         verifyNoMoreInteractions(mThumbnailView);
 
         // Verify metrics scale + translate.
@@ -189,7 +187,7 @@
 
         // Verify.
         verify(mViewGroup).setMinimumWidth(updatedCardWidth);
-        verify(mThumbnailView).setColorThumbnailPlaceHolder(false, true);
+        verify(mThumbnailView).updateThumbnailPlaceholder(false, true);
         verify(mThumbnailView).setImageDrawable(null);
         assertThat(mLayoutParams.width, equalTo(updatedCardWidth));
         verify(mFetcher).fetch(mCallbackCaptor.capture(), any(), eq(true));
@@ -201,7 +199,6 @@
         verify(mThumbnailView).setImageBitmap(mBitmap);
         ArgumentCaptor<Matrix> matrixCaptor = ArgumentCaptor.forClass(Matrix.class);
         verify(mThumbnailView).setImageMatrix(matrixCaptor.capture());
-        verify(mThumbnailView).getLayoutParams();
         verifyNoMoreInteractions(mThumbnailView);
 
         // Verify metrics scale + translate.
@@ -225,7 +222,7 @@
 
         // Verify.
         verify(mViewGroup).setMinimumHeight(updatedCardHeight);
-        verify(mThumbnailView).setColorThumbnailPlaceHolder(false, true);
+        verify(mThumbnailView).updateThumbnailPlaceholder(false, true);
         verify(mThumbnailView).setImageDrawable(null);
         assertThat(mLayoutParams.height, equalTo(updatedCardHeight));
         verify(mFetcher).fetch(mCallbackCaptor.capture(), any(), eq(true));
@@ -237,7 +234,6 @@
         verify(mThumbnailView).setImageBitmap(mBitmap);
         ArgumentCaptor<Matrix> matrixCaptor = ArgumentCaptor.forClass(Matrix.class);
         verify(mThumbnailView).setImageMatrix(matrixCaptor.capture());
-        verify(mThumbnailView).getLayoutParams();
         verifyNoMoreInteractions(mThumbnailView);
 
         // Verify metrics scale + translate.
diff --git a/chrome/android/features/tab_ui/junit/src/org/chromium/chrome/browser/tasks/tab_management/TabListMediatorUnitTest.java b/chrome/android/features/tab_ui/junit/src/org/chromium/chrome/browser/tasks/tab_management/TabListMediatorUnitTest.java
index caaa7050..a7a7c611 100644
--- a/chrome/android/features/tab_ui/junit/src/org/chromium/chrome/browser/tasks/tab_management/TabListMediatorUnitTest.java
+++ b/chrome/android/features/tab_ui/junit/src/org/chromium/chrome/browser/tasks/tab_management/TabListMediatorUnitTest.java
@@ -319,8 +319,8 @@
     private TabListModel mModel;
     private SimpleRecyclerViewAdapter.ViewHolder mViewHolder1;
     private SimpleRecyclerViewAdapter.ViewHolder mViewHolder2;
-    private RecyclerView.ViewHolder mDummyViewHolder1;
-    private RecyclerView.ViewHolder mDummyViewHolder2;
+    private RecyclerView.ViewHolder mFakeViewHolder1;
+    private RecyclerView.ViewHolder mFakeViewHolder2;
     private View mItemView1 = mock(View.class);
     private View mItemView2 = mock(View.class);
     private TabModelObserver mMediatorTabModelObserver;
@@ -356,8 +356,8 @@
         mTab2 = prepareTab(TAB2_ID, TAB2_TITLE, TAB2_URL);
         mViewHolder1 = prepareViewHolder(TAB1_ID, POSITION1);
         mViewHolder2 = prepareViewHolder(TAB2_ID, POSITION2);
-        mDummyViewHolder1 = prepareDummyViewHolder(mItemView1, POSITION1);
-        mDummyViewHolder2 = prepareDummyViewHolder(mItemView2, POSITION2);
+        mFakeViewHolder1 = prepareFakeViewHolder(mItemView1, POSITION1);
+        mFakeViewHolder2 = prepareFakeViewHolder(mItemView2, POSITION2);
         List<Tab> tabs1 = new ArrayList<>(Arrays.asList(mTab1));
         List<Tab> tabs2 = new ArrayList<>(Arrays.asList(mTab2));
 
@@ -712,8 +712,8 @@
 
     @Test
     @Features.EnableFeatures(TAB_GROUPS_CONTINUATION_ANDROID)
-    public void updatesFaviconFetcher_TabGroup_GTS() {
-        setUpForTabGroupOperation(TabListMediatorType.TAB_SWITCHER, TabListMode.GRID);
+    public void updatesFaviconFetcher_TabGroup_ListGTS() {
+        setUpForTabGroupOperation(TabListMediatorType.TAB_SWITCHER, TabListMode.LIST);
 
         assertNotNull(mModel.get(0).model.get(TabProperties.FAVICON_FETCHER));
         mModel.get(0).model.set(TabProperties.FAVICON_FETCHER, null);
@@ -890,13 +890,13 @@
         itemTouchHelperCallback.setActionsOnAllRelatedTabsForTesting(true);
         itemTouchHelperCallback.setHoveredTabIndexForTesting(POSITION1);
         itemTouchHelperCallback.setSelectedTabIndexForTesting(POSITION2);
-        itemTouchHelperCallback.getMovementFlags(mRecyclerView, mDummyViewHolder1);
+        itemTouchHelperCallback.getMovementFlags(mRecyclerView, mFakeViewHolder1);
 
         doReturn(mAdapter).when(mRecyclerView).getAdapter();
 
         // Simulate the drop action.
         itemTouchHelperCallback.onSelectedChanged(
-                mDummyViewHolder2, ItemTouchHelper.ACTION_STATE_IDLE);
+                mFakeViewHolder2, ItemTouchHelper.ACTION_STATE_IDLE);
 
         verify(mTabGroupModelFilter).mergeTabsToGroup(eq(TAB2_ID), eq(TAB1_ID));
         verify(mGridLayoutManager).removeView(mItemView2);
@@ -915,8 +915,8 @@
         SimpleRecyclerViewAdapter.ViewHolder viewHolder4 = prepareViewHolder(TAB4_ID, POSITION2);
         View itemView3 = mock(View.class);
         View itemView4 = mock(View.class);
-        RecyclerView.ViewHolder dummyViewHolder3 = prepareDummyViewHolder(itemView3, 2);
-        RecyclerView.ViewHolder dummyViewHolder4 = prepareDummyViewHolder(itemView4, 3);
+        RecyclerView.ViewHolder fakeViewHolder3 = prepareFakeViewHolder(itemView3, 2);
+        RecyclerView.ViewHolder fakeViewHolder4 = prepareFakeViewHolder(itemView4, 3);
 
         List<Tab> tabs = new ArrayList<>(Arrays.asList(mTab1, mTab2, tab3, tab4));
         mMediator.resetWithListOfTabs(PseudoTab.getListOfPseudoTab(tabs), false, false);
@@ -927,12 +927,12 @@
         itemTouchHelperCallback.setActionsOnAllRelatedTabsForTesting(true);
         itemTouchHelperCallback.setHoveredTabIndexForTesting(POSITION1);
         itemTouchHelperCallback.setSelectedTabIndexForTesting(POSITION2);
-        itemTouchHelperCallback.getMovementFlags(mRecyclerView, mDummyViewHolder1);
+        itemTouchHelperCallback.getMovementFlags(mRecyclerView, mFakeViewHolder1);
 
         doReturn(mAdapter).when(mRecyclerView).getAdapter();
 
         itemTouchHelperCallback.onSelectedChanged(
-                mDummyViewHolder2, ItemTouchHelper.ACTION_STATE_IDLE);
+                mFakeViewHolder2, ItemTouchHelper.ACTION_STATE_IDLE);
 
         verify(mTabGroupModelFilter).mergeTabsToGroup(eq(TAB2_ID), eq(TAB1_ID));
         verify(mGridLayoutManager).removeView(mItemView2);
@@ -945,19 +945,19 @@
         mTabGroupModelFilterObserverCaptor.getValue().didMergeTabToGroup(mTab2, TAB1_ID);
 
         assertThat(mModel.size(), equalTo(3));
-        mDummyViewHolder1 = prepareDummyViewHolder(mItemView1, 0);
-        dummyViewHolder3 = prepareDummyViewHolder(itemView3, 1);
-        dummyViewHolder4 = prepareDummyViewHolder(itemView4, 2);
+        mFakeViewHolder1 = prepareFakeViewHolder(mItemView1, 0);
+        fakeViewHolder3 = prepareFakeViewHolder(itemView3, 1);
+        fakeViewHolder4 = prepareFakeViewHolder(itemView4, 2);
 
         // Merge 4 to 3.
         when(mTabGroupModelFilter.getTabAt(1)).thenReturn(tab3);
         when(mTabGroupModelFilter.getTabAt(2)).thenReturn(tab4);
         itemTouchHelperCallback.setHoveredTabIndexForTesting(1);
         itemTouchHelperCallback.setSelectedTabIndexForTesting(2);
-        itemTouchHelperCallback.getMovementFlags(mRecyclerView, dummyViewHolder3);
+        itemTouchHelperCallback.getMovementFlags(mRecyclerView, fakeViewHolder3);
 
         itemTouchHelperCallback.onSelectedChanged(
-                dummyViewHolder4, ItemTouchHelper.ACTION_STATE_IDLE);
+                fakeViewHolder4, ItemTouchHelper.ACTION_STATE_IDLE);
 
         verify(mTabGroupModelFilter).mergeTabsToGroup(eq(TAB4_ID), eq(TAB3_ID));
         verify(mGridLayoutManager).removeView(itemView4);
@@ -969,18 +969,18 @@
         mTabGroupModelFilterObserverCaptor.getValue().didMergeTabToGroup(tab4, TAB3_ID);
 
         assertThat(mModel.size(), equalTo(2));
-        mDummyViewHolder1 = prepareDummyViewHolder(mItemView1, 0);
-        dummyViewHolder3 = prepareDummyViewHolder(itemView3, 1);
+        mFakeViewHolder1 = prepareFakeViewHolder(mItemView1, 0);
+        fakeViewHolder3 = prepareFakeViewHolder(itemView3, 1);
 
         // Merge 3 to 1.
         when(mTabGroupModelFilter.getTabAt(0)).thenReturn(mTab1);
         when(mTabGroupModelFilter.getTabAt(1)).thenReturn(tab3);
         itemTouchHelperCallback.setHoveredTabIndexForTesting(0);
         itemTouchHelperCallback.setSelectedTabIndexForTesting(1);
-        itemTouchHelperCallback.getMovementFlags(mRecyclerView, mDummyViewHolder1);
+        itemTouchHelperCallback.getMovementFlags(mRecyclerView, mFakeViewHolder1);
 
         itemTouchHelperCallback.onSelectedChanged(
-                dummyViewHolder3, ItemTouchHelper.ACTION_STATE_IDLE);
+                fakeViewHolder3, ItemTouchHelper.ACTION_STATE_IDLE);
 
         verify(mTabGroupModelFilter).mergeTabsToGroup(eq(TAB3_ID), eq(TAB1_ID));
         verify(mGridLayoutManager).removeView(itemView3);
@@ -1002,13 +1002,13 @@
         itemTouchHelperCallback.setActionsOnAllRelatedTabsForTesting(true);
         itemTouchHelperCallback.setHoveredTabIndexForTesting(POSITION1);
         itemTouchHelperCallback.setSelectedTabIndexForTesting(POSITION2);
-        itemTouchHelperCallback.getMovementFlags(mRecyclerView, mDummyViewHolder1);
+        itemTouchHelperCallback.getMovementFlags(mRecyclerView, mFakeViewHolder1);
 
         doReturn(mAdapter).when(mRecyclerView).getAdapter();
 
         // Simulate the drop action.
         itemTouchHelperCallback.onSelectedChanged(
-                mDummyViewHolder1, ItemTouchHelper.ACTION_STATE_IDLE);
+                mFakeViewHolder1, ItemTouchHelper.ACTION_STATE_IDLE);
 
         verify(mGridLayoutManager, never()).removeView(any(View.class));
     }
@@ -1020,14 +1020,14 @@
         TabGridItemTouchHelperCallback itemTouchHelperCallback = getItemTouchHelperCallback();
         itemTouchHelperCallback.setActionsOnAllRelatedTabsForTesting(false);
         itemTouchHelperCallback.setUnGroupTabIndexForTesting(POSITION1);
-        itemTouchHelperCallback.getMovementFlags(mRecyclerView, mDummyViewHolder1);
+        itemTouchHelperCallback.getMovementFlags(mRecyclerView, mFakeViewHolder1);
 
         doReturn(mAdapter).when(mRecyclerView).getAdapter();
         doReturn(1).when(mAdapter).getItemCount();
 
         // Simulate the ungroup action.
         itemTouchHelperCallback.onSelectedChanged(
-                mDummyViewHolder1, ItemTouchHelper.ACTION_STATE_IDLE);
+                mFakeViewHolder1, ItemTouchHelper.ACTION_STATE_IDLE);
 
         verify(mTabGroupModelFilter).moveTabOutOfGroup(eq(TAB1_ID));
         verify(mGridLayoutManager).removeView(mItemView1);
@@ -3217,7 +3217,7 @@
     @Test
     @Features.EnableFeatures({TAB_GROUPS_CONTINUATION_ANDROID})
     public void testUpdateFaviconFetcherForGroup() {
-        setUpForTabGroupOperation(TabListMediatorType.TAB_SWITCHER, TabListMode.GRID);
+        setUpForTabGroupOperation(TabListMediatorType.TAB_SWITCHER, TabListMode.LIST);
         mModel.get(0).model.set(TabProperties.FAVICON, null);
         mModel.get(0).model.set(TabProperties.FAVICON_FETCHER, null);
 
@@ -3248,7 +3248,7 @@
     @Test
     @Features.EnableFeatures({TAB_GROUPS_CONTINUATION_ANDROID})
     public void testUpdateFaviconFetcherForGroup_StaleIndex_SelectAnotherTabWithinGroup() {
-        setUpForTabGroupOperation(TabListMediatorType.TAB_SWITCHER, TabListMode.GRID);
+        setUpForTabGroupOperation(TabListMediatorType.TAB_SWITCHER, TabListMode.LIST);
         mMediator.setActionOnAllRelatedTabsForTesting(true);
         assertNull(mModel.get(0).model.get(TabProperties.FAVICON));
         assertNull(mModel.get(1).model.get(TabProperties.FAVICON));
@@ -3276,7 +3276,7 @@
     @Test
     @Features.EnableFeatures({TAB_GROUPS_CONTINUATION_ANDROID})
     public void testUpdateFaviconFetcherForGroup_StaleIndex_CloseTab() {
-        setUpForTabGroupOperation(TabListMediatorType.TAB_SWITCHER, TabListMode.GRID);
+        setUpForTabGroupOperation(TabListMediatorType.TAB_SWITCHER, TabListMode.LIST);
         mMediator.setActionOnAllRelatedTabsForTesting(true);
         assertNull(mModel.get(0).model.get(TabProperties.FAVICON));
         assertNull(mModel.get(1).model.get(TabProperties.FAVICON));
@@ -3303,7 +3303,7 @@
     @Test
     @Features.EnableFeatures({TAB_GROUPS_CONTINUATION_ANDROID})
     public void testUpdateFaviconFetcherForGroup_StaleIndex_Reset() {
-        setUpForTabGroupOperation(TabListMediatorType.TAB_SWITCHER, TabListMode.GRID);
+        setUpForTabGroupOperation(TabListMediatorType.TAB_SWITCHER, TabListMode.LIST);
         assertNull(mModel.get(0).model.get(TabProperties.FAVICON));
         assertNull(mModel.get(1).model.get(TabProperties.FAVICON));
         mModel.get(0).model.set(TabProperties.FAVICON_FETCHER, null);
@@ -3704,7 +3704,7 @@
         return viewHolder;
     }
 
-    private RecyclerView.ViewHolder prepareDummyViewHolder(View itemView, int index) {
+    private RecyclerView.ViewHolder prepareFakeViewHolder(View itemView, int index) {
         RecyclerView.ViewHolder viewHolder = new RecyclerView.ViewHolder(itemView) {};
         when(mRecyclerView.findViewHolderForAdapterPosition(index)).thenReturn(viewHolder);
         return viewHolder;
diff --git a/chrome/android/features/tab_ui/tab_management_java_sources.gni b/chrome/android/features/tab_ui/tab_management_java_sources.gni
index f9c89c77..314cd071 100644
--- a/chrome/android/features/tab_ui/tab_management_java_sources.gni
+++ b/chrome/android/features/tab_ui/tab_management_java_sources.gni
@@ -121,6 +121,7 @@
   "//chrome/android/features/tab_ui/javatests/src/org/chromium/chrome/browser/tasks/tab_management/TabGridDialogTest.java",
   "//chrome/android/features/tab_ui/javatests/src/org/chromium/chrome/browser/tasks/tab_management/TabGridIncognitoReauthPromoTest.java",
   "//chrome/android/features/tab_ui/javatests/src/org/chromium/chrome/browser/tasks/tab_management/TabGridIphTest.java",
+  "//chrome/android/features/tab_ui/javatests/src/org/chromium/chrome/browser/tasks/tab_management/TabGridThumbnailViewRenderTest.java",
   "//chrome/android/features/tab_ui/javatests/src/org/chromium/chrome/browser/tasks/tab_management/TabGroupUiTest.java",
   "//chrome/android/features/tab_ui/javatests/src/org/chromium/chrome/browser/tasks/tab_management/TabListContainerViewBinderTest.java",
   "//chrome/android/features/tab_ui/javatests/src/org/chromium/chrome/browser/tasks/tab_management/TabSelectionEditorMenuTest.java",
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/bookmarks/ImprovedBookmarkFolderView.java b/chrome/android/java/src/org/chromium/chrome/browser/bookmarks/ImprovedBookmarkFolderView.java
index fc83303..92989117 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/bookmarks/ImprovedBookmarkFolderView.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/bookmarks/ImprovedBookmarkFolderView.java
@@ -6,6 +6,7 @@
 
 import android.content.Context;
 import android.content.res.ColorStateList;
+import android.content.res.Resources;
 import android.graphics.drawable.Drawable;
 import android.util.AttributeSet;
 import android.view.LayoutInflater;
@@ -23,9 +24,10 @@
 import org.chromium.components.browser_ui.widget.RoundedCornerOutlineProvider;
 
 /**
- * Common logic for improved bookmark and folder rows.
+ * Draws the image at the start of a bookmark folder row. This may contains elements from the
+ * folder's children bookmarks, such as thumbnail or count.
  */
-// TODO(crbug.com/1448907): Create coordinator for this view.
+// TODO(https://crbug.com/1448907): Create coordinator for this view.
 public class ImprovedBookmarkFolderView extends FrameLayout {
     private final RoundedCornerOutlineProvider mPrimaryImageOutline;
     private final RoundedCornerOutlineProvider mSecondaryImageOutline;
@@ -50,33 +52,27 @@
     public ImprovedBookmarkFolderView(Context context, AttributeSet attrs) {
         super(context, attrs);
 
-        mPrimaryImageOutline =
-                new RoundedCornerOutlineProvider(context.getResources().getDimensionPixelSize(
-                        R.dimen.improved_bookmark_row_outer_corner_radius));
+        Resources resources = context.getResources();
+        int outerRadius =
+                resources.getDimensionPixelSize(R.dimen.improved_bookmark_row_outer_corner_radius);
+        int innerRadius =
+                resources.getDimensionPixelSize(R.dimen.improved_bookmark_row_inner_corner_radius);
 
-        mSecondaryImageOutline =
-                new RoundedCornerOutlineProvider(context.getResources().getDimensionPixelSize(
-                        R.dimen.improved_bookmark_row_outer_corner_radius));
+        mPrimaryImageOutline = new RoundedCornerOutlineProvider(outerRadius);
+
+        mSecondaryImageOutline = new RoundedCornerOutlineProvider(outerRadius);
         mSecondaryImageOutline.setRoundingEdges(false, true, true, false);
 
-        mChildTextBackgroundOutlineOneImageTop =
-                new RoundedCornerOutlineProvider(context.getResources().getDimensionPixelSize(
-                        R.dimen.improved_bookmark_row_inner_corner_radius));
+        mChildTextBackgroundOutlineOneImageTop = new RoundedCornerOutlineProvider(innerRadius);
         mChildTextBackgroundOutlineOneImageTop.setRoundingEdges(true, true, false, false);
 
-        mChildTextBackgroundOutlineOneImageBot =
-                new RoundedCornerOutlineProvider(context.getResources().getDimensionPixelSize(
-                        R.dimen.improved_bookmark_row_outer_corner_radius));
+        mChildTextBackgroundOutlineOneImageBot = new RoundedCornerOutlineProvider(outerRadius);
         mChildTextBackgroundOutlineOneImageBot.setRoundingEdges(false, false, true, true);
 
-        mChildTextContainerOutlineOneImage =
-                new RoundedCornerOutlineProvider(context.getResources().getDimensionPixelSize(
-                        R.dimen.improved_bookmark_row_inner_corner_radius));
+        mChildTextContainerOutlineOneImage = new RoundedCornerOutlineProvider(innerRadius);
         mChildTextContainerOutlineOneImage.setRoundingEdges(true, true, false, false);
 
-        mChildTextContainerOutlineTwoImages =
-                new RoundedCornerOutlineProvider(context.getResources().getDimensionPixelSize(
-                        R.dimen.improved_bookmark_row_outer_corner_radius));
+        mChildTextContainerOutlineTwoImages = new RoundedCornerOutlineProvider(outerRadius);
         mChildTextContainerOutlineTwoImages.setRoundingEdges(false, false, true, true);
     }
 
@@ -84,8 +80,14 @@
     protected void onFinishInflate() {
         super.onFinishInflate();
 
-        LayoutInflater.from(getContext())
-                .inflate(R.layout.improved_bookmark_row_folder_view_layout, this);
+        Context context = getContext();
+        final @ColorInt int surface0 =
+                ChromeColors.getSurfaceColor(context, R.dimen.default_elevation_0);
+        final @ColorInt int surface1 =
+                ChromeColors.getSurfaceColor(context, R.dimen.default_elevation_1);
+
+        LayoutInflater.from(context).inflate(
+                R.layout.improved_bookmark_row_folder_view_layout, this);
 
         mPrimaryImage = findViewById(R.id.primary_image);
         mPrimaryImage.setOutlineProvider(mPrimaryImageOutline);
@@ -102,35 +104,30 @@
         mSecondaryImage.setClipToOutline(true);
 
         mSecondaryImageContainer = findViewById(R.id.secondary_image_container);
-        mSecondaryImageContainer.setBackgroundColor(
-                ChromeColors.getSurfaceColor(getContext(), R.dimen.default_elevation_0));
+        mSecondaryImageContainer.setBackgroundColor(surface0);
 
         // Setup the background for the child count view when there's one image present.
         mChildCountBackgroundOneImage = findViewById(R.id.child_count_background_one_image);
         View childCountBackgroundOneImageTop =
                 findViewById(R.id.child_count_background_one_image_top);
-        childCountBackgroundOneImageTop.setBackgroundColor(
-                ChromeColors.getSurfaceColor(getContext(), R.dimen.default_elevation_1));
+        childCountBackgroundOneImageTop.setBackgroundColor(surface1);
         childCountBackgroundOneImageTop.setOutlineProvider(mChildTextBackgroundOutlineOneImageTop);
         childCountBackgroundOneImageTop.setClipToOutline(true);
         View childCountBackgroundOneImageBot =
                 findViewById(R.id.child_count_background_one_image_bot);
-        childCountBackgroundOneImageBot.setBackgroundColor(
-                ChromeColors.getSurfaceColor(getContext(), R.dimen.default_elevation_1));
+        childCountBackgroundOneImageBot.setBackgroundColor(surface1);
         childCountBackgroundOneImageBot.setOutlineProvider(mChildTextBackgroundOutlineOneImageBot);
         childCountBackgroundOneImageBot.setClipToOutline(true);
 
         // Setup the background for the child count view when there's two images present.
         mChildCountBackgroundTwoImages = findViewById(R.id.child_count_background_two_images);
-        mChildCountBackgroundTwoImages.setBackgroundColor(
-                ChromeColors.getSurfaceColor(getContext(), R.dimen.default_elevation_1));
+        mChildCountBackgroundTwoImages.setBackgroundColor(surface1);
         mChildCountBackgroundTwoImages.setOutlineProvider(mChildTextContainerOutlineTwoImages);
         mChildCountBackgroundTwoImages.setClipToOutline(true);
 
         // The container which separates the child text from the images.
         mChildCountContainer = findViewById(R.id.child_count_container);
-        mChildCountContainer.setBackgroundColor(
-                ChromeColors.getSurfaceColor(getContext(), R.dimen.default_elevation_0));
+        mChildCountContainer.setBackgroundColor(surface0);
         mChildCountContainer.setClipToOutline(true);
 
         mChildCount = findViewById(R.id.child_count_text);
@@ -158,7 +155,7 @@
         mChildCountContainer.setVisibility(View.GONE);
 
         if (primaryDrawable == null && secondaryDrawable == null) {
-            // Placehodler folder image case.
+            // Placeholder folder image case.
             mNoImagePlaceholder.setVisibility(View.VISIBLE);
         } else if (primaryDrawable != null && secondaryDrawable == null) {
             // 1-image case.
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/bookmarks/ImprovedBookmarkFolderRowRenderTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/bookmarks/ImprovedBookmarkFolderRowRenderTest.java
index 62e4c2b..4d45da4 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/bookmarks/ImprovedBookmarkFolderRowRenderTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/bookmarks/ImprovedBookmarkFolderRowRenderTest.java
@@ -21,7 +21,6 @@
 import org.junit.Test;
 import org.junit.rules.TestRule;
 import org.junit.runner.RunWith;
-import org.mockito.Mock;
 import org.mockito.junit.MockitoJUnit;
 import org.mockito.junit.MockitoRule;
 
@@ -43,7 +42,6 @@
 import org.chromium.components.bookmarks.BookmarkType;
 import org.chromium.components.browser_ui.styles.ChromeColors;
 import org.chromium.components.browser_ui.styles.SemanticColorUtils;
-import org.chromium.components.payments.CurrencyFormatter;
 import org.chromium.content_public.browser.test.util.TestThreadUtils;
 import org.chromium.ui.modelutil.PropertyModel;
 import org.chromium.ui.modelutil.PropertyModelChangeProcessor;
@@ -56,7 +54,8 @@
 import java.util.List;
 
 /**
- * Render tests for the improved bookmark row.
+ * Render tests for {@link ImprovedBookmarkRow} when the row represents a bookmark folder and the
+ * start/image is a {@link ImprovedBookmarkFolderView}.
  */
 @RunWith(ParameterizedRunner.class)
 @UseRunnerDelegate(ChromeJUnit4RunnerDelegate.class)
@@ -68,26 +67,19 @@
 
     @Rule
     public final DisableAnimationsTestRule mDisableAnimationsRule = new DisableAnimationsTestRule();
-
     @Rule
     public MockitoRule mMockitoRule = MockitoJUnit.rule();
-
     @Rule
     public BaseActivityTestRule<BlankUiTestActivity> mActivityTestRule =
             new BaseActivityTestRule<>(BlankUiTestActivity.class);
-
     @Rule
     public ChromeRenderTestRule mRenderTestRule =
             ChromeRenderTestRule.Builder.withPublicCorpus()
                     .setBugComponent(ChromeRenderTestRule.Component.UI_BROWSER_BOOKMARKS)
                     .build();
-
     @Rule
     public TestRule mProcessor = new Features.JUnitProcessor();
 
-    @Mock
-    CurrencyFormatter mFormatter;
-
     private ImprovedBookmarkRow mView;
     private PropertyModel mModel;
     private Bitmap mPrimaryBitmap;
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/bookmarks/ImprovedBookmarkRowRenderTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/bookmarks/ImprovedBookmarkRowRenderTest.java
index cd482f4..3d111c3 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/bookmarks/ImprovedBookmarkRowRenderTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/bookmarks/ImprovedBookmarkRowRenderTest.java
@@ -60,9 +60,7 @@
 import java.util.Arrays;
 import java.util.List;
 
-/**
- * Render tests for the improved bookmark row.
- */
+/** Render tests for {@link ImprovedBookmarkRow} when it does not represent a folder. */
 @RunWith(ParameterizedRunner.class)
 @ParameterAnnotations.UseRunnerDelegate(ChromeJUnit4RunnerDelegate.class)
 @Batch(Batch.PER_CLASS)
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/browserservices/TrustedWebActivityTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/browserservices/TrustedWebActivityTest.java
index edd51dba..686406e7 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/browserservices/TrustedWebActivityTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/browserservices/TrustedWebActivityTest.java
@@ -33,11 +33,11 @@
 import org.chromium.base.ContextUtils;
 import org.chromium.base.library_loader.LibraryLoader;
 import org.chromium.base.metrics.RecordHistogram;
-import org.chromium.base.test.util.Batch;
 import org.chromium.base.test.util.CommandLineFlags;
 import org.chromium.base.test.util.Criteria;
 import org.chromium.base.test.util.CriteriaHelper;
 import org.chromium.base.test.util.DisabledTest;
+import org.chromium.base.test.util.DoNotBatch;
 import org.chromium.base.test.util.Feature;
 import org.chromium.base.test.util.Restriction;
 import org.chromium.cc.input.BrowserControlsState;
@@ -65,7 +65,7 @@
  */
 @RunWith(ChromeJUnit4ClassRunner.class)
 @CommandLineFlags.Add({ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE})
-@Batch(Batch.PER_CLASS)
+@DoNotBatch(reason = "https://crbug.com/1454648")
 public class TrustedWebActivityTest {
     // TODO(peconn): Add test for navigating away from the trusted origin.
     public CustomTabActivityTestRule mCustomTabActivityTestRule = new CustomTabActivityTestRule();
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/externalnav/UrlOverridingTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/externalnav/UrlOverridingTest.java
index 9c168c2..e6afadb 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/externalnav/UrlOverridingTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/externalnav/UrlOverridingTest.java
@@ -18,6 +18,7 @@
 import android.content.IntentFilter;
 import android.content.pm.ActivityInfo;
 import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
 import android.net.Uri;
@@ -51,7 +52,6 @@
 import org.mockito.quality.Strictness;
 
 import org.chromium.base.ApiCompatibilityUtils;
-import org.chromium.base.BuildInfo;
 import org.chromium.base.Callback;
 import org.chromium.base.ContextUtils;
 import org.chromium.base.IntentUtils;
@@ -205,10 +205,6 @@
             "intent://test/#Intent;scheme=externalappscheme;end;";
 
     private static final String OTHER_BROWSER_PACKAGE = "com.other.browser";
-    // Needs to be a real package on the device so we can get an icon from it. It will not be
-    // launched.
-    private static final String NON_BROWSER_PACKAGE = "com.android.settings";
-    private static final String NON_BROWSER_PACKAGE_AUTO = "com.android.car.settings";
 
     private static final String EXTERNAL_APP_SCHEME = "externalappscheme";
 
@@ -360,9 +356,7 @@
     public void setUp() throws Exception {
         mActivityTestRule.getEmbeddedTestServerRule().setServerUsesHttps(true);
         mContextToRestore = ContextUtils.getApplicationContext();
-        mNonBrowserPackageName =
-            BuildInfo.getInstance().isAutomotive ? NON_BROWSER_PACKAGE_AUTO
-                : NON_BROWSER_PACKAGE;
+        mNonBrowserPackageName = getNonBrowserPackageName();
         mTestContext = new TestContext(mContextToRestore, mNonBrowserPackageName);
         ContextUtils.initApplicationContextForTests(mTestContext);
         IntentFilter filter = new IntentFilter(Intent.ACTION_VIEW);
@@ -653,6 +647,16 @@
                 + Base64.encodeToString(value, Base64.URL_SAFE);
     }
 
+    private String getNonBrowserPackageName() {
+        List<PackageInfo> packages =
+                ContextUtils.getApplicationContext().getPackageManager().getInstalledPackages(0);
+        if (packages == null || packages.size() == 0) {
+            return "";
+        }
+
+        return packages.get(0).packageName;
+    }
+
     @Test
     @SmallTest
     public void testNavigationFromTimer() {
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/signin/SigninSignoutIntegrationTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/signin/SigninSignoutIntegrationTest.java
index 3da53b9..476769cd 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/signin/SigninSignoutIntegrationTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/signin/SigninSignoutIntegrationTest.java
@@ -61,6 +61,7 @@
 import org.chromium.components.signin.identitymanager.ConsentLevel;
 import org.chromium.components.signin.metrics.SigninAccessPoint;
 import org.chromium.content_public.browser.test.util.TestThreadUtils;
+import org.chromium.ui.test.util.MockitoHelper;
 import org.chromium.url.GURL;
 
 /**
@@ -199,11 +200,11 @@
         onView(withText(R.string.sign_out_and_turn_off_sync)).perform(click());
         onView(withText(R.string.continue_button)).inRoot(isDialog()).perform(click());
         assertSignedOut();
-        verify(mSignInStateObserverMock).onSignedOut();
-        verify(mSigninMetricsUtilsNativeMock)
+        MockitoHelper.waitForEvent(mSignInStateObserverMock).onSignedOut();
+        MockitoHelper.waitForEvent(mSigninMetricsUtilsNativeMock)
                 .logProfileAccountManagementMenu(ProfileAccountManagementMetrics.TOGGLE_SIGNOUT,
                         GAIAServiceType.GAIA_SERVICE_TYPE_NONE);
-        verify(mSigninMetricsUtilsNativeMock)
+        MockitoHelper.waitForEvent(mSigninMetricsUtilsNativeMock)
                 .logProfileAccountManagementMenu(ProfileAccountManagementMetrics.SIGNOUT_SIGNOUT,
                         GAIAServiceType.GAIA_SERVICE_TYPE_NONE);
     }
@@ -328,7 +329,7 @@
 
     private void assertSignedOut() {
         TestThreadUtils.runOnUiThreadBlocking(() -> {
-            Assert.assertFalse("Account should be signed in!",
+            Assert.assertFalse("Account should be signed out!",
                     mSigninManager.getIdentityManager().hasPrimaryAccount(ConsentLevel.SIGNIN));
         });
     }
diff --git a/chrome/app/chrome_command_ids.h b/chrome/app/chrome_command_ids.h
index b8cc4be0..bb1aae9 100644
--- a/chrome/app/chrome_command_ids.h
+++ b/chrome/app/chrome_command_ids.h
@@ -167,6 +167,9 @@
 #define IDC_CUSTOMIZE_CHROME            37350
 #define IDC_CLOSE_PROFILE               35351
 #define IDC_MANAGE_GOOGLE_ACCOUNT       35352
+#define IDC_SHOW_SYNC_SETTINGS          35353
+#define IDC_TURN_ON_SYNC                35354
+#define IDC_SHOW_SIGNIN_WHEN_PAUSED     35355
 
 // Zoom
 #define IDC_ZOOM_MENU                   38000
diff --git a/chrome/app/chromeos_strings.grdp b/chrome/app/chromeos_strings.grdp
index 1f4cc87..2661ad8a 100644
--- a/chrome/app/chromeos_strings.grdp
+++ b/chrome/app/chromeos_strings.grdp
@@ -6815,4 +6815,34 @@
     Your Google Account is the same account you use for Gmail, YouTube, Chrome, and other Google products.
     Use your account to easily access all of your bookmarks, files, and more. If you don't have an account, you can create one on the next screen.
   </message>
+
+  <!-- Floating Workspace -->
+  <message name="IDS_FLOATING_WORKSPACE_NO_NETWORK_TITLE" desc="Title of Floating Workspace notification when there is no network connection.">
+    Can't resume session
+  </message>
+  <message name="IDS_FLOATING_WORKSPACE_NO_NETWORK_MESSAGE" desc="Message for Floating Workspace notification when is no network connection.">
+    ChromeOS can't resume your previous session due to a network problem. Connect to a stable network and retry.
+  </message>
+  <message name="IDS_FLOATING_WORKSPACE_NO_NETWORK_BUTTON" desc="Button to show network settings for Floating Workspace notification when is no network connection.">
+    Network settings
+  </message>
+  <message name="IDS_FLOATING_WORKSPACE_RESTORE_FROM_ERROR_TITLE" desc="Title of Floating Workspace notification asking user to restore or not when a new remote desk arrives after an earlier restoration error occurs.">
+    Resume previous session?
+  </message>
+  <message name="IDS_FLOATING_WORKSPACE_RESTORE_FROM_ERROR_MESSAGE" desc="Message of Floating Workspace notification asking user to restore or not when a new remote desk arrives after an earlier restoration error occurs.">
+    You can resume your previous session while your current session remains active.
+  </message>
+  <message name="IDS_FLOATING_WORKSPACE_RESTORE_FROM_ERROR_RESTORATION_BUTTON" desc="Button for restoration option of Floating Workspace notification asking user to restore or not when a new remote desk arrives after an earlier restoration error occurs.">
+    Resume
+  </message>
+  <message name="IDS_FLOATING_WORKSPACE_ERROR_TITLE" desc="Title for Floating Workspace notification when there is an sync service error or timeout.">
+    Can't resume previous session
+  </message>
+  <message name="IDS_FLOATING_WORKSPACE_ERROR_MESSAGE" desc="Message for Floating Workspace notification when there is an sync service error or timeout.">
+    Something went wrong. Open the windows manuallly instead.
+  </message>
+  <message name="IDS_FLOATING_WORKSPACE_DISPLAY_SOURCE" desc="This is the string shown as the display source of the notification to notify user about floating workspace status.">
+    ChromeOS
+  </message>
+
 </grit-part>
diff --git a/chrome/app/chromeos_strings_grdp/IDS_FLOATING_WORKSPACE_DISPLAY_SOURCE.png.sha1 b/chrome/app/chromeos_strings_grdp/IDS_FLOATING_WORKSPACE_DISPLAY_SOURCE.png.sha1
new file mode 100644
index 0000000..383d0b3b
--- /dev/null
+++ b/chrome/app/chromeos_strings_grdp/IDS_FLOATING_WORKSPACE_DISPLAY_SOURCE.png.sha1
@@ -0,0 +1 @@
+e1fdea549bfcbc09c370ca2128b3f3be26e20fa1
\ No newline at end of file
diff --git a/chrome/app/chromeos_strings_grdp/IDS_FLOATING_WORKSPACE_ERROR_MESSAGE.png.sha1 b/chrome/app/chromeos_strings_grdp/IDS_FLOATING_WORKSPACE_ERROR_MESSAGE.png.sha1
new file mode 100644
index 0000000..4a4f2dc
--- /dev/null
+++ b/chrome/app/chromeos_strings_grdp/IDS_FLOATING_WORKSPACE_ERROR_MESSAGE.png.sha1
@@ -0,0 +1 @@
+c32b2dffd252e5cc77949cc879012817300c546d
\ No newline at end of file
diff --git a/chrome/app/chromeos_strings_grdp/IDS_FLOATING_WORKSPACE_ERROR_TITLE.png.sha1 b/chrome/app/chromeos_strings_grdp/IDS_FLOATING_WORKSPACE_ERROR_TITLE.png.sha1
new file mode 100644
index 0000000..2de6529
--- /dev/null
+++ b/chrome/app/chromeos_strings_grdp/IDS_FLOATING_WORKSPACE_ERROR_TITLE.png.sha1
@@ -0,0 +1 @@
+2aef42f78a727184bac0fface197d124e57403e7
\ No newline at end of file
diff --git a/chrome/app/chromeos_strings_grdp/IDS_FLOATING_WORKSPACE_NO_NETWORK_BUTTON.png.sha1 b/chrome/app/chromeos_strings_grdp/IDS_FLOATING_WORKSPACE_NO_NETWORK_BUTTON.png.sha1
new file mode 100644
index 0000000..e7e8c002
--- /dev/null
+++ b/chrome/app/chromeos_strings_grdp/IDS_FLOATING_WORKSPACE_NO_NETWORK_BUTTON.png.sha1
@@ -0,0 +1 @@
+667a25e9b9e081f376dcb8cb76b794df2f604ffa
\ No newline at end of file
diff --git a/chrome/app/chromeos_strings_grdp/IDS_FLOATING_WORKSPACE_NO_NETWORK_MESSAGE.png.sha1 b/chrome/app/chromeos_strings_grdp/IDS_FLOATING_WORKSPACE_NO_NETWORK_MESSAGE.png.sha1
new file mode 100644
index 0000000..a74f2379
--- /dev/null
+++ b/chrome/app/chromeos_strings_grdp/IDS_FLOATING_WORKSPACE_NO_NETWORK_MESSAGE.png.sha1
@@ -0,0 +1 @@
+6741c921c2e6cdc1d2b4e0b53972077ea605abcd
\ No newline at end of file
diff --git a/chrome/app/chromeos_strings_grdp/IDS_FLOATING_WORKSPACE_NO_NETWORK_TITLE.png.sha1 b/chrome/app/chromeos_strings_grdp/IDS_FLOATING_WORKSPACE_NO_NETWORK_TITLE.png.sha1
new file mode 100644
index 0000000..5d8eb7e3
--- /dev/null
+++ b/chrome/app/chromeos_strings_grdp/IDS_FLOATING_WORKSPACE_NO_NETWORK_TITLE.png.sha1
@@ -0,0 +1 @@
+134b8089538fd6d07e55948760a32d9574035d00
\ No newline at end of file
diff --git a/chrome/app/chromeos_strings_grdp/IDS_FLOATING_WORKSPACE_RESTORE_FROM_ERROR_DISCARD_BUTTON.png.sha1 b/chrome/app/chromeos_strings_grdp/IDS_FLOATING_WORKSPACE_RESTORE_FROM_ERROR_DISCARD_BUTTON.png.sha1
new file mode 100644
index 0000000..5c8680a
--- /dev/null
+++ b/chrome/app/chromeos_strings_grdp/IDS_FLOATING_WORKSPACE_RESTORE_FROM_ERROR_DISCARD_BUTTON.png.sha1
@@ -0,0 +1 @@
+aa14b7ebede6cd6cc079523b24a1bb3d84221b12
\ No newline at end of file
diff --git a/chrome/app/chromeos_strings_grdp/IDS_FLOATING_WORKSPACE_RESTORE_FROM_ERROR_MESSAGE.png.sha1 b/chrome/app/chromeos_strings_grdp/IDS_FLOATING_WORKSPACE_RESTORE_FROM_ERROR_MESSAGE.png.sha1
new file mode 100644
index 0000000..30b8127
--- /dev/null
+++ b/chrome/app/chromeos_strings_grdp/IDS_FLOATING_WORKSPACE_RESTORE_FROM_ERROR_MESSAGE.png.sha1
@@ -0,0 +1 @@
+4feb04291389d670465163bc3b73b3e46abefd09
\ No newline at end of file
diff --git a/chrome/app/chromeos_strings_grdp/IDS_FLOATING_WORKSPACE_RESTORE_FROM_ERROR_RESTORATION_BUTTON.png.sha1 b/chrome/app/chromeos_strings_grdp/IDS_FLOATING_WORKSPACE_RESTORE_FROM_ERROR_RESTORATION_BUTTON.png.sha1
new file mode 100644
index 0000000..d1f22d5
--- /dev/null
+++ b/chrome/app/chromeos_strings_grdp/IDS_FLOATING_WORKSPACE_RESTORE_FROM_ERROR_RESTORATION_BUTTON.png.sha1
@@ -0,0 +1 @@
+26116458d3c79e2805ab53507482e6087223b613
\ No newline at end of file
diff --git a/chrome/app/chromeos_strings_grdp/IDS_FLOATING_WORKSPACE_RESTORE_FROM_ERROR_TITLE.png.sha1 b/chrome/app/chromeos_strings_grdp/IDS_FLOATING_WORKSPACE_RESTORE_FROM_ERROR_TITLE.png.sha1
new file mode 100644
index 0000000..7b883372
--- /dev/null
+++ b/chrome/app/chromeos_strings_grdp/IDS_FLOATING_WORKSPACE_RESTORE_FROM_ERROR_TITLE.png.sha1
@@ -0,0 +1 @@
+94785c5a2a4158d8d670c29fdc96266e24daa804
\ No newline at end of file
diff --git a/chrome/app/chromium_strings.grd b/chrome/app/chromium_strings.grd
index 3452464f..ac42082 100644
--- a/chrome/app/chromium_strings.grd
+++ b/chrome/app/chromium_strings.grd
@@ -1771,24 +1771,6 @@
           </message>
         </if>
       </if>
-
-      <!-- Payments settings DeviceAuthentication strings -->
-      <if expr="is_macosx">
-        <message name="IDS_PAYMENTS_AUTOFILL_MANDATORY_REAUTH_PROMPT" desc="Message to be displayed to the user in the device authentication prompt when they click on the mandatory auth toggle on the Payments settings page, irrespective to enable or disable it. When mandatory auth toggle is enabled, we would authenticate the user before autofilling in the payment details. Note: MacOS has the wording 'Chromium is trying to' prepended onto the message during user auth.">
-          modify settings for filling payment methods.
-        </message>
-        <message name="IDS_PAYMENTS_AUTOFILL_EDIT_CARD_MANDATORY_REAUTH_PROMPT" desc="Message to be displayed to the user in the device authentication prompt when they try to edit a local card on the Payments settings page if the mandatory auth toggle is enabled. If the user auth was succesful, we would then show the edit card dialog. Note: MacOS has the wording 'Chromium is trying to' prepended onto the message during user auth.">
-          edit payment methods.
-        </message>
-      </if>
-      <if expr="is_win">
-        <message name="IDS_PAYMENTS_AUTOFILL_MANDATORY_REAUTH_PROMPT" desc="Message to be displayed to the user in the device authentication prompt when they click on the mandatory auth toggle on the Payments settings page, irrespective to enable or disable it. When mandatory auth toggle is enabled, we would authenticate the user before autofilling in the payment details.">
-          Chromium is trying to modify settings for filling payment methods.
-        </message>
-        <message name="IDS_PAYMENTS_AUTOFILL_EDIT_CARD_MANDATORY_REAUTH_PROMPT" desc="Message to be displayed to the user in the device authentication prompt when they try to edit a local card on the Payments settings page if the mandatory auth toggle is enabled. If the user auth was succesful, we would then show the edit card dialog.">
-          Chromium is trying to edit payment methods.
-        </message>
-      </if>
     </messages>
   </release>
 </grit>
diff --git a/chrome/app/generated_resources.grd b/chrome/app/generated_resources.grd
index 2577f83..331e9b6 100644
--- a/chrome/app/generated_resources.grd
+++ b/chrome/app/generated_resources.grd
@@ -1168,6 +1168,21 @@
           <message name="IDS_NEW_INCOGNITO_WINDOW" desc="The text label of a menu item for opening a new Incognito window">
             New &amp;Incognito window
           </message>
+          <message name="IDS_PROFILE_ROW_SIGNED_IN_MESSAGE" desc="Label that let's the user know what email they are signed into their profile as.">
+            Signed in as <ph name="EMAIL">$1<ex>user@gmail.com</ex></ph>
+          </message>
+          <message name="IDS_PROFILE_ROW_SYNC_IS_ON" desc="The text label of a menu item that notifies the user that sync is on.">
+            &amp;Sync is on
+          </message>
+          <message name="IDS_PROFILE_ROW_TURN_ON_SYNC" desc="The text label of a menu item that prompts the user to turn on sync.">
+            Turn on &amp;sync...
+          </message>
+          <message name="IDS_PROFILE_ROW_SYNC_ERROR_MESSAGE" desc="The text label of a menu item that prompts the user to fix the profile sync issue.">
+            Fix &amp;sync issue
+          </message>
+           <message name="IDS_PROFILE_ROW_SIGN_IN_AGAIN" desc="The text label of a menu item that prompts the user to sign in again.">
+            &amp;Sign in again
+          </message>
           <message name="IDS_EDIT2" desc="The text label before the cut/copy/paste buttons in the merged menu">
             Edit
           </message>
@@ -1281,6 +1296,21 @@
           <message name="IDS_NEW_INCOGNITO_WINDOW" desc="In Title Case: The text label of a menu item for opening a new Incognito window">
             New &amp;Incognito Window
           </message>
+          <message name="IDS_PROFILE_ROW_SIGNED_IN_MESSAGE" desc="In Title Case: Label that let's the user know what email they are signed into their profile as.">
+            Signed in as <ph name="EMAIL">$1<ex>user@gmail.com</ex></ph>
+          </message>
+          <message name="IDS_PROFILE_ROW_SYNC_IS_ON" desc="In Title Case: The text label of a menu item that notifies the user that sync is on.">
+            &amp;Sync is On
+          </message>
+          <message name="IDS_PROFILE_ROW_TURN_ON_SYNC" desc="In Title Case: The text label of a menu item that prompts the user to turn on sync.">
+            Turn on &amp;Sync...
+          </message>
+          <message name="IDS_PROFILE_ROW_SYNC_ERROR_MESSAGE" desc="In Title Case: The text label of a menu item that prompts the user to fix the profile sync issue.">
+            Fix &amp;Sync Issue
+          </message>
+           <message name="IDS_PROFILE_ROW_SIGN_IN_AGAIN" desc="In Title Case: The text label of a menu item that prompts the user to sign in again.">
+            &amp;Sign in Again
+          </message>
           <message name="IDS_EDIT2" desc="In Title Case: The text label before the cut/copy/paste buttons in the merged menu">
             Edit
           </message>
@@ -11142,15 +11172,11 @@
         This profile is managed by <ph name="DOMAIN">$1<ex>example.com</ex></ph>. To continue using this managed profile, your organization requires you to share your device signals.
 
 Device signals can include information about your browser, OS, device, installed software, and files.
-
-If you choose not to share signals, this profile will be closed.
         </message>
         <message name="IDS_DEVICE_SIGNALS_CONSENT_DIALOG_DEFAULT_BODY_TEXT" desc="Default message for device signals consent dialog when we are unable to retrieve the name of the managing organization.">
         This profile is managed by your organization. To continue using this managed profile, your organization requires you to share your device signals.
 
 Device signals can include information about your browser, OS, device, installed software, and files.
-
-If you choose not to share signals, this profile will be closed.
         </message>
       </if>
 
@@ -13236,6 +13262,18 @@
       <message name="IDS_PAGE_LOADING_AX_TITLE_FORMAT" is_accessibility_with_no_ui="true" desc="Screen reader announcement when the back/forward button is focused and has been pressed. If the title of the page can be obtained, it will be included. Example: 'Loading Google News'.">
           Loading <ph name="WINDOW_TITLE">$1<ex>Google News</ex></ph>
       </message>
+      <message name="IDS_TAB_AX_INACTIVE_TAB" desc="Accessibility label text when the tab becomes inactive to save memory.'">
+        <ph name="WINDOW_TITLE">$1<ex>Google Hangouts</ex></ph> - Inactive tab
+      </message>
+      <message name="IDS_TAB_AX_MEMORY_SAVINGS" desc="Accessibility label text of how much memory was saved by the inactive tab'">
+        <ph name="WINDOW_TITLE">$1<ex>Google Hangouts</ex></ph> - Saved <ph name="MEMORY_VALUE">$2<ex>175MB</ex></ph>
+      </message>
+      <message name="IDS_TAB_AX_MEMORY_USAGE" desc="Accessibility label text of how much memory is used by the tab'">
+        <ph name="WINDOW_TITLE">$1<ex>Google Hangouts</ex></ph> - Memory usage - <ph name="MEMORY_VALUE">$2<ex>175MB</ex></ph>
+      </message>
+      <message name="IDS_TAB_AX_HIGH_MEMORY_USAGE" desc="Accessibility label text of how much memory is used by the tab'">
+        <ph name="WINDOW_TITLE">$1<ex>Google Hangouts</ex></ph> - High memory usage - <ph name="MEMORY_VALUE">$2<ex>175MB</ex></ph>
+      </message>
 
       <!-- ProcessSingleton -->
       <message name="IDS_PROFILE_IN_USE_LINUX_QUIT" desc="Text of button in profile in use dialog to quit without doing anything.">
diff --git a/chrome/app/generated_resources_grd/IDS_DEVICE_SIGNALS_CONSENT_DIALOG_BODY_TEXT.png.sha1 b/chrome/app/generated_resources_grd/IDS_DEVICE_SIGNALS_CONSENT_DIALOG_BODY_TEXT.png.sha1
index 6ed36ba..a707005 100644
--- a/chrome/app/generated_resources_grd/IDS_DEVICE_SIGNALS_CONSENT_DIALOG_BODY_TEXT.png.sha1
+++ b/chrome/app/generated_resources_grd/IDS_DEVICE_SIGNALS_CONSENT_DIALOG_BODY_TEXT.png.sha1
@@ -1 +1 @@
-c751bd92bc9c010308bc17bc89fc0d4d62013839
\ No newline at end of file
+c31e41fef1139e39f4c06d2dcee9d7c8a52d5793
\ No newline at end of file
diff --git a/chrome/app/generated_resources_grd/IDS_DEVICE_SIGNALS_CONSENT_DIALOG_DEFAULT_BODY_TEXT.png.sha1 b/chrome/app/generated_resources_grd/IDS_DEVICE_SIGNALS_CONSENT_DIALOG_DEFAULT_BODY_TEXT.png.sha1
index 42e4ed3b..2943ce1 100644
--- a/chrome/app/generated_resources_grd/IDS_DEVICE_SIGNALS_CONSENT_DIALOG_DEFAULT_BODY_TEXT.png.sha1
+++ b/chrome/app/generated_resources_grd/IDS_DEVICE_SIGNALS_CONSENT_DIALOG_DEFAULT_BODY_TEXT.png.sha1
@@ -1 +1 @@
-7b2ba0ea6b778d5a5f8f808443306bdb2ad48d7a
\ No newline at end of file
+806e9de0d8bca9c83670fcbe55879693538d6b1d
\ No newline at end of file
diff --git a/chrome/app/generated_resources_grd/IDS_PROFILE_ROW_SIGNED_IN_MESSAGE.png.sha1 b/chrome/app/generated_resources_grd/IDS_PROFILE_ROW_SIGNED_IN_MESSAGE.png.sha1
new file mode 100644
index 0000000..131b3c77
--- /dev/null
+++ b/chrome/app/generated_resources_grd/IDS_PROFILE_ROW_SIGNED_IN_MESSAGE.png.sha1
@@ -0,0 +1 @@
+f6eef2e8df5dfb9ec9b8c7a7cec9288101bd50da
\ No newline at end of file
diff --git a/chrome/app/generated_resources_grd/IDS_PROFILE_ROW_SIGN_IN_AGAIN.png.sha1 b/chrome/app/generated_resources_grd/IDS_PROFILE_ROW_SIGN_IN_AGAIN.png.sha1
new file mode 100644
index 0000000..f9f2ca559
--- /dev/null
+++ b/chrome/app/generated_resources_grd/IDS_PROFILE_ROW_SIGN_IN_AGAIN.png.sha1
@@ -0,0 +1 @@
+18d9423d3025a8e0e16f3e5784da652496d78db8
\ No newline at end of file
diff --git a/chrome/app/generated_resources_grd/IDS_PROFILE_ROW_SYNC_ERROR_MESSAGE.png.sha1 b/chrome/app/generated_resources_grd/IDS_PROFILE_ROW_SYNC_ERROR_MESSAGE.png.sha1
new file mode 100644
index 0000000..131b3c77
--- /dev/null
+++ b/chrome/app/generated_resources_grd/IDS_PROFILE_ROW_SYNC_ERROR_MESSAGE.png.sha1
@@ -0,0 +1 @@
+f6eef2e8df5dfb9ec9b8c7a7cec9288101bd50da
\ No newline at end of file
diff --git a/chrome/app/generated_resources_grd/IDS_PROFILE_ROW_SYNC_IS_ON.png.sha1 b/chrome/app/generated_resources_grd/IDS_PROFILE_ROW_SYNC_IS_ON.png.sha1
new file mode 100644
index 0000000..d55782f0
--- /dev/null
+++ b/chrome/app/generated_resources_grd/IDS_PROFILE_ROW_SYNC_IS_ON.png.sha1
@@ -0,0 +1 @@
+effe481f55e0e0c861de1b9aa4a63784406f072b
\ No newline at end of file
diff --git a/chrome/app/generated_resources_grd/IDS_PROFILE_ROW_TURN_ON_SYNC.png.sha1 b/chrome/app/generated_resources_grd/IDS_PROFILE_ROW_TURN_ON_SYNC.png.sha1
new file mode 100644
index 0000000..87661f4
--- /dev/null
+++ b/chrome/app/generated_resources_grd/IDS_PROFILE_ROW_TURN_ON_SYNC.png.sha1
@@ -0,0 +1 @@
+ba4be5c1b86cf9528daa46e23d4e8ec49709c80e
\ No newline at end of file
diff --git a/chrome/app/generated_resources_grd/IDS_TAB_AX_HIGH_MEMORY_USAGE.png.sha1 b/chrome/app/generated_resources_grd/IDS_TAB_AX_HIGH_MEMORY_USAGE.png.sha1
new file mode 100644
index 0000000..6def48c
--- /dev/null
+++ b/chrome/app/generated_resources_grd/IDS_TAB_AX_HIGH_MEMORY_USAGE.png.sha1
@@ -0,0 +1 @@
+f6b9ad1f71a93bdd1de62ca06df7b96e16609637
\ No newline at end of file
diff --git a/chrome/app/generated_resources_grd/IDS_TAB_AX_INACTIVE_TAB.png.sha1 b/chrome/app/generated_resources_grd/IDS_TAB_AX_INACTIVE_TAB.png.sha1
new file mode 100644
index 0000000..9a07bbe7
--- /dev/null
+++ b/chrome/app/generated_resources_grd/IDS_TAB_AX_INACTIVE_TAB.png.sha1
@@ -0,0 +1 @@
+322d48384f4060e11fb2af1b54d40bf51b2dfccf
\ No newline at end of file
diff --git a/chrome/app/generated_resources_grd/IDS_TAB_AX_MEMORY_SAVINGS.png.sha1 b/chrome/app/generated_resources_grd/IDS_TAB_AX_MEMORY_SAVINGS.png.sha1
new file mode 100644
index 0000000..a2b0d95
--- /dev/null
+++ b/chrome/app/generated_resources_grd/IDS_TAB_AX_MEMORY_SAVINGS.png.sha1
@@ -0,0 +1 @@
+0e7af550bc5f177b95b884224860b97ac7f9cd39
\ No newline at end of file
diff --git a/chrome/app/generated_resources_grd/IDS_TAB_AX_MEMORY_USAGE.png.sha1 b/chrome/app/generated_resources_grd/IDS_TAB_AX_MEMORY_USAGE.png.sha1
new file mode 100644
index 0000000..5d38d929
--- /dev/null
+++ b/chrome/app/generated_resources_grd/IDS_TAB_AX_MEMORY_USAGE.png.sha1
@@ -0,0 +1 @@
+03fbef65b7e6a62bdd3b90a7be9b45a1401ec9f0
\ No newline at end of file
diff --git a/chrome/app/google_chrome_strings.grd b/chrome/app/google_chrome_strings.grd
index 7331085b..065fc80 100644
--- a/chrome/app/google_chrome_strings.grd
+++ b/chrome/app/google_chrome_strings.grd
@@ -1835,24 +1835,6 @@
           </message>
         </if>
       </if>
-
-      <!-- Payments settings DeviceAuthentication strings -->
-      <if expr="is_macosx">
-        <message name="IDS_PAYMENTS_AUTOFILL_MANDATORY_REAUTH_PROMPT" desc="Message to be displayed to the user in the device authentication prompt when they click on the mandatory auth toggle on the Payments settings page, irrespective to enable or disable it. When mandatory auth toggle is enabled, we would authenticate the user before autofilling in the payment details. Note: MacOS has the wording 'Google Chrome is trying to' prepended onto the message during user auth.">
-          modify settings for filling payment methods.
-        </message>
-        <message name="IDS_PAYMENTS_AUTOFILL_EDIT_CARD_MANDATORY_REAUTH_PROMPT" desc="Message to be displayed to the user in the device authentication prompt when they try to edit a local card on the Payments settings page if the mandatory auth toggle is enabled. If the user auth was succesful, we would then show the edit card dialog. Note: MacOS has the wording 'Google Chrome is trying to' prepended onto the message during user auth.">
-          edit payment methods.
-        </message>
-      </if>
-      <if expr="is_win">
-        <message name="IDS_PAYMENTS_AUTOFILL_MANDATORY_REAUTH_PROMPT" desc="Message to be displayed to the user in the device authentication prompt when they click on the mandatory auth toggle on the Payments settings page, irrespective to enable or disable it. When mandatory auth toggle is enabled, we would authenticate the user before autofilling in the payment details.">
-          Google Chrome is trying to modify settings for filling payment methods.
-        </message>
-        <message name="IDS_PAYMENTS_AUTOFILL_EDIT_CARD_MANDATORY_REAUTH_PROMPT" desc="Message to be displayed to the user in the device authentication prompt when they try to edit a local card on the Payments settings page if the mandatory auth toggle is enabled. If the user auth was succesful, we would then show the edit card dialog.">
-          Google Chrome is trying to edit payment methods.
-        </message>
-      </if>
     </messages>
   </release>
 </grit>
diff --git a/chrome/app/media_router_strings.grdp b/chrome/app/media_router_strings.grdp
index ef1dcfe..cd02554 100644
--- a/chrome/app/media_router_strings.grdp
+++ b/chrome/app/media_router_strings.grdp
@@ -405,4 +405,26 @@
     PC is wired and Chromecast is on Wi-Fi
   </message>
 
+  <!-- OS-level notifications -->
+  <message name="IDS_MEDIA_ROUTER_NOTIFICATION_MESSAGE_CAST_SCREEN" desc="The notification message to tell the user we are casting the desktop screen.">
+    You are currently casting your screen
+  </message>
+  <message name="IDS_MEDIA_ROUTER_NOTIFICATION_MESSAGE_PAUSED" desc="The notification message to tell the user that casting is paused.">
+    Casting is currently paused. You can resume casting or stop casting at any time.
+  </message>
+  <message name="IDS_MEDIA_ROUTER_NOTIFICATION_MESSAGE_SCREEN_CAN_PAUSE" desc="Notification message to tell the user that we are casting the desktop screen, and that the casting session may be paused.">
+    You are casting your screen. You can pause or stop casting your screen at any time.
+  </message>
+  <message name="IDS_MEDIA_ROUTER_NOTIFICATION_MESSAGE_SCREEN_PAUSED" desc="Notification message to tell the user that we are casting the desktop screen, and that the casting session is currently paused.">
+    Casting is currently paused. You can resume casting or stop casting your screen at any time.
+  </message>
+  <message name="IDS_MEDIA_ROUTER_NOTIFICATION_MESSAGE_TAB_CAN_PAUSE" desc="Notification message to tell the user that we are casting a tab, and that the casting session may be paused.">
+    You are casting a tab. You can pause or stop casting at any time.
+  </message>
+  <message name="IDS_MEDIA_ROUTER_NOTIFICATION_TITLE" desc="The notification title to tell the user we are casting to a cast device.">
+    Casting to <ph name="RECEIVER_NAME">$1<ex>Living Room</ex></ph>
+  </message>
+  <message name="IDS_MEDIA_ROUTER_NOTIFICATION_TITLE_UNKNOWN" desc="The label used when we have detected we are casting but do not know what we are casting or who we are casting to.">
+    Casting to an unknown receiver
+  </message>
 </grit-part>
diff --git a/chrome/app/media_router_strings_grdp/IDS_MEDIA_ROUTER_NOTIFICATION_MESSAGE_CAST_SCREEN.png.sha1 b/chrome/app/media_router_strings_grdp/IDS_MEDIA_ROUTER_NOTIFICATION_MESSAGE_CAST_SCREEN.png.sha1
new file mode 100644
index 0000000..475bbd0
--- /dev/null
+++ b/chrome/app/media_router_strings_grdp/IDS_MEDIA_ROUTER_NOTIFICATION_MESSAGE_CAST_SCREEN.png.sha1
@@ -0,0 +1 @@
+f0190bf661e776bd79d84daf91064adc2317145a
\ No newline at end of file
diff --git a/chrome/app/media_router_strings_grdp/IDS_MEDIA_ROUTER_NOTIFICATION_MESSAGE_PAUSED.png.sha1 b/chrome/app/media_router_strings_grdp/IDS_MEDIA_ROUTER_NOTIFICATION_MESSAGE_PAUSED.png.sha1
new file mode 100644
index 0000000..aaecd26
--- /dev/null
+++ b/chrome/app/media_router_strings_grdp/IDS_MEDIA_ROUTER_NOTIFICATION_MESSAGE_PAUSED.png.sha1
@@ -0,0 +1 @@
+77ecfe482867dd967df11234df6e2a2394bfb8fd
\ No newline at end of file
diff --git a/chrome/app/media_router_strings_grdp/IDS_MEDIA_ROUTER_NOTIFICATION_MESSAGE_SCREEN_CAN_PAUSE.png.sha1 b/chrome/app/media_router_strings_grdp/IDS_MEDIA_ROUTER_NOTIFICATION_MESSAGE_SCREEN_CAN_PAUSE.png.sha1
new file mode 100644
index 0000000..e3dcaa2
--- /dev/null
+++ b/chrome/app/media_router_strings_grdp/IDS_MEDIA_ROUTER_NOTIFICATION_MESSAGE_SCREEN_CAN_PAUSE.png.sha1
@@ -0,0 +1 @@
+5201eef9af754085570250ae1509cff227e90ac0
\ No newline at end of file
diff --git a/chrome/app/media_router_strings_grdp/IDS_MEDIA_ROUTER_NOTIFICATION_MESSAGE_SCREEN_PAUSED.png.sha1 b/chrome/app/media_router_strings_grdp/IDS_MEDIA_ROUTER_NOTIFICATION_MESSAGE_SCREEN_PAUSED.png.sha1
new file mode 100644
index 0000000..aaecd26
--- /dev/null
+++ b/chrome/app/media_router_strings_grdp/IDS_MEDIA_ROUTER_NOTIFICATION_MESSAGE_SCREEN_PAUSED.png.sha1
@@ -0,0 +1 @@
+77ecfe482867dd967df11234df6e2a2394bfb8fd
\ No newline at end of file
diff --git a/chrome/app/media_router_strings_grdp/IDS_MEDIA_ROUTER_NOTIFICATION_MESSAGE_TAB_CAN_PAUSE.png.sha1 b/chrome/app/media_router_strings_grdp/IDS_MEDIA_ROUTER_NOTIFICATION_MESSAGE_TAB_CAN_PAUSE.png.sha1
new file mode 100644
index 0000000..8a7ce1a
--- /dev/null
+++ b/chrome/app/media_router_strings_grdp/IDS_MEDIA_ROUTER_NOTIFICATION_MESSAGE_TAB_CAN_PAUSE.png.sha1
@@ -0,0 +1 @@
+7e01b794e901147bb270195f5c36315ab6561930
\ No newline at end of file
diff --git a/chrome/app/media_router_strings_grdp/IDS_MEDIA_ROUTER_NOTIFICATION_TITLE.png.sha1 b/chrome/app/media_router_strings_grdp/IDS_MEDIA_ROUTER_NOTIFICATION_TITLE.png.sha1
new file mode 100644
index 0000000..aaecd26
--- /dev/null
+++ b/chrome/app/media_router_strings_grdp/IDS_MEDIA_ROUTER_NOTIFICATION_TITLE.png.sha1
@@ -0,0 +1 @@
+77ecfe482867dd967df11234df6e2a2394bfb8fd
\ No newline at end of file
diff --git a/chrome/app/media_router_strings_grdp/IDS_MEDIA_ROUTER_NOTIFICATION_TITLE_UNKNOWN.png.sha1 b/chrome/app/media_router_strings_grdp/IDS_MEDIA_ROUTER_NOTIFICATION_TITLE_UNKNOWN.png.sha1
new file mode 100644
index 0000000..c286171
--- /dev/null
+++ b/chrome/app/media_router_strings_grdp/IDS_MEDIA_ROUTER_NOTIFICATION_TITLE_UNKNOWN.png.sha1
@@ -0,0 +1 @@
+3db63c9351200fb323a46b23f39ae7ea2b29d411
\ No newline at end of file
diff --git a/chrome/app/vector_icons/BUILD.gn b/chrome/app/vector_icons/BUILD.gn
index c06407f..1be7774 100644
--- a/chrome/app/vector_icons/BUILD.gn
+++ b/chrome/app/vector_icons/BUILD.gn
@@ -298,6 +298,7 @@
   if (is_chromeos_ash) {
     sources += [
       "autocorrect_undo.icon",
+      "floating_workspace_notification.icon",
       "full_restore_notification.icon",
       "game_controls_add.icon",
       "game_controls_delete.icon",
diff --git a/chrome/app/vector_icons/floating_workspace_notification.icon b/chrome/app/vector_icons/floating_workspace_notification.icon
new file mode 100644
index 0000000..a456bb5
--- /dev/null
+++ b/chrome/app/vector_icons/floating_workspace_notification.icon
@@ -0,0 +1,89 @@
+// 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.
+
+CANVAS_DIMENSIONS, 20,
+MOVE_TO, 11.48f, 18,
+H_LINE_TO, 8.52f,
+R_CUBIC_TO, -0.59f, 0, -1.09f, -0.42f, -1.16f, -0.99f,
+R_LINE_TO, -0.22f, -1.47f,
+R_ARC_TO, 7.28f, 7.28f, 0, 0, 1, -0.63f, -0.36f,
+R_LINE_TO, -1.44f, 0.56f,
+R_CUBIC_TO, -0.56f, 0.2f, -1.18f, -0.02f, -1.45f, -0.51f,
+R_LINE_TO, -1.46f, -2.47f,
+R_CUBIC_TO, -0.28f, -0.52f, -0.16f, -1.12f, 0.29f, -1.47f,
+R_LINE_TO, 1.22f, -0.93f,
+R_ARC_TO, 5.17f, 5.17f, 0, 0, 1, -0.02f, -0.36f,
+R_CUBIC_TO, 0, -0.12f, 0.01f, -0.24f, 0.02f, -0.36f,
+R_LINE_TO, -1.22f, -0.93f,
+ARC_TO, 1.11f, 1.11f, 0, 0, 1, 2.16f, 7.24f,
+R_LINE_TO, 1.48f, -2.49f,
+R_CUBIC_TO, 0.27f, -0.48f, 0.89f, -0.7f, 1.43f, -0.49f,
+R_LINE_TO, 1.45f, 0.57f,
+R_ARC_TO, 8.51f, 8.51f, 0, 0, 1, 0.62f, -0.36f,
+R_LINE_TO, 0.22f, -1.49f,
+CUBIC_TO, 7.43f, 2.43f, 7.93f, 2, 8.51f, 2,
+R_H_LINE_TO, 2.96f,
+R_CUBIC_TO, 0.59f, 0, 1.09f, 0.42f, 1.16f, 0.99f,
+R_LINE_TO, 0.22f, 1.48f,
+R_CUBIC_TO, 0.22f, 0.11f, 0.42f, 0.23f, 0.63f, 0.36f,
+R_LINE_TO, 1.44f, -0.56f,
+R_CUBIC_TO, 0.57f, -0.2f, 1.18f, 0.02f, 1.46f, 0.51f,
+R_LINE_TO, 1.47f, 2.48f,
+R_ARC_TO, 1.13f, 1.13f, 0, 0, 1, -0.29f, 1.47f,
+R_LINE_TO, -1.22f, 0.93f,
+R_CUBIC_TO, 0.01f, 0.12f, 0.02f, 0.23f, 0.02f, 0.36f,
+R_CUBIC_TO, 0, 0.13f, -0.01f, 0.24f, -0.02f, 0.36f,
+R_LINE_TO, 1.22f, 0.93f,
+R_CUBIC_TO, 0.45f, 0.35f, 0.58f, 0.96f, 0.3f, 1.45f,
+R_LINE_TO, -1.49f, 2.51f,
+R_CUBIC_TO, -0.27f, 0.48f, -0.89f, 0.7f, -1.44f, 0.49f,
+R_LINE_TO, -1.44f, -0.56f,
+R_ARC_TO, 8.54f, 8.54f, 0, 0, 1, -0.62f, 0.36f,
+R_LINE_TO, -0.22f, 1.49f,
+R_CUBIC_TO, -0.08f, 0.53f, -0.58f, 0.95f, -1.17f, 0.95f,
+CLOSE,
+R_MOVE_TO, -2.53f, -2,
+R_H_LINE_TO, 2.11f,
+R_LINE_TO, 0.28f, -1.85f,
+R_LINE_TO, 0.41f, -0.16f,
+R_CUBIC_TO, 0.34f, -0.13f, 0.67f, -0.32f, 1.03f, -0.57f,
+R_LINE_TO, 0.35f, -0.25f,
+R_LINE_TO, 1.82f, 0.7f,
+LINE_TO, 16, 12.12f,
+R_LINE_TO, -1.55f, -1.15f,
+R_LINE_TO, 0.05f, -0.41f,
+R_CUBIC_TO, 0.02f, -0.19f, 0.05f, -0.37f, 0.05f, -0.57f,
+R_CUBIC_TO, 0, -0.2f, -0.02f, -0.38f, -0.05f, -0.57f,
+R_LINE_TO, -0.05f, -0.41f,
+LINE_TO, 16, 7.88f,
+R_LINE_TO, -1.06f, -1.74f,
+R_LINE_TO, -1.83f, 0.7f,
+R_LINE_TO, -0.34f, -0.25f,
+R_ARC_TO, 4.63f, 4.63f, 0, 0, 0, -1.02f, -0.56f,
+R_LINE_TO, -0.4f, -0.16f,
+LINE_TO, 11.06f, 4,
+H_LINE_TO, 8.95f,
+R_LINE_TO, -0.28f, 1.86f,
+R_LINE_TO, -0.41f, 0.15f,
+R_ARC_TO, 4.91f, 4.91f, 0, 0, 0, -1.03f, 0.57f,
+R_LINE_TO, -0.34f, 0.24f,
+R_LINE_TO, -1.82f, -0.69f,
+LINE_TO, 4, 7.87f,
+R_LINE_TO, 1.56f, 1.15f,
+R_LINE_TO, -0.05f, 0.41f,
+R_CUBIC_TO, -0.02f, 0.19f, -0.05f, 0.39f, -0.05f, 0.57f,
+R_CUBIC_TO, 0, 0.19f, 0.02f, 0.39f, 0.05f, 0.57f,
+R_LINE_TO, 0.05f, 0.41f,
+LINE_TO, 4, 12.12f,
+R_LINE_TO, 1.06f, 1.75f,
+R_LINE_TO, 1.83f, -0.7f,
+R_LINE_TO, 0.35f, 0.26f,
+R_CUBIC_TO, 0.33f, 0.24f, 0.66f, 0.42f, 1.02f, 0.56f,
+R_LINE_TO, 0.41f, 0.16f,
+LINE_TO, 8.95f, 16,
+CLOSE,
+MOVE_TO, 10, 12.5f,
+R_ARC_TO, 2.5f, 2.5f, 0, 1, 0, 0, -5,
+R_ARC_TO, 2.5f, 2.5f, 0, 0, 0, 0, 5,
+CLOSE
diff --git a/chrome/browser/about_flags.cc b/chrome/browser/about_flags.cc
index 2c75aba3..7516ff5c 100644
--- a/chrome/browser/about_flags.cc
+++ b/chrome/browser/about_flags.cc
@@ -297,6 +297,7 @@
 #endif  // ENABLE_CARDBOARD
 
 #if BUILDFLAG(ENABLE_EXTENSIONS)
+#include "chrome/browser/extensions/cws_info_service.h"
 #include "extensions/common/extension_features.h"
 #include "extensions/common/switches.h"
 #endif  // BUILDFLAG(ENABLE_EXTENSIONS)
@@ -6088,6 +6089,11 @@
      flag_descriptions::kOmniboxCR23ExpandedStateLayoutDescription, kOsDesktop,
      FEATURE_VALUE_TYPE(omnibox::kExpandedLayout)},
 
+    {"omnibox-cr23-suggestion-hover-fill-shape",
+     flag_descriptions::kOmniboxCR23SuggestionHoverFillShapeName,
+     flag_descriptions::kOmniboxCR23SuggestionHoverFillShapeDescription,
+     kOsDesktop, FEATURE_VALUE_TYPE(omnibox::kSuggestionHoverFillShape)},
+
     {"omnibox-gm3-steady-state-background-color",
      flag_descriptions::kOmniboxGM3SteadyStateBackgroundColorName,
      flag_descriptions::kOmniboxGM3SteadyStateBackgroundColorDescription,
@@ -10483,6 +10489,12 @@
      flag_descriptions::kCrosBatterySaverDescription, kOsCrOS,
      FEATURE_VALUE_TYPE(ash::features::kBatterySaver)},
 #endif  // BUILDFLAG(IS_CHROMEOS_ASH)
+        //
+#if BUILDFLAG(IS_ANDROID)
+    {"thumbnail-placeholder", flag_descriptions::kThumbnailPlaceholderName,
+     flag_descriptions::kThumbnailPlaceholderDescription, kOsAndroid,
+     FEATURE_VALUE_TYPE(chrome::android::kThumbnailPlaceholder)},
+#endif  // BUILDFLAG(IS_ANDROID)
 
     {"enable-process-per-site-up-to-main-frame-threshold",
      flag_descriptions::kEnableProcessPerSiteUpToMainFrameThresholdName,
@@ -10511,6 +10523,28 @@
                                     kRenderDocumentVariations,
                                     "RenderDocument")},
 
+#if BUILDFLAG(ENABLE_EXTENSIONS)
+    {"cws-info-fast-check", flag_descriptions::kCWSInfoFastCheckName,
+     flag_descriptions::kCWSInfoFastCheckDescription, kOsDesktop,
+     FEATURE_VALUE_TYPE(extensions::kCWSInfoFastCheck)},
+
+    {"safety-check-extensions", flag_descriptions::kSafetyCheckExtensionsName,
+     flag_descriptions::kSafetyCheckExtensionsDescription, kOsDesktop,
+     FEATURE_VALUE_TYPE(features::kSafetyCheckExtensions)},
+#endif  // BUILDFLAG(ENABLE_EXTENSIONS)
+
+#if BUILDFLAG(IS_ANDROID)
+    {"webapk-install-failure-notification",
+     flag_descriptions::kWebApkInstallFailureNotificationName,
+     flag_descriptions::kWebApkInstallFailureNotificationDescription,
+     kOsAndroid,
+     FEATURE_VALUE_TYPE(webapps::features::kWebApkInstallFailureNotification)},
+    {"webapk-install-failure-retry",
+     flag_descriptions::kWebApkInstallFailureRetryName,
+     flag_descriptions::kWebApkInstallFailureRetryDescription, kOsAndroid,
+     FEATURE_VALUE_TYPE(webapps::features::kWebApkInstallFailureRetry)},
+#endif  // BUILDFLAG(IS_ANDROID)
+
     // NOTE: Adding a new flag requires adding a corresponding entry to enum
     // "LoginCustomFlags" in tools/metrics/histograms/enums.xml. See "Flag
     // Histograms" in tools/metrics/histograms/README.md (run the
diff --git a/chrome/browser/app_controller_mac_unittest.mm b/chrome/browser/app_controller_mac_unittest.mm
index 548529a..511ebbe 100644
--- a/chrome/browser/app_controller_mac_unittest.mm
+++ b/chrome/browser/app_controller_mac_unittest.mm
@@ -2,14 +2,12 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "base/memory/raw_ptr.h"
-
 #import <Cocoa/Cocoa.h>
 
 #include "base/files/file_path.h"
 #include "base/functional/callback_helpers.h"
-#include "base/mac/scoped_nsobject.h"
 #include "base/mac/scoped_objc_class_swizzler.h"
+#include "base/memory/raw_ptr.h"
 #include "base/run_loop.h"
 #include "base/strings/utf_string_conversions.h"
 #include "base/test/bind.h"
@@ -27,10 +25,14 @@
 #include "content/public/test/browser_task_environment.h"
 #include "testing/platform_test.h"
 
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
 namespace {
 
-id* TargetForAction() {
-  static id targetForAction;
+id __weak* TargetForAction() {
+  static id __weak targetForAction;
   return &targetForAction;
 }
 
@@ -44,13 +46,13 @@
 
 // A class providing alternative implementations of various methods.
 @interface AppControllerKeyEquivalentTestHelper : NSObject
-- (id)targetForAction:(SEL)selector;
+- (id __weak)targetForAction:(SEL)selector;
 - (BOOL)windowHasBrowserTabs:(NSWindow*)window;
 @end
 
 @implementation AppControllerKeyEquivalentTestHelper
 
-- (id)targetForAction:(SEL)selector {
+- (id __weak)targetForAction:(SEL)selector {
   return *TargetForAction();
 }
 
@@ -90,39 +92,39 @@
   void SetUp() override {
     PlatformTest::SetUp();
 
-    nsAppTargetForActionSwizzler_ =
+    nsapp_target_for_action_swizzler_ =
         std::make_unique<base::mac::ScopedObjCClassSwizzler>(
             [NSApp class], [AppControllerKeyEquivalentTestHelper class],
             @selector(targetForAction:));
-    appControllerSwizzler_ =
+    app_controller_swizzler_ =
         std::make_unique<base::mac::ScopedObjCClassSwizzler>(
             [AppController class], [AppControllerKeyEquivalentTestHelper class],
             @selector(windowHasBrowserTabs:));
 
-    appController_ = AppController.sharedController;
+    app_controller_ = AppController.sharedController;
 
-    closeWindowMenuItem_.reset([[NSMenuItem alloc] initWithTitle:@""
-                                                          action:nullptr
-                                                   keyEquivalent:@""]);
-    [appController_ setCloseWindowMenuItemForTesting:closeWindowMenuItem_];
+    close_window_menu_item_ = [[NSMenuItem alloc] initWithTitle:@""
+                                                         action:nullptr
+                                                  keyEquivalent:@""];
+    [app_controller_ setCloseWindowMenuItemForTesting:close_window_menu_item_];
 
-    closeTabMenuItem_.reset([[NSMenuItem alloc] initWithTitle:@""
-                                                       action:nullptr
-                                                keyEquivalent:@""]);
-    [appController_ setCloseTabMenuItemForTesting:closeTabMenuItem_];
+    close_tab_menu_item_ = [[NSMenuItem alloc] initWithTitle:@""
+                                                      action:nullptr
+                                               keyEquivalent:@""];
+    [app_controller_ setCloseTabMenuItemForTesting:close_tab_menu_item_];
   }
 
   void CheckMenuItemsMatchBrowserWindow() {
     ASSERT_EQ([NSApp targetForAction:@selector(performClose:)],
               *TargetForAction());
 
-    [appController_ updateMenuItemKeyEquivalents];
+    [app_controller_ updateMenuItemKeyEquivalents];
 
-    EXPECT_TRUE([[closeWindowMenuItem_ keyEquivalent] isEqualToString:@"W"]);
-    EXPECT_EQ([closeWindowMenuItem_ keyEquivalentModifierMask],
+    EXPECT_TRUE([[close_window_menu_item_ keyEquivalent] isEqualToString:@"W"]);
+    EXPECT_EQ([close_window_menu_item_ keyEquivalentModifierMask],
               NSEventModifierFlagCommand);
-    EXPECT_TRUE([[closeTabMenuItem_ keyEquivalent] isEqualToString:@"w"]);
-    EXPECT_EQ([closeTabMenuItem_ keyEquivalentModifierMask],
+    EXPECT_TRUE([[close_tab_menu_item_ keyEquivalent] isEqualToString:@"w"]);
+    EXPECT_EQ([close_tab_menu_item_ keyEquivalentModifierMask],
               NSEventModifierFlagCommand);
   }
 
@@ -130,30 +132,30 @@
     ASSERT_EQ([NSApp targetForAction:@selector(performClose:)],
               *TargetForAction());
 
-    [appController_ updateMenuItemKeyEquivalents];
+    [app_controller_ updateMenuItemKeyEquivalents];
 
-    EXPECT_TRUE([[closeWindowMenuItem_ keyEquivalent] isEqualToString:@"w"]);
-    EXPECT_EQ([closeWindowMenuItem_ keyEquivalentModifierMask],
+    EXPECT_TRUE([[close_window_menu_item_ keyEquivalent] isEqualToString:@"w"]);
+    EXPECT_EQ([close_window_menu_item_ keyEquivalentModifierMask],
               NSEventModifierFlagCommand);
-    EXPECT_TRUE([[closeTabMenuItem_ keyEquivalent] isEqualToString:@""]);
-    EXPECT_EQ([closeTabMenuItem_ keyEquivalentModifierMask], 0UL);
+    EXPECT_TRUE([[close_tab_menu_item_ keyEquivalent] isEqualToString:@""]);
+    EXPECT_EQ([close_tab_menu_item_ keyEquivalentModifierMask], 0UL);
   }
 
   void TearDown() override {
     PlatformTest::TearDown();
 
-    [appController_ setCloseWindowMenuItemForTesting:nil];
-    [appController_ setCloseTabMenuItemForTesting:nil];
+    [app_controller_ setCloseWindowMenuItemForTesting:nil];
+    [app_controller_ setCloseTabMenuItemForTesting:nil];
     *TargetForAction() = nil;
   }
 
  private:
   std::unique_ptr<base::mac::ScopedObjCClassSwizzler>
-      nsAppTargetForActionSwizzler_;
-  std::unique_ptr<base::mac::ScopedObjCClassSwizzler> appControllerSwizzler_;
-  AppController* appController_;
-  base::scoped_nsobject<NSMenuItem> closeWindowMenuItem_;
-  base::scoped_nsobject<NSMenuItem> closeTabMenuItem_;
+      nsapp_target_for_action_swizzler_;
+  std::unique_ptr<base::mac::ScopedObjCClassSwizzler> app_controller_swizzler_;
+  AppController* __strong app_controller_;
+  NSMenuItem* __strong close_window_menu_item_;
+  NSMenuItem* __strong close_tab_menu_item_;
 };
 
 TEST_F(AppControllerTest, DockMenuProfileNotLoaded) {
@@ -217,20 +219,22 @@
 TEST_F(AppControllerKeyEquivalentTest, UpdateMenuItemsForBubbleWindow) {
   // Set up the "bubble" and main window.
   const NSRect kContentRect = NSMakeRect(0.0, 0.0, 10.0, 10.0);
-  base::scoped_nsobject<NSWindow> childWindow([[NSWindow alloc]
-      initWithContentRect:kContentRect
-                styleMask:NSWindowStyleMaskClosable
-                  backing:NSBackingStoreBuffered
-                    defer:YES]);
-  base::scoped_nsobject<NSWindow> browserWindow([[FakeBrowserWindow alloc]
-      initWithContentRect:kContentRect
-                styleMask:NSWindowStyleMaskClosable
-                  backing:NSBackingStoreBuffered
-                    defer:YES]);
+  NSWindow* child_window =
+      [[NSWindow alloc] initWithContentRect:kContentRect
+                                  styleMask:NSWindowStyleMaskClosable
+                                    backing:NSBackingStoreBuffered
+                                      defer:YES];
+  child_window.releasedWhenClosed = NO;
+  NSWindow* browser_window =
+      [[FakeBrowserWindow alloc] initWithContentRect:kContentRect
+                                           styleMask:NSWindowStyleMaskClosable
+                                             backing:NSBackingStoreBuffered
+                                               defer:YES];
+  browser_window.releasedWhenClosed = NO;
 
-  [browserWindow addChildWindow:childWindow ordered:NSWindowAbove];
+  [browser_window addChildWindow:child_window ordered:NSWindowAbove];
 
-  *TargetForAction() = childWindow;
+  *TargetForAction() = child_window;
 
   CheckMenuItemsMatchBrowserWindow();
 }
@@ -239,21 +243,24 @@
 TEST_F(AppControllerKeyEquivalentTest, UpdateMenuItemsForPopover) {
   // Set up the popover and main window.
   const NSRect kContentRect = NSMakeRect(0.0, 0.0, 10.0, 10.0);
-  base::scoped_nsobject<NSPopover> popover([[NSPopover alloc] init]);
-  base::scoped_nsobject<NSWindow> popoverWindow([[NSWindow alloc]
-      initWithContentRect:kContentRect
-                styleMask:NSWindowStyleMaskClosable
-                  backing:NSBackingStoreBuffered
-                    defer:YES]);
-  [popover
-      setContentViewController:[[[NSViewController alloc] init] autorelease]];
-  [[popover contentViewController] setView:[popoverWindow contentView]];
-  base::scoped_nsobject<NSWindow> browserWindow([[FakeBrowserWindow alloc]
-      initWithContentRect:kContentRect
-                styleMask:NSWindowStyleMaskClosable
-                  backing:NSBackingStoreBuffered
-                    defer:YES]);
-  [browserWindow addChildWindow:popoverWindow ordered:NSWindowAbove];
+  NSPopover* popover = [[NSPopover alloc] init];
+  NSWindow* popover_window =
+      [[NSWindow alloc] initWithContentRect:kContentRect
+                                  styleMask:NSWindowStyleMaskClosable
+                                    backing:NSBackingStoreBuffered
+                                      defer:YES];
+  popover_window.releasedWhenClosed = NO;
+
+  [popover setContentViewController:[[NSViewController alloc] init]];
+  [[popover contentViewController] setView:[popover_window contentView]];
+
+  NSWindow* browser_window =
+      [[FakeBrowserWindow alloc] initWithContentRect:kContentRect
+                                           styleMask:NSWindowStyleMaskClosable
+                                             backing:NSBackingStoreBuffered
+                                               defer:YES];
+  browser_window.releasedWhenClosed = NO;
+  [browser_window addChildWindow:popover_window ordered:NSWindowAbove];
 
   *TargetForAction() = popover;
 
@@ -264,13 +271,13 @@
 TEST_F(AppControllerKeyEquivalentTest, UpdateMenuItemsForBrowserWindow) {
   // Set up the browser window.
   const NSRect kContentRect = NSMakeRect(0.0, 0.0, 10.0, 10.0);
-  base::scoped_nsobject<NSWindow> browserWindow([[FakeBrowserWindow alloc]
-      initWithContentRect:kContentRect
-                styleMask:NSWindowStyleMaskClosable
-                  backing:NSBackingStoreBuffered
-                    defer:YES]);
+  NSWindow* browser_window =
+      [[FakeBrowserWindow alloc] initWithContentRect:kContentRect
+                                           styleMask:NSWindowStyleMaskClosable
+                                             backing:NSBackingStoreBuffered
+                                               defer:YES];
 
-  *TargetForAction() = browserWindow;
+  *TargetForAction() = browser_window;
 
   CheckMenuItemsMatchBrowserWindow();
 }
@@ -279,21 +286,21 @@
 TEST_F(AppControllerKeyEquivalentTest, UpdateMenuItemsForNonBrowserWindow) {
   // Set up the window.
   const NSRect kContentRect = NSMakeRect(0.0, 0.0, 10.0, 10.0);
-  base::scoped_nsobject<NSWindow> mainWindow([[NSWindow alloc]
-      initWithContentRect:kContentRect
-                styleMask:NSWindowStyleMaskClosable
-                  backing:NSBackingStoreBuffered
-                    defer:YES]);
+  NSWindow* main_window =
+      [[NSWindow alloc] initWithContentRect:kContentRect
+                                  styleMask:NSWindowStyleMaskClosable
+                                    backing:NSBackingStoreBuffered
+                                      defer:YES];
 
-  *TargetForAction() = mainWindow;
+  *TargetForAction() = main_window;
 
   CheckMenuItemsMatchNonBrowserWindow();
 }
 
 // Tests key equivalents for Close Window when target is not a window.
 TEST_F(AppControllerKeyEquivalentTest, UpdateMenuItemsForNonWindow) {
-  base::scoped_nsobject<NSObject> nonWindowObject([[NSObject alloc] init]);
-  *TargetForAction() = nonWindowObject;
+  NSObject* non_window_object = [[NSObject alloc] init];
+  *TargetForAction() = non_window_object;
 
   CheckMenuItemsMatchNonBrowserWindow();
 }
diff --git a/chrome/browser/ash/BUILD.gn b/chrome/browser/ash/BUILD.gn
index f3a7273..03cd210 100644
--- a/chrome/browser/ash/BUILD.gn
+++ b/chrome/browser/ash/BUILD.gn
@@ -3448,6 +3448,7 @@
     "//chromeos:chromeos_export",
     "//chromeos/ash/components/attestation",
     "//chromeos/ash/components/audio",
+    "//chromeos/ash/components/auth_panel",
     "//chromeos/ash/components/browser_context_helper",
     "//chromeos/ash/components/cryptohome",
     "//chromeos/ash/components/dbus:metrics_event_proto",
diff --git a/chrome/browser/ash/extensions/autotest_private/autotest_private_apitest.cc b/chrome/browser/ash/extensions/autotest_private/autotest_private_apitest.cc
index bf2adc08..dc4d6f8 100644
--- a/chrome/browser/ash/extensions/autotest_private/autotest_private_apitest.cc
+++ b/chrome/browser/ash/extensions/autotest_private/autotest_private_apitest.cc
@@ -513,7 +513,9 @@
             ash::features::kLacrosOnly,
             ash::features::kLacrosProfileMigrationForceOff,
         },
-        {});
+        // Disable ash extension keeplist so that the test extension will not
+        // be blocked in Ash.
+        {ash::features::kEnforceAshExtensionKeeplist});
     crosapi::BrowserManager::DisableForTesting();
   }
   ~AutotestPrivateLacrosTest() override {
diff --git a/chrome/browser/ash/floating_workspace/floating_workspace_service.cc b/chrome/browser/ash/floating_workspace/floating_workspace_service.cc
index 048a54e10..b48de8a 100644
--- a/chrome/browser/ash/floating_workspace/floating_workspace_service.cc
+++ b/chrome/browser/ash/floating_workspace/floating_workspace_service.cc
@@ -5,10 +5,12 @@
 #include "chrome/browser/ash/floating_workspace/floating_workspace_service.h"
 
 #include <cstddef>
+#include <string>
 #include <vector>
 
 #include "ash/constants/ash_features.h"
 #include "ash/public/cpp/desk_template.h"
+#include "ash/public/cpp/notification_utils.h"
 #include "ash/wm/desks/templates/saved_desk_metrics_util.h"
 #include "ash/wm/desks/templates/saved_desk_util.h"
 #include "base/check.h"
@@ -16,24 +18,56 @@
 #include "base/task/single_thread_task_runner.h"
 #include "base/time/time.h"
 #include "base/uuid.h"
+#include "chrome/app/vector_icons/vector_icons.h"
 #include "chrome/browser/ash/floating_workspace/floating_workspace_metrics_util.h"
 #include "chrome/browser/ash/floating_workspace/floating_workspace_service_factory.h"
 #include "chrome/browser/ash/floating_workspace/floating_workspace_util.h"
 #include "chrome/browser/ash/login/session/user_session_manager.h"
+#include "chrome/browser/notifications/notification_display_service.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/sessions/session_restore.h"
 #include "chrome/browser/sync/desk_sync_service_factory.h"
 #include "chrome/browser/sync/session_sync_service_factory.h"
 #include "chrome/browser/sync/sync_service_factory.h"
 #include "chrome/browser/ui/ash/desks/desks_client.h"
-#include "components/app_restore/restore_data.h"
+#include "chrome/browser/ui/settings_window_manager_chromeos.h"
+#include "chrome/browser/ui/webui/settings/chromeos/constants/routes.mojom-forward.h"
+#include "chrome/grit/generated_resources.h"
+#include "components/desks_storage/core/desk_model.h"
 #include "components/desks_storage/core/desk_sync_bridge.h"
+#include "components/desks_storage/core/desk_sync_service.h"
+#include "components/sync/base/model_type.h"
+#include "components/sync/service/sync_service.h"
 #include "components/sync_sessions/open_tabs_ui_delegate.h"
 #include "components/sync_sessions/session_sync_service.h"
 #include "components/sync_sessions/synced_session.h"
+#include "ui/base/l10n/l10n_util.h"
+#include "ui/chromeos/devicetype_utils.h"
+#include "ui/message_center/public/cpp/notification.h"
 
 namespace ash {
 
+const char kNotificationForNoNetworkConnection[] =
+    "notification_no_network_connection";
+const char kNotificationForSyncErrorOrTimeOut[] =
+    "notification_sync_error_or_timeout";
+const char kNotificationForRestoreAfterError[] =
+    "notification_restore_after_error";
+
+FloatingWorkspaceServiceNotificationType GetNotificationTypeById(
+    const std::string& id) {
+  if (id == kNotificationForNoNetworkConnection) {
+    return FloatingWorkspaceServiceNotificationType::kNoNetworkConnection;
+  }
+  if (id == kNotificationForSyncErrorOrTimeOut) {
+    return FloatingWorkspaceServiceNotificationType::kSyncErrorOrTimeOut;
+  }
+  if (id == kNotificationForRestoreAfterError) {
+    return FloatingWorkspaceServiceNotificationType::kRestoreAfterError;
+  }
+  return FloatingWorkspaceServiceNotificationType::kUnknown;
+}
+
 // Static
 FloatingWorkspaceService* FloatingWorkspaceService::GetForProfile(
     Profile* profile) {
@@ -41,52 +75,48 @@
       FloatingWorkspaceServiceFactory::GetInstance()->GetForProfile(profile));
 }
 
-FloatingWorkspaceService::FloatingWorkspaceService(Profile* profile)
-    : profile_(profile), initialization_timestamp_(base::TimeTicks::Now()) {}
+FloatingWorkspaceService::FloatingWorkspaceService(
+    Profile* profile,
+    floating_workspace_util::FloatingWorkspaceVersion version)
+    : profile_(profile),
+      version_(version),
+      initialization_timestamp_(base::TimeTicks::Now()) {}
 
 FloatingWorkspaceService::~FloatingWorkspaceService() {
   if (is_testing_)
     return;
   if (floating_workspace_util::IsFloatingWorkspaceV2Enabled()) {
     StopCaptureAndUploadActiveDesk();
-    OnDeskModelDestroying();
   }
 }
 
-void FloatingWorkspaceService::Init() {
-  is_testing_ = false;
-  if (floating_workspace_util::IsFloatingWorkspaceV1Enabled()) {
+void FloatingWorkspaceService::Init(
+    syncer::SyncService* sync_service,
+    desks_storage::DeskSyncService* desk_sync_service) {
+  if (is_testing_) {
+    CHECK_IS_TEST();
+    if (version_ == floating_workspace_util::FloatingWorkspaceVersion::
+                        kFloatingWorkspaceV1Enabled) {
+      InitForV1();
+    } else {
+      InitForV2(sync_service, desk_sync_service);
+    }
+    return;
+  }
+  if (version_ == floating_workspace_util::FloatingWorkspaceVersion::
+                      kFloatingWorkspaceV1Enabled) {
     floating_workspace_metrics_util::
         RecordFloatingWorkspaceV1InitializedHistogram();
     InitForV1();
     return;
   }
 
-  if (saved_desk_util::AreDesksTemplatesEnabled() &&
+  if (version_ == floating_workspace_util::FloatingWorkspaceVersion::
+                      kFloatingWorkspaceV2Enabled &&
+      saved_desk_util::AreDesksTemplatesEnabled() &&
       features::IsDeskTemplateSyncEnabled() &&
       floating_workspace_util::IsFloatingWorkspaceV2Enabled()) {
-    InitForV2();
-  }
-}
-
-void FloatingWorkspaceService::InitForTest(
-    TestFloatingWorkspaceVersion version,
-    raw_ptr<desks_storage::DeskSyncService> fake_desk_sync_service) {
-  CHECK_IS_TEST();
-  is_testing_ = true;
-  switch (version) {
-    case TestFloatingWorkspaceVersion::kNoVersionEnabled:
-      break;
-    case TestFloatingWorkspaceVersion::kFloatingWorkspaceV1Enabled:
-      InitForV1();
-      break;
-    case TestFloatingWorkspaceVersion::kFloatingWorkspaceV2Enabled:
-      // For testings we don't need to add itself to observer list of
-      // DeskSyncBridge, tests can be done by calling
-      // EntriesAddedOrUpdatedRemotely directly so InitForV2 can be skipped.
-      desk_sync_service_ = fake_desk_sync_service;
-      StartCaptureAndUploadActiveDesk();
-      break;
+    InitForV2(sync_service, desk_sync_service);
   }
 }
 
@@ -165,37 +195,70 @@
   }
 }
 
-void FloatingWorkspaceService::OnDeskModelDestroying() {
-  desk_sync_service_->GetDeskModel()->RemoveObserver(this);
-}
-
-void FloatingWorkspaceService::EntriesAddedOrUpdatedRemotely(
-    const std::vector<const DeskTemplate*>& new_entries) {
-  const DeskTemplate* floating_workspace_template = nullptr;
-  for (const DeskTemplate* desk_template : new_entries) {
-    if (desk_template &&
-        desk_template->type() == DeskTemplateType::kFloatingWorkspace) {
-      // Set the to be floating workspace template to the latest floating
-      // workspace template found.
-      if (!floating_workspace_template ||
-          floating_workspace_template->GetLastUpdatedTime() <
-              desk_template->GetLastUpdatedTime()) {
-        floating_workspace_template = desk_template;
-      }
+void FloatingWorkspaceService::OnStateChanged(syncer::SyncService* sync) {
+  if (!should_run_restore_) {
+    return;
+  }
+  switch (sync->GetDownloadStatusFor(syncer::ModelType::WORKSPACE_DESK)) {
+    case syncer::SyncService::ModelTypeDownloadStatus::kWaitingForUpdates: {
+      // Floating Workspace Service needs to Wait until workspace desks are up
+      // to date.
+      break;
+    }
+    case syncer::SyncService::ModelTypeDownloadStatus::kUpToDate: {
+      RestoreFloatingWorkspaceTemplate(GetLatestFloatingWorkspaceTemplate());
+      break;
+    }
+    case syncer::SyncService::ModelTypeDownloadStatus::kError: {
+      // Sync is not expected to deliver the data, let user decide.
+      // TODO: send notification to user asking if restore local.
+      HandleSyncEror();
+      break;
     }
   }
+}
 
-  if (floating_workspace_template) {
-    RestoreFloatingWorkspaceTemplate(floating_workspace_template);
-  } else {
-    // Completed waiting for desk templates to download. Unable to find a
-    // floating workspace template. Emit a metric indictating we timeout because
-    // there is no floating workspace template.
-    floating_workspace_metrics_util::
-        RecordFloatingWorkspaceV2TemplateLaunchTimeout(
-            floating_workspace_metrics_util::LaunchTemplateTimeoutType::
-                kNoFloatingWorkspaceTemplate);
+void FloatingWorkspaceService::Click(
+    const absl::optional<int>& button_index,
+    const absl::optional<std::u16string>& reply) {
+  DCHECK(notification_);
+
+  switch (GetNotificationTypeById(notification_->id())) {
+    case FloatingWorkspaceServiceNotificationType::kUnknown:
+      // For unknown type of notification id, do nothing and run close logic.
+      break;
+    case FloatingWorkspaceServiceNotificationType::kSyncErrorOrTimeOut:
+      break;
+    case FloatingWorkspaceServiceNotificationType::kNoNetworkConnection:
+      if (button_index.has_value()) {
+        // Show network settings if the user clicks the show network settings
+        // button.
+        chrome::SettingsWindowManager::GetInstance()->ShowOSSettings(
+            profile_, chromeos::settings::mojom::kNetworkSectionPath);
+      }
+      break;
+    case FloatingWorkspaceServiceNotificationType::kRestoreAfterError:
+      if (!button_index.has_value() ||
+          button_index.value() ==
+              static_cast<int>(
+                  RestoreFromErrorNotificationButtonIndex::kRestore)) {
+        VLOG(1) << "Restore button clicked for floating workspace after error";
+        LaunchFloatingWorkspaceTemplate(GetLatestFloatingWorkspaceTemplate());
+      }
+      break;
   }
+  MaybeCloseNotification();
+}
+
+void FloatingWorkspaceService::MaybeCloseNotification() {
+  if (notification_ == nullptr) {
+    return;
+  }
+  auto* notification_display_service =
+      NotificationDisplayService::GetForProfile(profile_);
+  notification_display_service->Close(NotificationHandler::Type::TRANSIENT,
+                                      notification_->id());
+  notification_ = nullptr;
 }
 
 void FloatingWorkspaceService::InitForV1() {
@@ -203,10 +266,23 @@
       SessionSyncServiceFactory::GetInstance()->GetForProfile(profile_);
 }
 
-void FloatingWorkspaceService::InitForV2() {
-  desk_sync_service_ = DeskSyncServiceFactory::GetForProfile(profile_);
+void FloatingWorkspaceService::InitForV2(
+    syncer::SyncService* sync_service,
+    desks_storage::DeskSyncService* desk_sync_service) {
+  sync_service_ = sync_service;
+  desk_sync_service_ = desk_sync_service;
+  sync_service_->AddObserver(this);
   StartCaptureAndUploadActiveDesk();
-  desk_sync_service_->GetDeskModel()->AddObserver(this);
+  // Post a task to check if anything is restored after FWS timeout.
+  base::SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask(
+      FROM_HERE,
+      base::BindOnce(&FloatingWorkspaceService::MaybeHandleDownloadTimeOut,
+                     weak_pointer_factory_.GetWeakPtr()),
+      ash::features::kFloatingWorkspaceV2MaxTimeAvailableForRestoreAfterLogin
+          .Get());
+  if (!floating_workspace_util::IsInternetConnected()) {
+    SendNotification(kNotificationForNoNetworkConnection);
+  }
 }
 
 const sync_sessions::SyncedSession*
@@ -270,6 +346,29 @@
   timer_.Stop();
 }
 
+const DeskTemplate*
+FloatingWorkspaceService::GetLatestFloatingWorkspaceTemplate() {
+  desks_storage::DeskModel::GetAllEntriesResult result =
+      desk_sync_service_->GetDeskModel()->GetAllEntries();
+  if (result.status != desks_storage::DeskModel::GetAllEntriesStatus::kOk) {
+    return nullptr;
+  }
+  const DeskTemplate* floating_workspace_template = nullptr;
+  for (const DeskTemplate* desk_template : result.entries) {
+    if (desk_template &&
+        desk_template->type() == DeskTemplateType::kFloatingWorkspace) {
+      // Set the to be floating workspace template to the latest floating
+      // workspace template found.
+      if (!floating_workspace_template ||
+          floating_workspace_template->GetLastUpdatedTime() <
+              desk_template->GetLastUpdatedTime()) {
+        floating_workspace_template = desk_template;
+      }
+    }
+  }
+  return floating_workspace_template;
+}
+
 void FloatingWorkspaceService::CaptureAndUploadActiveDesk() {
   GetDesksClient()->CaptureActiveDesk(
       base::BindOnce(&FloatingWorkspaceService::OnTemplateCaptured,
@@ -286,8 +385,8 @@
 // workspace templates.
 void FloatingWorkspaceService::RestoreFloatingWorkspaceTemplate(
     const DeskTemplate* desk_template) {
-  // Desk templates have been downloaded.
-  if (!should_run_restore_) {
+  if (desk_template == nullptr) {
+    should_run_restore_ = false;
     return;
   }
   // Record metrics for window and tab count and also the time it took to
@@ -295,25 +394,28 @@
   floating_workspace_metrics_util::RecordFloatingWorkspaceV2TemplateLoadTime(
       base::TimeTicks::Now() - initialization_timestamp_);
   RecordWindowAndTabCountHistogram(*desk_template);
-  // Check if template has been downloaded after 15 seconds (TBD).
+  // Check if template has been downloaded after
+  // kFloatingWorkspaceV2MaxTimeAvailableForRestoreAfterLogin.
   if (base::TimeTicks::Now() >
       initialization_timestamp_ +
           ash::features::
               kFloatingWorkspaceV2MaxTimeAvailableForRestoreAfterLogin.Get()) {
-    // No need to restore any remote session 15 seconds (TBD) after login.
+    // Template arrives late, asking user to restore or not.
+    SendNotification(kNotificationForRestoreAfterError);
+    // Set this flag false after sending restore notification to user
+    // since user will control the restoration behavior from then on.
     should_run_restore_ = false;
-    floating_workspace_metrics_util::
-        RecordFloatingWorkspaceV2TemplateLaunchTimeout(
-            floating_workspace_metrics_util::LaunchTemplateTimeoutType::
-                kPassedWaitPeriod);
     return;
   }
-
   LaunchFloatingWorkspaceTemplate(desk_template);
 }
 
 void FloatingWorkspaceService::LaunchFloatingWorkspaceTemplate(
     const DeskTemplate* desk_template) {
+  should_run_restore_ = false;
+  if (desk_template == nullptr) {
+    return;
+  }
   GetDesksClient()->LaunchDeskTemplate(
       desk_template->uuid(),
       base::BindOnce(&FloatingWorkspaceService::OnTemplateLaunched,
@@ -404,8 +506,6 @@
 void FloatingWorkspaceService::OnTemplateLaunched(
     absl::optional<DesksClient::DeskActionError> error,
     const base::Uuid& desk_uuid) {
-  // Disable future floating workspace restore.
-  should_run_restore_ = false;
   if (error) {
     HandleTemplateUploadErrors(error.value());
     return;
@@ -478,4 +578,77 @@
   return (*iter)->uuid();
 }
 
+void FloatingWorkspaceService::HandleSyncEror() {
+  SendNotification(kNotificationForSyncErrorOrTimeOut);
+}
+
+void FloatingWorkspaceService::MaybeHandleDownloadTimeOut() {
+  if (!should_run_restore_) {
+    return;
+  }
+  // Record timeout metrics.
+  floating_workspace_metrics_util::
+      RecordFloatingWorkspaceV2TemplateLaunchTimeout(
+          floating_workspace_metrics_util::LaunchTemplateTimeoutType::
+              kPassedWaitPeriod);
+  SendNotification(kNotificationForSyncErrorOrTimeOut);
+}
+
+void FloatingWorkspaceService::SendNotification(const std::string& id) {
+  // If there is a previous notification for floating workspace, close it.
+  MaybeCloseNotification();
+
+  message_center::RichNotificationData notification_data;
+  std::u16string title, message;
+  message_center::SystemNotificationWarningLevel warning_level;
+  switch (GetNotificationTypeById(id)) {
+    case FloatingWorkspaceServiceNotificationType::kNoNetworkConnection:
+      title =
+          l10n_util::GetStringUTF16(IDS_FLOATING_WORKSPACE_NO_NETWORK_TITLE);
+      message =
+          l10n_util::GetStringUTF16(IDS_FLOATING_WORKSPACE_NO_NETWORK_MESSAGE);
+      warning_level =
+          message_center::SystemNotificationWarningLevel::CRITICAL_WARNING;
+      notification_data.buttons.emplace_back(
+          l10n_util::GetStringUTF16(IDS_FLOATING_WORKSPACE_NO_NETWORK_BUTTON));
+      break;
+    case FloatingWorkspaceServiceNotificationType::kSyncErrorOrTimeOut:
+      title = l10n_util::GetStringUTF16(IDS_FLOATING_WORKSPACE_ERROR_TITLE);
+      message = l10n_util::GetStringUTF16(IDS_FLOATING_WORKSPACE_ERROR_MESSAGE);
+      warning_level =
+          message_center::SystemNotificationWarningLevel::CRITICAL_WARNING;
+      break;
+    case FloatingWorkspaceServiceNotificationType::kRestoreAfterError:
+      title = l10n_util::GetStringUTF16(
+          IDS_FLOATING_WORKSPACE_RESTORE_FROM_ERROR_TITLE);
+      message = l10n_util::GetStringUTF16(
+          IDS_FLOATING_WORKSPACE_RESTORE_FROM_ERROR_MESSAGE);
+      warning_level = message_center::SystemNotificationWarningLevel::NORMAL;
+      notification_data.buttons.emplace_back(l10n_util::GetStringUTF16(
+          IDS_FLOATING_WORKSPACE_RESTORE_FROM_ERROR_RESTORATION_BUTTON));
+      break;
+    case FloatingWorkspaceServiceNotificationType::kUnknown:
+      VLOG(2) << "Unknown notification type for floating workspace, skip "
+                 "sending notification";
+      return;
+  }
+
+  notification_ = CreateSystemNotificationPtr(
+      message_center::NOTIFICATION_TYPE_SIMPLE, id, title, message,
+      l10n_util::GetStringUTF16(IDS_FLOATING_WORKSPACE_DISPLAY_SOURCE), GURL(),
+      message_center::NotifierId(message_center::NotifierType::SYSTEM_COMPONENT,
+                                 id,
+                                 NotificationCatalogName::kFloatingWorkspace),
+      notification_data,
+      base::MakeRefCounted<message_center::ThunkNotificationDelegate>(
+          weak_pointer_factory_.GetWeakPtr()),
+      kFloatingWorkspaceNotificationIcon, warning_level);
+  notification_->set_priority(message_center::SYSTEM_PRIORITY);
+  auto* notification_display_service =
+      NotificationDisplayService::GetForProfile(profile_);
+  notification_display_service->Display(NotificationHandler::Type::TRANSIENT,
+                                        *notification_,
+                                        /*metadata=*/nullptr);
+}
+
 }  // namespace ash
diff --git a/chrome/browser/ash/floating_workspace/floating_workspace_service.h b/chrome/browser/ash/floating_workspace/floating_workspace_service.h
index eb177ad..1000aa9 100644
--- a/chrome/browser/ash/floating_workspace/floating_workspace_service.h
+++ b/chrome/browser/ash/floating_workspace/floating_workspace_service.h
@@ -13,12 +13,16 @@
 #include "base/time/time.h"
 #include "base/timer/timer.h"
 #include "base/uuid.h"
+#include "chrome/browser/ash/floating_workspace/floating_workspace_util.h"
 #include "chrome/browser/ui/ash/desks/desks_client.h"
 #include "components/desks_storage/core/desk_model.h"
 #include "components/desks_storage/core/desk_model_observer.h"
 #include "components/desks_storage/core/desk_sync_bridge.h"
 #include "components/desks_storage/core/desk_sync_service.h"
 #include "components/keyed_service/core/keyed_service.h"
+#include "components/sync/service/sync_service.h"
+#include "components/sync/service/sync_service_observer.h"
+#include "ui/message_center/public/cpp/notification.h"
 
 class Profile;
 
@@ -28,41 +32,44 @@
 struct SyncedSession;
 }  // namespace sync_sessions
 
-namespace desks_storage {
-class DeskModelObserver;
-}  // namespace desks_storage
-
 namespace ash {
 
-// Testing enum to substitute for feature flag during testing.
-enum TestFloatingWorkspaceVersion {
-  // Default value, indicates no version was enabled.
-  kNoVersionEnabled = 0,
+extern const char kNotificationForNoNetworkConnection[];
+extern const char kNotificationForSyncErrorOrTimeOut[];
+extern const char kNotificationForRestoreAfterError[];
 
-  // Version 1.
-  kFloatingWorkspaceV1Enabled = 1,
+// The restore from error notification button index.
+enum class RestoreFromErrorNotificationButtonIndex {
+  kRestore = 0,
+  kCancel,
+};
 
-  // Version 2.
-  kFloatingWorkspaceV2Enabled = 2,
+// The notification type for floating workspace service.
+enum class FloatingWorkspaceServiceNotificationType {
+  kUnknown = 0,
+  kNoNetworkConnection,
+  kSyncErrorOrTimeOut,
+  kRestoreAfterError,
 };
 
 // A keyed service to support floating workspace. Note that a periodical
 // task `CaptureAndUploadActiveDesk` will be dispatched during service
 // initialization.
 class FloatingWorkspaceService : public KeyedService,
-                                 public desks_storage::DeskModelObserver {
+                                 public message_center::NotificationObserver,
+                                 public syncer::SyncServiceObserver {
  public:
   static FloatingWorkspaceService* GetForProfile(Profile* profile);
 
-  explicit FloatingWorkspaceService(Profile* profile);
+  explicit FloatingWorkspaceService(
+      Profile* profile,
+      floating_workspace_util::FloatingWorkspaceVersion version);
 
   ~FloatingWorkspaceService() override;
 
   // Used in constructor for initializations
-  void Init();
-  void InitForTest(
-      TestFloatingWorkspaceVersion version,
-      raw_ptr<desks_storage::DeskSyncService> fake_desk_sync_service);
+  void Init(syncer::SyncService* sync_service,
+            desks_storage::DeskSyncService* desk_sync_service);
 
   // Add subscription to foreign session changes.
   void SubscribeToForeignSessionUpdates();
@@ -76,19 +83,24 @@
   void CaptureAndUploadActiveDeskForTest(
       std::unique_ptr<DeskTemplate> desk_template);
 
-  // desks_storage::DeskModelObserver overrides:
-  void DeskModelLoaded() override {}
-  void OnDeskModelDestroying() override;
-  void EntriesAddedOrUpdatedRemotely(
-      const std::vector<const DeskTemplate*>& new_entries) override;
-  void EntriesRemovedRemotely(const std::vector<base::Uuid>& uuids) override {}
+  // syncer::SyncServiceObserver overrides:
+  void OnStateChanged(syncer::SyncService* sync) override;
+
+  // message_center::NotificationObserver overrides:
+  void Click(const absl::optional<int>& button_index,
+             const absl::optional<std::u16string>& reply) override;
+
+  void MaybeCloseNotification();
 
  protected:
   std::unique_ptr<DeskTemplate> previously_captured_desk_template_;
+  // Indicate if it is a testing class.
+  bool is_testing_ = false;
 
  private:
   void InitForV1();
-  void InitForV2();
+  void InitForV2(syncer::SyncService* sync_service,
+                 desks_storage::DeskSyncService* desk_sync_service);
 
   const sync_sessions::SyncedSession* GetMostRecentlyUsedRemoteSession();
 
@@ -108,6 +120,9 @@
   void StartCaptureAndUploadActiveDesk();
   void StopCaptureAndUploadActiveDesk();
 
+  // Get latest Floating Workspace Template from DeskSyncBridge.
+  const DeskTemplate* GetLatestFloatingWorkspaceTemplate();
+
   // Capture the current active desk task, running every ~30(TBD) seconds.
   // Upload captured desk to chrome sync and record the randomly generated
   // UUID key to `floating_workspace_template_uuid_`.
@@ -156,9 +171,22 @@
   // an absl::nullopt if there is no floating workspace uuid that is associated
   // with the current device.
   absl::optional<base::Uuid> GetFloatingWorkspaceUuidForCurrentDevice();
+  // When sync passes an error status to floating workspace service,
+  // floating workspace service should send notification to user asking whether
+  // to restore the most recent FWS desk from local storage.
+  void HandleSyncEror();
+
+  // When floating workspace service waited long enough but no desk is restored
+  // floating workspace service should send notification to user asking whether
+  // to restore the most recent FWS desk from local storage.
+  void MaybeHandleDownloadTimeOut();
+
+  void SendNotification(const std::string& id);
 
   const raw_ptr<Profile, ExperimentalAsh> profile_;
 
+  const floating_workspace_util::FloatingWorkspaceVersion version_;
+
   raw_ptr<sync_sessions::SessionSyncService, ExperimentalAsh>
       session_sync_service_;
 
@@ -173,17 +201,21 @@
   // Timer used for periodic capturing and uploading.
   base::RepeatingTimer timer_;
 
+  // Timer used to wait for internet connection after service initialization.
+  base::OneShotTimer connection_timer_;
+
   // Convenience pointer to desks_storage::DeskSyncService. Guaranteed to be not
   // null for the duration of `this`.
   raw_ptr<desks_storage::DeskSyncService> desk_sync_service_ = nullptr;
 
-  // Indicate if it is a testing class.
-  bool is_testing_ = false;
+  raw_ptr<syncer::SyncService> sync_service_ = nullptr;
 
   // The uuid associated with this device's floating workspace template. This is
   // populated when we first capture a floating workspace template.
   absl::optional<base::Uuid> floating_workspace_uuid_;
 
+  std::unique_ptr<message_center::Notification> notification_;
+
   // Weak pointer factory used to provide references to this service.
   base::WeakPtrFactory<FloatingWorkspaceService> weak_pointer_factory_{this};
 };
diff --git a/chrome/browser/ash/floating_workspace/floating_workspace_service_factory.cc b/chrome/browser/ash/floating_workspace/floating_workspace_service_factory.cc
index c143b91..f092392 100644
--- a/chrome/browser/ash/floating_workspace/floating_workspace_service_factory.cc
+++ b/chrome/browser/ash/floating_workspace/floating_workspace_service_factory.cc
@@ -6,6 +6,7 @@
 
 #include "base/no_destructor.h"
 #include "chrome/browser/ash/floating_workspace/floating_workspace_service.h"
+#include "chrome/browser/ash/floating_workspace/floating_workspace_util.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/sync/desk_sync_service_factory.h"
 #include "chrome/browser/sync/session_sync_service_factory.h"
@@ -45,9 +46,20 @@
 
 KeyedService* FloatingWorkspaceServiceFactory::BuildServiceInstanceFor(
     content::BrowserContext* context) const {
+  Profile* profile = Profile::FromBrowserContext(context);
+  floating_workspace_util::FloatingWorkspaceVersion version =
+      floating_workspace_util::FloatingWorkspaceVersion::kNoVersionEnabled;
+  if (floating_workspace_util::IsFloatingWorkspaceV1Enabled()) {
+    version = floating_workspace_util::FloatingWorkspaceVersion::
+        kFloatingWorkspaceV1Enabled;
+  } else if (floating_workspace_util::IsFloatingWorkspaceV2Enabled()) {
+    version = floating_workspace_util::FloatingWorkspaceVersion::
+        kFloatingWorkspaceV2Enabled;
+  }
   FloatingWorkspaceService* service =
-      new FloatingWorkspaceService(Profile::FromBrowserContext(context));
-  service->Init();
+      new FloatingWorkspaceService(profile, version);
+  service->Init(SyncServiceFactory::GetForProfile(profile),
+                DeskSyncServiceFactory::GetForProfile(profile));
   return service;
 }
 
diff --git a/chrome/browser/ash/floating_workspace/floating_workspace_service_unittest.cc b/chrome/browser/ash/floating_workspace/floating_workspace_service_unittest.cc
index a697d6b..3326312 100644
--- a/chrome/browser/ash/floating_workspace/floating_workspace_service_unittest.cc
+++ b/chrome/browser/ash/floating_workspace/floating_workspace_service_unittest.cc
@@ -2,37 +2,51 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 #include "chrome/browser/ash/floating_workspace/floating_workspace_service.h"
+#include <memory>
 #include "ash/constants/ash_features.h"
 #include "ash/public/cpp/desk_template.h"
 #include "ash/wm/desks/templates/saved_desk_metrics_util.h"
+#include "base/allocator/partition_allocator/pointers/raw_ptr.h"
 #include "base/files/scoped_temp_dir.h"
 #include "base/memory/raw_ptr.h"
 #include "base/ranges/algorithm.h"
 #include "base/strings/utf_string_conversions.h"
+#include "base/test/bind.h"
 #include "base/test/metrics/histogram_tester.h"
 #include "base/test/task_environment.h"
 #include "base/time/time.h"
 #include "chrome/browser/ash/floating_workspace/floating_workspace_metrics_util.h"
+#include "chrome/browser/ash/floating_workspace/floating_workspace_util.h"
+#include "chrome/browser/notifications/notification_display_service_tester.h"
 #include "chrome/browser/ui/ash/desks/desks_client.h"
 #include "chrome/test/base/testing_profile.h"
+#include "chromeos/ash/components/network/network_handler.h"
+#include "chromeos/ash/services/network_config/public/cpp/cros_network_config_test_helper.h"
 #include "components/app_restore/app_launch_info.h"
 #include "components/app_restore/full_restore_utils.h"
 #include "components/app_restore/window_info.h"
 #include "components/app_restore/window_properties.h"
 #include "components/desks_storage/core/fake_desk_sync_service.h"
+#include "components/sync/base/model_type.h"
+#include "components/sync/service/sync_service.h"
+#include "components/sync/test/test_sync_service.h"
 #include "components/sync_sessions/open_tabs_ui_delegate.h"
 #include "components/sync_sessions/synced_session.h"
 #include "content/public/test/browser_task_environment.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/cros_system_api/dbus/shill/dbus-constants.h"
+#include "ui/message_center/public/cpp/notification.h"
 
-namespace ash {
+namespace ash::floating_workspace {
 
 namespace {
 
-constexpr char local_session_name[] = "local_session";
-constexpr char remote_session_1_name[] = "remote_session_1";
-constexpr char remote_session_2_name[] = "remote_session_2";
+constexpr char kLocalSessionName[] = "local_session";
+constexpr char kRemoteSessionOneName[] = "remote_session_1";
+constexpr char kRemoteSession2Name[] = "remote_session_2";
+constexpr char kDevicePath[] = "test_device_path";
+constexpr char kDeviceName[] = "test_device_name";
 const base::Time most_recent_time = base::Time::FromDoubleT(15);
 const base::Time more_recent_time = base::Time::FromDoubleT(10);
 const base::Time least_recent_time = base::Time::FromDoubleT(5);
@@ -151,23 +165,35 @@
       nullptr;
 };
 
+class MockSyncService : public syncer::TestSyncService {
+ public:
+  MockSyncService() = default;
+
+  ModelTypeDownloadStatus GetDownloadStatusFor(
+      syncer::ModelType type) const override {
+    return download_status_;
+  }
+
+  void SetDownloadStatus(ModelTypeDownloadStatus status) {
+    download_status_ = status;
+  }
+
+ private:
+  ModelTypeDownloadStatus download_status_;
+};
+
 }  // namespace
 
 class TestFloatingWorkSpaceService : public FloatingWorkspaceService {
  public:
-  explicit TestFloatingWorkSpaceService(TestingProfile* profile,
-                                        TestFloatingWorkspaceVersion version)
-      : FloatingWorkspaceService(profile) {
-    InitForTest(version, nullptr);
-    mock_open_tabs_ = std::make_unique<MockOpenTabsUIDelegate>();
-    mock_desks_client_ = std::make_unique<MockDesksClient>();
-  }
   explicit TestFloatingWorkSpaceService(
       TestingProfile* profile,
-      TestFloatingWorkspaceVersion version,
-      raw_ptr<desks_storage::FakeDeskSyncService> fake_desk_sync_service)
-      : FloatingWorkspaceService(profile) {
-    InitForTest(version, fake_desk_sync_service);
+      raw_ptr<desks_storage::FakeDeskSyncService> fake_desk_sync_service,
+      raw_ptr<MockSyncService> mock_sync_service,
+      floating_workspace_util::FloatingWorkspaceVersion version)
+      : FloatingWorkspaceService(profile, version) {
+    is_testing_ = true;
+    Init(mock_sync_service, fake_desk_sync_service);
     mock_open_tabs_ = std::make_unique<MockOpenTabsUIDelegate>();
     mock_desks_client_ = std::make_unique<MockDesksClient>();
   }
@@ -247,6 +273,25 @@
     return scoped_feature_list_;
   }
 
+  NotificationDisplayServiceTester* display_service() {
+    return display_service_.get();
+  }
+
+  bool HasNotificationFor(const std::string& id) {
+    absl::optional<message_center::Notification> notification =
+        display_service()->GetNotification(id);
+    return notification.has_value();
+  }
+
+  void AddTestNetworkDevice() {
+    helper_.AddDevice(kDevicePath, shill::kTypeWifi, kDeviceName);
+    if (!NetworkHandler::IsInitialized()) {
+      NetworkHandler::Initialize();
+    }
+  }
+
+  void CleanUpTestNetworkDevices() { helper_.ClearDevices(); }
+
   void SetUp() override {
     TestingProfile::Builder profile_builder;
     base::ScopedTempDir temp_dir;
@@ -258,6 +303,9 @@
     fake_desk_sync_service_ =
         std::make_unique<desks_storage::FakeDeskSyncService>(
             /*skip_engine_connection=*/true);
+    display_service_ =
+        std::make_unique<NotificationDisplayServiceTester>(profile_.get());
+    AddTestNetworkDevice();
   }
 
  private:
@@ -265,23 +313,29 @@
       base::test::TaskEnvironment::TimeSource::MOCK_TIME};
   std::unique_ptr<TestingProfile> profile_;
   std::unique_ptr<desks_storage::FakeDeskSyncService> fake_desk_sync_service_;
+  std::unique_ptr<NotificationDisplayServiceTester> display_service_;
   base::test::ScopedFeatureList scoped_feature_list_;
+  ash::NetworkStateTestHelper helper_{
+      /*use_default_devices_and_services=*/false};
 };
 
 TEST_F(FloatingWorkspaceServiceTest, RestoreRemoteSession) {
   scoped_feature_list().InitWithFeatures({features::kFloatingWorkspace}, {});
   TestFloatingWorkSpaceService test_floating_workspace_service(
-      profile(), TestFloatingWorkspaceVersion::kFloatingWorkspaceV1Enabled);
+      profile(), /*fake_desk_sync_service=*/nullptr,
+      /*mock_sync_service=*/nullptr,
+      floating_workspace_util::FloatingWorkspaceVersion::
+          kFloatingWorkspaceV1Enabled);
   std::unique_ptr<sync_sessions::SyncedSession> local_session =
-      CreateNewSession(local_session_name, more_recent_time);
+      CreateNewSession(kLocalSessionName, more_recent_time);
   std::vector<const sync_sessions::SyncedSession*> foreign_sessions;
   // This remote session has most recent timestamp and should be restored.
   const std::unique_ptr<sync_sessions::SyncedSession>
       most_recent_remote_session =
-          CreateNewSession(remote_session_1_name, most_recent_time);
+          CreateNewSession(kRemoteSessionOneName, most_recent_time);
   const std::unique_ptr<sync_sessions::SyncedSession>
       less_recent_remote_session =
-          CreateNewSession(remote_session_2_name, least_recent_time);
+          CreateNewSession(kRemoteSession2Name, least_recent_time);
   foreign_sessions.push_back(less_recent_remote_session.get());
   foreign_sessions.push_back(most_recent_remote_session.get());
 
@@ -296,7 +350,7 @@
       base::Seconds(1));
   EXPECT_TRUE(test_floating_workspace_service.GetRestoredSession());
   EXPECT_EQ(
-      remote_session_1_name,
+      kRemoteSessionOneName,
       test_floating_workspace_service.GetRestoredSession()->GetSessionName());
   scoped_feature_list().Reset();
 }
@@ -304,17 +358,20 @@
 TEST_F(FloatingWorkspaceServiceTest, RestoreLocalSession) {
   scoped_feature_list().InitWithFeatures({features::kFloatingWorkspace}, {});
   TestFloatingWorkSpaceService test_floating_workspace_service(
-      profile(), TestFloatingWorkspaceVersion::kFloatingWorkspaceV1Enabled);
+      profile(), /*fake_desk_sync_service=*/nullptr,
+      /*mock_sync_service=*/nullptr,
+      floating_workspace_util::FloatingWorkspaceVersion::
+          kFloatingWorkspaceV1Enabled);
   // Local session has most recent timestamp and should be restored.
   std::unique_ptr<sync_sessions::SyncedSession> local_session =
-      CreateNewSession(local_session_name, most_recent_time);
+      CreateNewSession(kLocalSessionName, most_recent_time);
   std::vector<const sync_sessions::SyncedSession*> foreign_sessions;
   const std::unique_ptr<sync_sessions::SyncedSession>
       most_recent_remote_session =
-          CreateNewSession(remote_session_1_name, more_recent_time);
+          CreateNewSession(kRemoteSessionOneName, more_recent_time);
   const std::unique_ptr<sync_sessions::SyncedSession>
       less_recent_remote_session =
-          CreateNewSession(remote_session_2_name, least_recent_time);
+          CreateNewSession(kRemoteSession2Name, least_recent_time);
   foreign_sessions.push_back(less_recent_remote_session.get());
   foreign_sessions.push_back(most_recent_remote_session.get());
 
@@ -329,7 +386,7 @@
       base::Seconds(1));
   EXPECT_TRUE(test_floating_workspace_service.GetRestoredSession());
   EXPECT_EQ(
-      local_session_name,
+      kLocalSessionName,
       test_floating_workspace_service.GetRestoredSession()->GetSessionName());
   scoped_feature_list().Reset();
 }
@@ -337,17 +394,20 @@
 TEST_F(FloatingWorkspaceServiceTest, RestoreRemoteSessionAfterUpdated) {
   scoped_feature_list().InitWithFeatures({features::kFloatingWorkspace}, {});
   TestFloatingWorkSpaceService test_floating_workspace_service(
-      profile(), TestFloatingWorkspaceVersion::kFloatingWorkspaceV1Enabled);
+      profile(), /*fake_desk_sync_service=*/nullptr,
+      /*mock_sync_service=*/nullptr,
+      floating_workspace_util::FloatingWorkspaceVersion::
+          kFloatingWorkspaceV1Enabled);
   // Local session has most recent timestamp and should be restored.
   std::unique_ptr<sync_sessions::SyncedSession> local_session =
-      CreateNewSession(local_session_name, most_recent_time);
+      CreateNewSession(kLocalSessionName, most_recent_time);
   std::vector<const sync_sessions::SyncedSession*> foreign_sessions;
   const std::unique_ptr<sync_sessions::SyncedSession>
       most_recent_remote_session =
-          CreateNewSession(remote_session_1_name, more_recent_time);
+          CreateNewSession(kRemoteSessionOneName, more_recent_time);
   const std::unique_ptr<sync_sessions::SyncedSession>
       less_recent_remote_session =
-          CreateNewSession(remote_session_2_name, least_recent_time);
+          CreateNewSession(kRemoteSession2Name, least_recent_time);
   foreign_sessions.push_back(less_recent_remote_session.get());
   foreign_sessions.push_back(most_recent_remote_session.get());
 
@@ -381,14 +441,17 @@
 TEST_F(FloatingWorkspaceServiceTest, NoLocalSession) {
   scoped_feature_list().InitWithFeatures({features::kFloatingWorkspace}, {});
   TestFloatingWorkSpaceService test_floating_workspace_service(
-      profile(), TestFloatingWorkspaceVersion::kFloatingWorkspaceV1Enabled);
+      profile(), /*fake_desk_sync_service=*/nullptr,
+      /*mock_sync_service=*/nullptr,
+      floating_workspace_util::FloatingWorkspaceVersion::
+          kFloatingWorkspaceV1Enabled);
   std::vector<const sync_sessions::SyncedSession*> foreign_sessions;
   const std::unique_ptr<sync_sessions::SyncedSession>
       most_recent_remote_session =
-          CreateNewSession(remote_session_1_name, more_recent_time);
+          CreateNewSession(kRemoteSessionOneName, more_recent_time);
   const std::unique_ptr<sync_sessions::SyncedSession>
       less_recent_remote_session =
-          CreateNewSession(remote_session_2_name, least_recent_time);
+          CreateNewSession(kRemoteSession2Name, least_recent_time);
   foreign_sessions.push_back(less_recent_remote_session.get());
   foreign_sessions.push_back(most_recent_remote_session.get());
   test_floating_workspace_service.SetForeignSessionForTesting(foreign_sessions);
@@ -410,9 +473,12 @@
 TEST_F(FloatingWorkspaceServiceTest, NoRemoteSession) {
   scoped_feature_list().InitWithFeatures({features::kFloatingWorkspace}, {});
   TestFloatingWorkSpaceService test_floating_workspace_service(
-      profile(), TestFloatingWorkspaceVersion::kFloatingWorkspaceV1Enabled);
+      profile(), /*fake_desk_sync_service=*/nullptr,
+      /*mock_sync_service=*/nullptr,
+      floating_workspace_util::FloatingWorkspaceVersion::
+          kFloatingWorkspaceV1Enabled);
   std::unique_ptr<sync_sessions::SyncedSession> local_session =
-      CreateNewSession(local_session_name, least_recent_time);
+      CreateNewSession(kLocalSessionName, least_recent_time);
   test_floating_workspace_service.SetLocalSessionForTesting(
       local_session.get());
   test_floating_workspace_service
@@ -424,7 +490,7 @@
 
   EXPECT_TRUE(test_floating_workspace_service.GetRestoredSession());
   EXPECT_EQ(
-      local_session_name,
+      kLocalSessionName,
       test_floating_workspace_service.GetRestoredSession()->GetSessionName());
   scoped_feature_list().Reset();
 }
@@ -432,7 +498,10 @@
 TEST_F(FloatingWorkspaceServiceTest, NoSession) {
   scoped_feature_list().InitWithFeatures({features::kFloatingWorkspace}, {});
   TestFloatingWorkSpaceService test_floating_workspace_service(
-      profile(), TestFloatingWorkspaceVersion::kFloatingWorkspaceV1Enabled);
+      profile(), /*fake_desk_sync_service=*/nullptr,
+      /*mock_sync_service=*/nullptr,
+      floating_workspace_util::FloatingWorkspaceVersion::
+          kFloatingWorkspaceV1Enabled);
   test_floating_workspace_service
       .RestoreBrowserWindowsFromMostRecentlyUsedDevice();
   // Wait for kFloatingWorkspaceMaxTimeAvailableForRestoreAfterLogin seconds.
@@ -449,16 +518,30 @@
       {features::kFloatingWorkspaceV2, features::kDesksTemplates,
        features::kDeskTemplateSync},
       {});
-  TestFloatingWorkSpaceService test_floating_workspace_service_v2(
-      profile(), TestFloatingWorkspaceVersion::kFloatingWorkspaceV2Enabled,
-      fake_desk_sync_service());
-  std::vector<const DeskTemplate*> desk_template_entries;
+
   const std::string template_name = "floating_workspace_template";
-  std::unique_ptr<const DeskTemplate> floating_workspace_template =
-      MakeTestFloatingWorkspaceDeskTemplate(template_name, base::Time::Now());
-  desk_template_entries.push_back(floating_workspace_template.get());
-  test_floating_workspace_service_v2.EntriesAddedOrUpdatedRemotely(
-      desk_template_entries);
+  base::RunLoop loop;
+  fake_desk_sync_service()->GetDeskModel()->AddOrUpdateEntry(
+      MakeTestFloatingWorkspaceDeskTemplate(template_name, base::Time::Now()),
+      base::BindLambdaForTesting(
+          [&](desks_storage::DeskModel::AddOrUpdateEntryStatus status,
+              std::unique_ptr<ash::DeskTemplate> new_entry) {
+            EXPECT_EQ(desks_storage::DeskModel::AddOrUpdateEntryStatus::kOk,
+                      status);
+            loop.Quit();
+          }));
+  loop.Run();
+  std::unique_ptr<MockSyncService> mock_sync_service =
+      std::make_unique<MockSyncService>();
+
+  TestFloatingWorkSpaceService test_floating_workspace_service_v2(
+      profile(), fake_desk_sync_service(), mock_sync_service.get(),
+      floating_workspace_util::FloatingWorkspaceVersion::
+          kFloatingWorkspaceV2Enabled);
+
+  mock_sync_service->SetDownloadStatus(
+      syncer::SyncService::ModelTypeDownloadStatus::kUpToDate);
+  test_floating_workspace_service_v2.OnStateChanged(mock_sync_service.get());
   EXPECT_TRUE(test_floating_workspace_service_v2
                   .GetRestoredFloatingWorkspaceTemplate());
   EXPECT_EQ(
@@ -468,25 +551,121 @@
   scoped_feature_list().Reset();
 }
 
-TEST_F(FloatingWorkspaceServiceTest, FloatingWorkspaceTemplateTimeOut) {
+TEST_F(FloatingWorkspaceServiceTest, NoNetworkForFloatingWorkspaceTemplate) {
   scoped_feature_list().InitWithFeatures(
       {features::kFloatingWorkspaceV2, features::kDesksTemplates,
        features::kDeskTemplateSync},
       {});
-  TestFloatingWorkSpaceService test_floating_workspace_service_v2(
-      profile(), TestFloatingWorkspaceVersion::kFloatingWorkspaceV2Enabled,
-      fake_desk_sync_service());
-  std::vector<const DeskTemplate*> desk_template_entries;
+  CleanUpTestNetworkDevices();
   const std::string template_name = "floating_workspace_template";
-  std::unique_ptr<const DeskTemplate> floating_workspace_template =
-      MakeTestFloatingWorkspaceDeskTemplate(template_name, base::Time::Now());
-  desk_template_entries.push_back(floating_workspace_template.get());
+  base::RunLoop loop;
+
+  std::unique_ptr<MockSyncService> mock_sync_service =
+      std::make_unique<MockSyncService>();
+  TestFloatingWorkSpaceService test_floating_workspace_service_v2(
+      profile(), fake_desk_sync_service(), mock_sync_service.get(),
+      floating_workspace_util::FloatingWorkspaceVersion::
+          kFloatingWorkspaceV2Enabled);
+
+  EXPECT_TRUE(HasNotificationFor(kNotificationForNoNetworkConnection));
+  scoped_feature_list().Reset();
+}
+
+TEST_F(FloatingWorkspaceServiceTest,
+       FloatingWorkspaceTemplateRestoreAfterTimeOut) {
+  scoped_feature_list().InitWithFeatures(
+      {features::kFloatingWorkspaceV2, features::kDesksTemplates,
+       features::kDeskTemplateSync},
+      {});
+  const std::string template_name = "floating_workspace_template";
+  base::RunLoop loop;
+  fake_desk_sync_service()->GetDeskModel()->AddOrUpdateEntry(
+      MakeTestFloatingWorkspaceDeskTemplate(template_name, base::Time::Now()),
+      base::BindLambdaForTesting(
+          [&](desks_storage::DeskModel::AddOrUpdateEntryStatus status,
+              std::unique_ptr<ash::DeskTemplate> new_entry) {
+            EXPECT_EQ(desks_storage::DeskModel::AddOrUpdateEntryStatus::kOk,
+                      status);
+            loop.Quit();
+          }));
+  loop.Run();
+  std::unique_ptr<MockSyncService> mock_sync_service =
+      std::make_unique<MockSyncService>();
+  TestFloatingWorkSpaceService test_floating_workspace_service_v2(
+      profile(), fake_desk_sync_service(), mock_sync_service.get(),
+      floating_workspace_util::FloatingWorkspaceVersion::
+          kFloatingWorkspaceV2Enabled);
+
   task_environment().FastForwardBy(
       ash::features::kFloatingWorkspaceV2MaxTimeAvailableForRestoreAfterLogin
           .Get() +
       base::Seconds(1));
-  test_floating_workspace_service_v2.EntriesAddedOrUpdatedRemotely(
-      desk_template_entries);
+  EXPECT_TRUE(HasNotificationFor(kNotificationForSyncErrorOrTimeOut));
+
+  // Download completes after timeout.
+  mock_sync_service->SetDownloadStatus(
+      syncer::SyncService::ModelTypeDownloadStatus::kUpToDate);
+  test_floating_workspace_service_v2.OnStateChanged(mock_sync_service.get());
+  EXPECT_FALSE(test_floating_workspace_service_v2
+                   .GetRestoredFloatingWorkspaceTemplate());
+  EXPECT_TRUE(HasNotificationFor(kNotificationForRestoreAfterError));
+  // User clicks restore button on the notification.
+  display_service()->SimulateClick(
+      NotificationHandler::Type::TRANSIENT, kNotificationForRestoreAfterError,
+      static_cast<int>(RestoreFromErrorNotificationButtonIndex::kRestore),
+      absl::nullopt);
+  EXPECT_TRUE(test_floating_workspace_service_v2
+                  .GetRestoredFloatingWorkspaceTemplate());
+  EXPECT_EQ(
+      test_floating_workspace_service_v2.GetRestoredFloatingWorkspaceTemplate()
+          ->template_name(),
+      base::UTF8ToUTF16(template_name));
+  scoped_feature_list().Reset();
+}
+
+TEST_F(FloatingWorkspaceServiceTest,
+       FloatingWorkspaceTemplateDiscardAfterTimeOut) {
+  scoped_feature_list().InitWithFeatures(
+      {features::kFloatingWorkspaceV2, features::kDesksTemplates,
+       features::kDeskTemplateSync},
+      {});
+  const std::string template_name = "floating_workspace_template";
+  base::RunLoop loop;
+  fake_desk_sync_service()->GetDeskModel()->AddOrUpdateEntry(
+      MakeTestFloatingWorkspaceDeskTemplate(template_name, base::Time::Now()),
+      base::BindLambdaForTesting(
+          [&](desks_storage::DeskModel::AddOrUpdateEntryStatus status,
+              std::unique_ptr<ash::DeskTemplate> new_entry) {
+            EXPECT_EQ(desks_storage::DeskModel::AddOrUpdateEntryStatus::kOk,
+                      status);
+            loop.Quit();
+          }));
+  loop.Run();
+  std::unique_ptr<MockSyncService> mock_sync_service =
+      std::make_unique<MockSyncService>();
+  TestFloatingWorkSpaceService test_floating_workspace_service_v2(
+      profile(), fake_desk_sync_service(), mock_sync_service.get(),
+      floating_workspace_util::FloatingWorkspaceVersion::
+          kFloatingWorkspaceV2Enabled);
+
+  task_environment().FastForwardBy(
+      ash::features::kFloatingWorkspaceV2MaxTimeAvailableForRestoreAfterLogin
+          .Get() +
+      base::Seconds(1));
+  EXPECT_TRUE(HasNotificationFor(kNotificationForSyncErrorOrTimeOut));
+
+  // Download completes after timeout.
+  mock_sync_service->SetDownloadStatus(
+      syncer::SyncService::ModelTypeDownloadStatus::kUpToDate);
+  test_floating_workspace_service_v2.OnStateChanged(mock_sync_service.get());
+  EXPECT_FALSE(test_floating_workspace_service_v2
+                   .GetRestoredFloatingWorkspaceTemplate());
+  EXPECT_TRUE(HasNotificationFor(kNotificationForRestoreAfterError));
+  // User clicks restore button on the notification.
+  display_service()->SimulateClick(
+      NotificationHandler::Type::TRANSIENT, kNotificationForRestoreAfterError,
+      static_cast<int>(RestoreFromErrorNotificationButtonIndex::kCancel),
+      absl::nullopt);
   EXPECT_FALSE(test_floating_workspace_service_v2
                    .GetRestoredFloatingWorkspaceTemplate());
   scoped_feature_list().Reset();
@@ -497,17 +676,29 @@
       {features::kFloatingWorkspaceV2, features::kDesksTemplates,
        features::kDeskTemplateSync},
       {});
-  base::HistogramTester histogram_tester;
-  TestFloatingWorkSpaceService test_floating_workspace_service_v2(
-      profile(), TestFloatingWorkspaceVersion::kFloatingWorkspaceV2Enabled,
-      fake_desk_sync_service());
-  std::vector<const DeskTemplate*> desk_template_entries;
   const std::string template_name = "floating_workspace_template";
-  std::unique_ptr<const DeskTemplate> floating_workspace_template =
-      MakeTestFloatingWorkspaceDeskTemplate(template_name, base::Time::Now());
-  desk_template_entries.push_back(floating_workspace_template.get());
-  test_floating_workspace_service_v2.EntriesAddedOrUpdatedRemotely(
-      desk_template_entries);
+  base::RunLoop loop;
+  fake_desk_sync_service()->GetDeskModel()->AddOrUpdateEntry(
+      MakeTestFloatingWorkspaceDeskTemplate(template_name, base::Time::Now()),
+      base::BindLambdaForTesting(
+          [&](desks_storage::DeskModel::AddOrUpdateEntryStatus status,
+              std::unique_ptr<ash::DeskTemplate> new_entry) {
+            EXPECT_EQ(desks_storage::DeskModel::AddOrUpdateEntryStatus::kOk,
+                      status);
+            loop.Quit();
+          }));
+  loop.Run();
+  base::HistogramTester histogram_tester;
+  std::unique_ptr<MockSyncService> mock_sync_service =
+      std::make_unique<MockSyncService>();
+  TestFloatingWorkSpaceService test_floating_workspace_service_v2(
+      profile(), fake_desk_sync_service(), mock_sync_service.get(),
+      floating_workspace_util::FloatingWorkspaceVersion::
+          kFloatingWorkspaceV2Enabled);
+
+  mock_sync_service->SetDownloadStatus(
+      syncer::SyncService::ModelTypeDownloadStatus::kUpToDate);
+  test_floating_workspace_service_v2.OnStateChanged(mock_sync_service.get());
   EXPECT_TRUE(test_floating_workspace_service_v2
                   .GetRestoredFloatingWorkspaceTemplate());
   EXPECT_EQ(
@@ -526,20 +717,34 @@
        features::kDeskTemplateSync},
       {});
   base::HistogramTester histogram_tester;
-  TestFloatingWorkSpaceService test_floating_workspace_service_v2(
-      profile(), TestFloatingWorkspaceVersion::kFloatingWorkspaceV2Enabled,
-      fake_desk_sync_service());
-  std::vector<const DeskTemplate*> desk_template_entries;
+
   const std::string template_name = "floating_workspace_template";
-  std::unique_ptr<const DeskTemplate> floating_workspace_template =
-      MakeTestFloatingWorkspaceDeskTemplate(template_name, base::Time::Now());
-  desk_template_entries.push_back(floating_workspace_template.get());
+  base::RunLoop loop;
+  fake_desk_sync_service()->GetDeskModel()->AddOrUpdateEntry(
+      MakeTestFloatingWorkspaceDeskTemplate(template_name, base::Time::Now()),
+      base::BindLambdaForTesting(
+          [&](desks_storage::DeskModel::AddOrUpdateEntryStatus status,
+              std::unique_ptr<ash::DeskTemplate> new_entry) {
+            EXPECT_EQ(desks_storage::DeskModel::AddOrUpdateEntryStatus::kOk,
+                      status);
+            loop.Quit();
+          }));
+  loop.Run();
+  std::unique_ptr<MockSyncService> mock_sync_service =
+      std::make_unique<MockSyncService>();
+  TestFloatingWorkSpaceService test_floating_workspace_service_v2(
+      profile(), fake_desk_sync_service(), mock_sync_service.get(),
+      floating_workspace_util::FloatingWorkspaceVersion::
+          kFloatingWorkspaceV2Enabled);
+
   task_environment().FastForwardBy(
       ash::features::kFloatingWorkspaceV2MaxTimeAvailableForRestoreAfterLogin
           .Get() +
       base::Seconds(1));
-  test_floating_workspace_service_v2.EntriesAddedOrUpdatedRemotely(
-      desk_template_entries);
+
+  // mock_sync_service->SetDownloadStatus(
+  //     syncer::SyncService::ModelTypeDownloadStatus::kUpToDate);
+  // test_floating_workspace_service_v2.OnStateChanged(mock_sync_service.get());
   EXPECT_FALSE(test_floating_workspace_service_v2
                    .GetRestoredFloatingWorkspaceTemplate());
   histogram_tester.ExpectTotalCount(
@@ -560,9 +765,13 @@
       {features::kFloatingWorkspaceV2, features::kDesksTemplates,
        features::kDeskTemplateSync},
       {});
+  std::unique_ptr<MockSyncService> mock_sync_service =
+      std::make_unique<MockSyncService>();
   TestFloatingWorkSpaceService test_floating_workspace_service_v2(
-      profile(), TestFloatingWorkspaceVersion::kFloatingWorkspaceV2Enabled,
-      fake_desk_sync_service());
+      profile(), fake_desk_sync_service(), mock_sync_service.get(),
+      floating_workspace_util::FloatingWorkspaceVersion::
+          kFloatingWorkspaceV2Enabled);
+
   const std::string template_name = "floating_workspace_captured_template";
   const base::Time creation_time = base::Time::Now();
   std::unique_ptr<DeskTemplate> floating_workspace_template =
@@ -587,9 +796,13 @@
       {features::kFloatingWorkspaceV2, features::kDesksTemplates,
        features::kDeskTemplateSync},
       {});
+  std::unique_ptr<MockSyncService> mock_sync_service =
+      std::make_unique<MockSyncService>();
   TestFloatingWorkSpaceService test_floating_workspace_service_v2(
-      profile(), TestFloatingWorkspaceVersion::kFloatingWorkspaceV2Enabled,
-      fake_desk_sync_service());
+      profile(), fake_desk_sync_service(), mock_sync_service.get(),
+      floating_workspace_util::FloatingWorkspaceVersion::
+          kFloatingWorkspaceV2Enabled);
+
   const std::string template_name = "floating_workspace_captured_template";
   const base::Time first_captured_template_creation_time = base::Time::Now();
   std::unique_ptr<DeskTemplate> first_captured_floating_workspace_template =
@@ -634,9 +847,13 @@
       {features::kFloatingWorkspaceV2, features::kDesksTemplates,
        features::kDeskTemplateSync},
       {});
+  std::unique_ptr<MockSyncService> mock_sync_service =
+      std::make_unique<MockSyncService>();
   TestFloatingWorkSpaceService test_floating_workspace_service_v2(
-      profile(), TestFloatingWorkspaceVersion::kFloatingWorkspaceV2Enabled,
-      fake_desk_sync_service());
+      profile(), fake_desk_sync_service(), mock_sync_service.get(),
+      floating_workspace_util::FloatingWorkspaceVersion::
+          kFloatingWorkspaceV2Enabled);
+
   const std::string template_name = "floating_workspace_captured_template";
   const base::Time first_captured_template_creation_time = base::Time::Now();
   std::unique_ptr<DeskTemplate> first_captured_floating_workspace_template =
@@ -679,4 +896,4 @@
   scoped_feature_list().Reset();
 }
 
-}  // namespace ash
+}  // namespace ash::floating_workspace
diff --git a/chrome/browser/ash/floating_workspace/floating_workspace_util.cc b/chrome/browser/ash/floating_workspace/floating_workspace_util.cc
index d4b1068..74ce7f5 100644
--- a/chrome/browser/ash/floating_workspace/floating_workspace_util.cc
+++ b/chrome/browser/ash/floating_workspace/floating_workspace_util.cc
@@ -7,8 +7,13 @@
 #include "ash/constants/ash_features.h"
 #include "ash/constants/ash_pref_names.h"
 #include "base/check.h"
+#include "base/strings/stringprintf.h"
+#include "base/strings/utf_string_conversions.h"
 #include "chrome/browser/ash/profiles/profile_helper.h"
 #include "chrome/browser/profiles/profile.h"
+#include "chromeos/ash/components/network/network_handler.h"
+#include "chromeos/ash/components/network/network_state_handler.h"
+#include "chromeos/ash/components/network/network_type_pattern.h"
 #include "components/prefs/pref_registry_simple.h"
 #include "components/prefs/pref_service.h"
 #include "components/user_manager/user_manager.h"
@@ -65,5 +70,11 @@
   return features::IsFloatingWorkspaceV2Enabled();
 }
 
+bool IsInternetConnected() {
+  NetworkStateHandler* nsh = NetworkHandler::Get()->network_state_handler();
+  return nsh != nullptr &&
+         nsh->ConnectedNetworkByType(NetworkTypePattern::Default()) != nullptr;
+}
+
 }  // namespace floating_workspace_util
 }  // namespace ash
diff --git a/chrome/browser/ash/floating_workspace/floating_workspace_util.h b/chrome/browser/ash/floating_workspace/floating_workspace_util.h
index cd139244..eddf004 100644
--- a/chrome/browser/ash/floating_workspace/floating_workspace_util.h
+++ b/chrome/browser/ash/floating_workspace/floating_workspace_util.h
@@ -9,9 +9,19 @@
 
 class PrefRegistrySimple;
 
-namespace ash {
+namespace ash::floating_workspace_util {
 
-namespace floating_workspace_util {
+// The restore from error notification button index.
+enum class FloatingWorkspaceVersion {
+  // Default value, indicates no version was enabled.
+  kNoVersionEnabled = 0,
+
+  // Version 1.
+  kFloatingWorkspaceV1Enabled = 1,
+
+  // Version 2.
+  kFloatingWorkspaceV2Enabled = 2,
+};
 
 ASH_EXPORT void RegisterProfilePrefs(PrefRegistrySimple* registry);
 
@@ -21,7 +31,8 @@
 ASH_EXPORT bool IsFloatingWorkspaceV1Enabled();
 ASH_EXPORT bool IsFloatingWorkspaceV2Enabled();
 
-}  // namespace floating_workspace_util
-}  // namespace ash
+bool IsInternetConnected();
+
+}  // namespace ash::floating_workspace_util
 
 #endif  // CHROME_BROWSER_ASH_FLOATING_WORKSPACE_FLOATING_WORKSPACE_UTIL_H_
diff --git a/chrome/browser/ash/login/easy_unlock/easy_unlock_service.cc b/chrome/browser/ash/login/easy_unlock/easy_unlock_service.cc
index ed1097a..894dab8c 100644
--- a/chrome/browser/ash/login/easy_unlock/easy_unlock_service.cc
+++ b/chrome/browser/ash/login/easy_unlock/easy_unlock_service.cc
@@ -443,10 +443,14 @@
     RecordEasyUnlockScreenUnlockEvent(event);
 
     if (will_authenticate_using_easy_unlock()) {
+      SmartLockMetricsRecorder::RecordSmartLockUnlockAuthMethodChoice(
+          SmartLockMetricsRecorder::SmartLockAuthMethodChoice::kSmartLock);
       RecordAuthResult(/*failure_reason=*/absl::nullopt);
       RecordEasyUnlockScreenUnlockDuration(base::TimeTicks::Now() -
                                            lock_screen_last_shown_timestamp_);
     } else {
+      SmartLockMetricsRecorder::RecordSmartLockUnlockAuthMethodChoice(
+          SmartLockMetricsRecorder::SmartLockAuthMethodChoice::kOther);
       SmartLockMetricsRecorder::RecordAuthMethodChoiceUnlockPasswordState(
           GetSmartUnlockPasswordAuthEvent());
       OnUserEnteredPassword();
diff --git a/chrome/browser/ash/platform_keys/platform_keys_service_test_util.cc b/chrome/browser/ash/platform_keys/platform_keys_service_test_util.cc
index 0785b0eb..2ed606a 100644
--- a/chrome/browser/ash/platform_keys/platform_keys_service_test_util.cc
+++ b/chrome/browser/ash/platform_keys/platform_keys_service_test_util.cc
@@ -74,15 +74,6 @@
   return true;
 }
 
-// TODO(olsa): Extend this initial implementation with more logic.
-bool FakeChapsUtil::ImportPkcs12Certificate(
-    PK11SlotInfo* slot,
-    const std::vector<uint8_t>& pkcs12_data,
-    const std::string& password,
-    bool is_software_backed) {
-  return true;
-}
-
 ScopedChapsUtilOverride::ScopedChapsUtilOverride() {
   chromeos::platform_keys::ChapsUtil::SetFactoryForTesting(base::BindRepeating(
       &ScopedChapsUtilOverride::CreateChapsUtil, base::Unretained(this)));
diff --git a/chrome/browser/ash/platform_keys/platform_keys_service_test_util.h b/chrome/browser/ash/platform_keys/platform_keys_service_test_util.h
index 1f5c7b1..79cab776 100644
--- a/chrome/browser/ash/platform_keys/platform_keys_service_test_util.h
+++ b/chrome/browser/ash/platform_keys/platform_keys_service_test_util.h
@@ -89,11 +89,6 @@
       crypto::ScopedSECKEYPublicKey* out_public_key,
       crypto::ScopedSECKEYPrivateKey* out_private_key) override;
 
-  bool ImportPkcs12Certificate(PK11SlotInfo* slot,
-                               const std::vector<uint8_t>& pkcs12_data,
-                               const std::string& password,
-                               bool is_software_backed) override;
-
  private:
   OnKeyGenerated on_key_generated_;
 };
diff --git a/chrome/browser/browsing_topics/browsing_topics_internals_browsertest.cc b/chrome/browser/browsing_topics/browsing_topics_internals_browsertest.cc
index bd419e77..e40fc41 100644
--- a/chrome/browser/browsing_topics/browsing_topics_internals_browsertest.cc
+++ b/chrome/browser/browsing_topics/browsing_topics_internals_browsertest.cc
@@ -354,16 +354,21 @@
 PrivacySandboxSettings3: disabled
 OverridePrivacySandboxSettingsLocalTesting: disabled
 BrowsingTopicsBypassIPIsPubliclyRoutableCheck: disabled
+BrowsingTopicsXHR: disabled
+BrowsingTopicsDocumentAPI: enabled
+Configuration version: 1
+BrowsingTopicsParameters: enabled
 BrowsingTopicsParameters:number_of_epochs_to_expose: 3
 BrowsingTopicsParameters:time_period_per_epoch: 7d-0h-0m-0s
 BrowsingTopicsParameters:number_of_top_topics_per_epoch: 5
 BrowsingTopicsParameters:use_random_topic_probability_percent: 5
+BrowsingTopicsParameters:max_epoch_introduction_delay: 2d-0h-0m-0s
 BrowsingTopicsParameters:number_of_epochs_of_observation_data_to_use_for_filtering: 3
 BrowsingTopicsParameters:max_number_of_api_usage_context_domains_to_keep_per_topic: 1000
 BrowsingTopicsParameters:max_number_of_api_usage_context_entries_to_load_per_epoch: 100000
 BrowsingTopicsParameters:max_number_of_api_usage_context_domains_to_store_per_page_load: 30
-BrowsingTopicsParameters:config_version: 1
 BrowsingTopicsParameters:taxonomy_version: 1
+BrowsingTopicsParameters:disabled_topics_list: 
 )");
 }
 
@@ -465,16 +470,21 @@
 PrivacySandboxSettings3: enabled
 OverridePrivacySandboxSettingsLocalTesting: disabled
 BrowsingTopicsBypassIPIsPubliclyRoutableCheck: disabled
+BrowsingTopicsXHR: disabled
+BrowsingTopicsDocumentAPI: enabled
+Configuration version: 1
+BrowsingTopicsParameters: enabled
 BrowsingTopicsParameters:number_of_epochs_to_expose: 3
 BrowsingTopicsParameters:time_period_per_epoch: 0d-0h-0m-15s
 BrowsingTopicsParameters:number_of_top_topics_per_epoch: 2
 BrowsingTopicsParameters:use_random_topic_probability_percent: 5
+BrowsingTopicsParameters:max_epoch_introduction_delay: 2d-0h-0m-0s
 BrowsingTopicsParameters:number_of_epochs_of_observation_data_to_use_for_filtering: 3
 BrowsingTopicsParameters:max_number_of_api_usage_context_domains_to_keep_per_topic: 1000
 BrowsingTopicsParameters:max_number_of_api_usage_context_entries_to_load_per_epoch: 100000
 BrowsingTopicsParameters:max_number_of_api_usage_context_domains_to_store_per_page_load: 30
-BrowsingTopicsParameters:config_version: 1
 BrowsingTopicsParameters:taxonomy_version: 1
+BrowsingTopicsParameters:disabled_topics_list: 
 )");
 }
 
diff --git a/chrome/browser/chromeos/BUILD.gn b/chrome/browser/chromeos/BUILD.gn
index 58daaaf1..ec4a4bdc 100644
--- a/chrome/browser/chromeos/BUILD.gn
+++ b/chrome/browser/chromeos/BUILD.gn
@@ -101,8 +101,6 @@
     "platform_keys/extension_platform_keys_service.h",
     "platform_keys/extension_platform_keys_service_factory.cc",
     "platform_keys/extension_platform_keys_service_factory.h",
-    "platform_keys/pkcs12_reader.cc",
-    "platform_keys/pkcs12_reader.h",
     "platform_keys/platform_keys.cc",
     "platform_keys/platform_keys.h",
     "policy/dlp/clipboard_bubble.cc",
@@ -470,7 +468,6 @@
     "kcer_nss/cert_cache_nss_unittest.cc",
     "kcer_nss/kcer_nss_unittest.cc",
     "platform_keys/chaps_util_impl_unittest.cc",
-    "platform_keys/pkcs12_reader_unittest.cc",
     "policy/dlp/data_transfer_dlp_controller_unittest.cc",
     "policy/dlp/dlp_clipboard_notifier_unittest.cc",
     "policy/dlp/dlp_confidential_contents_unittest.cc",
diff --git a/chrome/browser/chromeos/platform_keys/chaps_slot_session.cc b/chrome/browser/chromeos/platform_keys/chaps_slot_session.cc
index 5fb6f33e..bd487851 100644
--- a/chrome/browser/chromeos/platform_keys/chaps_slot_session.cc
+++ b/chrome/browser/chromeos/platform_keys/chaps_slot_session.cc
@@ -71,15 +71,14 @@
     }
     CK_C_OpenSession open_session = function_list->C_OpenSession;
     CK_C_CloseSession close_session = function_list->C_CloseSession;
-    CK_C_CreateObject create_object = function_list->C_CreateObject;
     CK_C_GenerateKeyPair generate_key_pair = function_list->C_GenerateKeyPair;
     CK_C_GetAttributeValue get_attribute_value =
         function_list->C_GetAttributeValue;
     CK_C_SetAttributeValue set_attribute_value =
         function_list->C_SetAttributeValue;
 
-    if (!open_session || !close_session || !create_object ||
-        !generate_key_pair || !get_attribute_value || !set_attribute_value) {
+    if (!open_session || !close_session || !generate_key_pair ||
+        !get_attribute_value || !set_attribute_value) {
       LogError(ErrorCode::kRequiredFunctionMissing);
       return nullptr;
     }
@@ -100,9 +99,8 @@
       return nullptr;
     }
     return base::WrapUnique(new ChapsSlotSessionImpl(
-        chaps_handle, open_session, close_session, create_object,
-        generate_key_pair, get_attribute_value, set_attribute_value, slot_id,
-        session_handle));
+        chaps_handle, open_session, close_session, generate_key_pair,
+        get_attribute_value, set_attribute_value, slot_id, session_handle));
   }
 
   ~ChapsSlotSessionImpl() override {
@@ -151,14 +149,6 @@
     return true;
   }
 
-  CK_RV CreateObject(CK_ATTRIBUTE_PTR pTemplate,
-                     CK_ULONG ulCount,
-                     CK_OBJECT_HANDLE_PTR phObject) override {
-    base::ScopedBlockingCall scoped_blocking_call(
-        FROM_HERE, base::BlockingType::WILL_BLOCK);
-    return create_object_(session_handle_, pTemplate, ulCount, phObject);
-  }
-
   CK_RV GenerateKeyPair(CK_MECHANISM_PTR pMechanism,
                         CK_ATTRIBUTE_PTR pPublicKeyTemplate,
                         CK_ULONG ulPublicKeyAttributeCount,
@@ -194,7 +184,6 @@
   ChapsSlotSessionImpl(void* chaps_handle,
                        CK_C_OpenSession open_session,
                        CK_C_CloseSession close_session,
-                       CK_C_CreateObject create_object,
                        CK_C_GenerateKeyPair generate_key_pair,
                        CK_C_GetAttributeValue get_attribute_value,
                        CK_C_SetAttributeValue set_attribute_value,
@@ -203,7 +192,6 @@
       : chaps_handle_(chaps_handle),
         open_session_(open_session),
         close_session_(close_session),
-        create_object_(create_object),
         generate_key_pair_(generate_key_pair),
         get_attribute_value_(get_attribute_value),
         set_attribute_value_(set_attribute_value),
@@ -219,7 +207,6 @@
   raw_ptr<void, ExperimentalAsh> chaps_handle_ = nullptr;
   CK_C_OpenSession open_session_ = nullptr;
   CK_C_CloseSession close_session_ = nullptr;
-  CK_C_CreateObject create_object_ = nullptr;
   CK_C_GenerateKeyPair generate_key_pair_ = nullptr;
   CK_C_GetAttributeValue get_attribute_value_ = nullptr;
   CK_C_SetAttributeValue set_attribute_value_ = nullptr;
diff --git a/chrome/browser/chromeos/platform_keys/chaps_slot_session.h b/chrome/browser/chromeos/platform_keys/chaps_slot_session.h
index fcccd8bf..ccd85b4e 100644
--- a/chrome/browser/chromeos/platform_keys/chaps_slot_session.h
+++ b/chrome/browser/chromeos/platform_keys/chaps_slot_session.h
@@ -24,12 +24,6 @@
   // Close and re-open this session.
   virtual bool ReopenSession() = 0;
 
-  // Calls C_CreateObject.
-  // PKCS #11 v2.20 section 11.7 page 128.
-  virtual CK_RV CreateObject(CK_ATTRIBUTE_PTR pTemplate,
-                             CK_ULONG ulCount,
-                             CK_OBJECT_HANDLE_PTR phObject) = 0;
-
   // Calls C_GenerateKeyPair.
   // PKCS #11 v2.20 section 11.14 page 176.
   virtual CK_RV GenerateKeyPair(CK_MECHANISM_PTR pMechanism,
diff --git a/chrome/browser/chromeos/platform_keys/chaps_util.h b/chrome/browser/chromeos/platform_keys/chaps_util.h
index ed05ae1..fe38b12 100644
--- a/chrome/browser/chromeos/platform_keys/chaps_util.h
+++ b/chrome/browser/chromeos/platform_keys/chaps_util.h
@@ -36,17 +36,6 @@
       crypto::ScopedSECKEYPublicKey* out_public_key,
       crypto::ScopedSECKEYPrivateKey* out_private_key) = 0;
 
-  // Import key and all included certificates from PKCS12 container.
-  // Imported objects will be stored in Chaps.
-  // If some of certificates can not be imported they will be skipped and
-  // Pkcs12ReaderStatusCode::kFailureDuringCertImport error will be logged.
-  // `is_software_backed` specifies whether a hardware-backed or software-backed
-  // storage is used.
-  virtual bool ImportPkcs12Certificate(PK11SlotInfo* slot,
-                                       const std::vector<uint8_t>& pkcs12_data,
-                                       const std::string& password,
-                                       bool is_software_backed) = 0;
-
   using FactoryCallback = base::RepeatingCallback<std::unique_ptr<ChapsUtil>()>;
 
   // Sets the factory which ChapsUtil::Create() will use to create ChapsUtil
diff --git a/chrome/browser/chromeos/platform_keys/chaps_util_impl.cc b/chrome/browser/chromeos/platform_keys/chaps_util_impl.cc
index 293a0a9..b445f62 100644
--- a/chrome/browser/chromeos/platform_keys/chaps_util_impl.cc
+++ b/chrome/browser/chromeos/platform_keys/chaps_util_impl.cc
@@ -11,21 +11,18 @@
 #include <pkcs11t.h>
 
 #include <ostream>
+#include <string>
 #include <vector>
 
 #include "base/check.h"
 #include "base/functional/bind.h"
 #include "base/functional/callback.h"
 #include "base/logging.h"
-#include "base/strings/string_number_conversions.h"
 #include "base/strings/string_piece.h"
 #include "chrome/browser/chromeos/platform_keys/chaps_slot_session.h"
-#include "chrome/browser/chromeos/platform_keys/pkcs12_reader.h"
 #include "crypto/chaps_support.h"
 #include "crypto/scoped_nss_types.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
-#include "third_party/boringssl/src/include/openssl/mem.h"
-#include "third_party/boringssl/src/include/openssl/pkcs8.h"
 
 namespace chromeos {
 namespace platform_keys {
@@ -37,10 +34,7 @@
 constexpr CK_ATTRIBUTE_TYPE kForceSoftwareAttribute = CKA_VENDOR_DEFINED + 4;
 // Chaps sets this for keys that are software-backed.
 constexpr CK_ATTRIBUTE_TYPE kKeyInSoftware = CKA_VENDOR_DEFINED + 5;
-constexpr char kPkcs12ImportFailed[] = "Chaps util PKCS12 import failed with ";
-constexpr char kPkcs12KeyImportFailed[] = "Chaps util key import failed with ";
-constexpr char kPkcs12CertImportFailed[] =
-    "Chaps util cert import failed with ";
+
 // Wraps public key and private key PKCS#11 object handles.
 struct KeyPairHandles {
   CK_OBJECT_HANDLE public_key;
@@ -48,7 +42,6 @@
 };
 
 using Pkcs11Operation = base::RepeatingCallback<CK_RV()>;
-
 // Performs |operation| and handles return values indicating that the PKCS11
 // session has been closed by attempting to re-open the |chaps_session|.
 // This is useful because the session could be closed e.g. because NSS could
@@ -162,16 +155,6 @@
   return key_in_software;
 }
 
-crypto::ScopedSECItem MakeIdFromPubKeyNss(std::vector<CK_BYTE>& rsa_modulus) {
-  SECItem secitem_modulus;
-  secitem_modulus.data = rsa_modulus.data();
-  secitem_modulus.len = rsa_modulus.size();
-  return crypto::ScopedSECItem(PK11_MakeIDFromPubKey(&secitem_modulus));
-}
-
-std::vector<uint8_t> SECItemToBytes(const crypto::ScopedSECItem& id) {
-  return std::vector<uint8_t>(id->data, id->data + id->len);
-}
 // Create the CKA_ID value that NSS would use for |key_pair| and return it.
 crypto::ScopedSECItem CreateNssCkaId(ChapsSlotSession* chaps_session,
                                      const KeyPairHandles& key_pair) {
@@ -179,7 +162,11 @@
   if (!modulus) {
     return nullptr;
   }
-  return MakeIdFromPubKeyNss(modulus.value());
+
+  SECItem secitem_modulus;
+  secitem_modulus.data = modulus->data();
+  secitem_modulus.len = modulus->size();
+  return crypto::ScopedSECItem(PK11_MakeIDFromPubKey(&secitem_modulus));
 }
 
 // Set the CKA_ID attribute of the public and private key objects in |key_pair|
@@ -207,218 +194,6 @@
   return true;
 }
 
-std::string MakePkcs12KeyImportErrorMessage(Pkcs12ReaderStatusCode error_code) {
-  return kPkcs12KeyImportFailed +
-         base::NumberToString(static_cast<int>(error_code));
-}
-
-std::string MakePkcs12CertImportErrorMessage(
-    Pkcs12ReaderStatusCode error_code) {
-  return kPkcs12CertImportFailed +
-         base::NumberToString(static_cast<int>(error_code));
-}
-
-std::string MakePkcs12ImportErrorMessage(Pkcs12ReaderStatusCode error_code) {
-  return kPkcs12ImportFailed +
-         base::NumberToString(static_cast<int>(error_code));
-}
-Pkcs12ReaderStatusCode ImportRsaKey(ChapsSlotSession* chaps_session,
-                                    bssl::UniquePtr<EVP_PKEY> key,
-                                    bool is_software_backed,
-                                    const Pkcs12Reader* pkcs12_reader,
-                                    std::vector<uint8_t>& out_id,
-                                    CK_OBJECT_HANDLE& out_key_handle) {
-  if (!key) {
-    LOG(ERROR) << MakePkcs12KeyImportErrorMessage(
-        Pkcs12ReaderStatusCode::kKeyDataMissed);
-    return Pkcs12ReaderStatusCode::kKeyDataMissed;
-  }
-
-  // All the data variables must stay alive until `key_template` is sent to
-  // Chaps.
-  const RSA* rsa_key = EVP_PKEY_get0_RSA(key.get());
-  std::vector<uint8_t> public_modulus_bytes =
-      pkcs12_reader->BignumToBytes(RSA_get0_n(rsa_key));
-  out_id = SECItemToBytes(MakeIdFromPubKeyNss(public_modulus_bytes));
-  std::vector<uint8_t> public_exponent_bytes =
-      pkcs12_reader->BignumToBytes(RSA_get0_e(rsa_key));
-  std::vector<uint8_t> private_exponent_bytes =
-      pkcs12_reader->BignumToBytes(RSA_get0_d(rsa_key));
-  std::vector<uint8_t> prime_factor_1 =
-      pkcs12_reader->BignumToBytes(RSA_get0_p(rsa_key));
-  std::vector<uint8_t> prime_factor_2 =
-      pkcs12_reader->BignumToBytes(RSA_get0_q(rsa_key));
-  std::vector<uint8_t> exponent_1 =
-      pkcs12_reader->BignumToBytes(RSA_get0_dmp1(rsa_key));
-  std::vector<uint8_t> exponent_2 =
-      pkcs12_reader->BignumToBytes(RSA_get0_dmq1(rsa_key));
-  std::vector<uint8_t> coefficient =
-      pkcs12_reader->BignumToBytes(RSA_get0_iqmp(rsa_key));
-
-  if (public_modulus_bytes.empty() || out_id.empty() ||
-      public_exponent_bytes.empty() || private_exponent_bytes.empty() ||
-      prime_factor_1.empty() || prime_factor_2.empty() || exponent_1.empty() ||
-      exponent_2.empty() || coefficient.empty()) {
-    LOG(ERROR) << MakePkcs12KeyImportErrorMessage(
-        Pkcs12ReaderStatusCode::kKeyAttrDataMissing);
-    return Pkcs12ReaderStatusCode::kKeyAttrDataMissing;
-  }
-
-  CK_BBOOL true_value = CK_TRUE;
-  CK_OBJECT_CLASS key_class = CKO_PRIVATE_KEY;
-  CK_KEY_TYPE key_type = CKK_RSA;
-  CK_BBOOL force_software_attribute = is_software_backed ? CK_TRUE : CK_FALSE;
-  CK_ATTRIBUTE attrs[] = {
-      {CKA_CLASS, &key_class, sizeof(key_class)},
-      {CKA_KEY_TYPE, &key_type, sizeof(key_type)},
-      {CKA_TOKEN, &true_value, sizeof(CK_BBOOL)},
-      {CKA_SENSITIVE, &true_value, sizeof(CK_BBOOL)},
-      {kForceSoftwareAttribute, &force_software_attribute, sizeof(CK_BBOOL)},
-      {CKA_PRIVATE, &true_value, sizeof(CK_BBOOL)},
-      {CKA_UNWRAP, &true_value, sizeof(CK_BBOOL)},
-      {CKA_DECRYPT, &true_value, sizeof(CK_BBOOL)},
-      {CKA_SIGN, &true_value, sizeof(CK_BBOOL)},
-      {CKA_SIGN_RECOVER, &true_value, sizeof(CK_BBOOL)},
-      {CKA_MODULUS, public_modulus_bytes.data(), public_modulus_bytes.size()},
-      {CKA_ID, out_id.data(), out_id.size()},
-      {CKA_PUBLIC_EXPONENT, public_exponent_bytes.data(),
-       public_exponent_bytes.size()},
-      {CKA_PRIVATE_EXPONENT, private_exponent_bytes.data(),
-       private_exponent_bytes.size()},
-      {CKA_PRIME_1, prime_factor_1.data(), prime_factor_1.size()},
-      {CKA_PRIME_2, prime_factor_2.data(), prime_factor_2.size()},
-      {CKA_EXPONENT_1, exponent_1.data(), exponent_1.size()},
-      {CKA_EXPONENT_2, exponent_2.data(), exponent_2.size()},
-      {CKA_COEFFICIENT, coefficient.data(), coefficient.size()}};
-
-  if (!PerformWithRetries(
-          chaps_session, "CreateObject",
-          base::BindRepeating(&ChapsSlotSession::CreateObject,
-                              base::Unretained(chaps_session), attrs,
-                              /*ulCount=*/std::size(attrs), &out_key_handle))) {
-    LOG(ERROR) << MakePkcs12KeyImportErrorMessage(
-        Pkcs12ReaderStatusCode::kCreateKeyFailed);
-    return Pkcs12ReaderStatusCode::kCreateKeyFailed;
-  }
-  return Pkcs12ReaderStatusCode::kSuccess;
-}
-
-Pkcs12ReaderStatusCode ImportOneCert(ChapsSlotSession* chaps_session,
-                                     X509* cert,
-                                     const std::vector<uint8_t>& id,
-                                     CK_OBJECT_HANDLE key_handle,
-                                     const Pkcs12Reader* pkcs12_helper,
-                                     bool is_software_backed) {
-  if (!cert) {
-    LOG(ERROR) << MakePkcs12CertImportErrorMessage(
-        Pkcs12ReaderStatusCode::kCertificateDataMissed);
-    return Pkcs12ReaderStatusCode::kCertificateDataMissed;
-  }
-
-  CK_OBJECT_CLASS cert_class = CKO_CERTIFICATE;
-  CK_CERTIFICATE_TYPE cert_type = CKC_X_509;
-  CK_BBOOL true_value = CK_TRUE;
-
-  int cert_der_size = 0;
-  bssl::UniquePtr<uint8_t> cert_der;
-  Pkcs12ReaderStatusCode get_cert_der_result =
-      pkcs12_helper->GetDerEncodedCert(cert, cert_der, cert_der_size);
-
-  if (get_cert_der_result != Pkcs12ReaderStatusCode::kSuccess) {
-    LOG(ERROR) << MakePkcs12CertImportErrorMessage(get_cert_der_result);
-    return get_cert_der_result;
-  }
-
-  base::span<const uint8_t> issuer_name_data;
-  Pkcs12ReaderStatusCode get_issuer_name_der_result =
-      pkcs12_helper->GetIssuerNameDer(cert, issuer_name_data);
-  if (get_issuer_name_der_result != Pkcs12ReaderStatusCode::kSuccess) {
-    LOG(ERROR) << MakePkcs12CertImportErrorMessage(get_issuer_name_der_result);
-    return get_issuer_name_der_result;
-  }
-
-  base::span<const uint8_t> subject_name_data;
-  Pkcs12ReaderStatusCode get_subject_name_der_result =
-      pkcs12_helper->GetSubjectNameDer(cert, subject_name_data);
-  if (get_subject_name_der_result != Pkcs12ReaderStatusCode::kSuccess) {
-    LOG(ERROR) << MakePkcs12CertImportErrorMessage(get_subject_name_der_result);
-    return get_subject_name_der_result;
-  }
-
-  int serial_number_der_size = 0;
-  bssl::UniquePtr<uint8_t> serial_number_der;
-  Pkcs12ReaderStatusCode get_serial_der_result =
-      pkcs12_helper->GetSerialNumberDer(cert, serial_number_der,
-                                        serial_number_der_size);
-  if (get_serial_der_result != Pkcs12ReaderStatusCode::kSuccess) {
-    LOG(ERROR) << MakePkcs12CertImportErrorMessage(get_serial_der_result);
-    return get_serial_der_result;
-  }
-
-  std::string label;
-  Pkcs12ReaderStatusCode get_label_result =
-      pkcs12_helper->GetLabel(cert, label);
-  if (get_label_result != Pkcs12ReaderStatusCode::kSuccess) {
-    LOG(ERROR) << MakePkcs12CertImportErrorMessage(get_label_result);
-    return get_label_result;
-  }
-
-  CK_BBOOL force_software_attribute = is_software_backed ? CK_TRUE : CK_FALSE;
-
-  CK_ATTRIBUTE attrs[] = {
-      {CKA_CLASS, &cert_class, sizeof(cert_class)},
-      {CKA_CERTIFICATE_TYPE, &cert_type, sizeof(cert_type)},
-      {CKA_TOKEN, &true_value, sizeof(true_value)},
-      {kForceSoftwareAttribute, &force_software_attribute, sizeof(CK_BBOOL)},
-      {CKA_ID, const_cast<uint8_t*>(id.data()), id.size()},
-      {CKA_LABEL, label.data(), label.size()},
-      {CKA_VALUE, cert_der.get(),
-       base::saturated_cast<CK_ULONG>(cert_der_size)},
-      {CKA_ISSUER, const_cast<uint8_t*>(issuer_name_data.data()),
-       issuer_name_data.size()},
-      {CKA_SUBJECT, const_cast<uint8_t*>(subject_name_data.data()),
-       subject_name_data.size()},
-      {CKA_SERIAL_NUMBER, serial_number_der.get(),
-       base::saturated_cast<CK_ULONG>(serial_number_der_size)}};
-
-  CK_OBJECT_HANDLE cert_handle;
-  if (!PerformWithRetries(
-          chaps_session, "CreateObject",
-          base::BindRepeating(&ChapsSlotSession::CreateObject,
-                              base::Unretained(chaps_session), attrs,
-                              /*ulCount=*/std::size(attrs), &cert_handle))) {
-    LOG(ERROR) << MakePkcs12CertImportErrorMessage(
-        Pkcs12ReaderStatusCode::kCreateCertFailed);
-    return Pkcs12ReaderStatusCode::kCreateCertFailed;
-  }
-
-  return Pkcs12ReaderStatusCode::kSuccess;
-}
-
-Pkcs12ReaderStatusCode ImportAllCerts(ChapsSlotSession* chaps_session,
-                                      bssl::UniquePtr<STACK_OF(X509)> certs,
-                                      const std::vector<uint8_t>& id,
-                                      CK_OBJECT_HANDLE key_handle,
-                                      const Pkcs12Reader* pkcs12_helper,
-                                      bool is_software_backed) {
-  if (!certs) {
-    LOG(ERROR) << MakePkcs12CertImportErrorMessage(
-        Pkcs12ReaderStatusCode::kCertificateDataMissed);
-    return Pkcs12ReaderStatusCode::kCertificateDataMissed;
-  }
-
-  Pkcs12ReaderStatusCode is_every_cert_imported =
-      Pkcs12ReaderStatusCode::kSuccess;
-  for (size_t i = 0; i < sk_X509_num(certs.get()); ++i) {
-    if (ImportOneCert(chaps_session, sk_X509_value(certs.get(), i), id,
-                      key_handle, pkcs12_helper,
-                      is_software_backed) != Pkcs12ReaderStatusCode::kSuccess) {
-      is_every_cert_imported = Pkcs12ReaderStatusCode::kFailureDuringCertImport;
-    }
-  }
-  return is_every_cert_imported;
-}
-
 }  // namespace
 
 ChapsUtilImpl::ChapsUtilImpl(
@@ -475,65 +250,10 @@
   return true;
 }
 
-bool ChapsUtilImpl::ImportPkcs12Certificate(
-    PK11SlotInfo* slot,
-    const std::vector<uint8_t>& pkcs12_data,
-    const std::string& password,
-    bool is_software_backed) {
-  return ImportPkcs12CertificateImpl(slot, pkcs12_data, password,
-                                     is_software_backed);
-}
-
-bool ChapsUtilImpl::ImportPkcs12CertificateImpl(
-    PK11SlotInfo* slot,
-    const std::vector<uint8_t>& pkcs12_data,
-    const std::string& password,
-    const bool is_software_backed,
-    const Pkcs12Reader& pkcs12_helper_inc) {
-  std::unique_ptr<ChapsSlotSession> chaps_session =
-      GetChapsSlotSessionForSlot(slot);
-  if (!chaps_session) {
-    LOG(ERROR) << MakePkcs12ImportErrorMessage(
-        Pkcs12ReaderStatusCode::kChapsSessionMissed);
-    return false;
-  }
-
-  bssl::UniquePtr<EVP_PKEY> key;
-  bssl::UniquePtr<STACK_OF(X509)> certs;
-  Pkcs12ReaderStatusCode get_key_and_cert_status =
-      pkcs12_helper_inc.GetPkcs12KeyAndCerts(pkcs12_data, password, key, certs);
-  if (get_key_and_cert_status != Pkcs12ReaderStatusCode::kSuccess) {
-    LOG(ERROR) << MakePkcs12ImportErrorMessage(get_key_and_cert_status);
-    return false;
-  }
-
-  CK_OBJECT_HANDLE key_handle;
-  // Same id will be used for the key and certs.
-  std::vector<uint8_t> cka_id_value;
-
-  Pkcs12ReaderStatusCode import_key_status =
-      ImportRsaKey(chaps_session.get(), std::move(key), is_software_backed,
-                   &pkcs12_helper_inc, cka_id_value, key_handle);
-  if (import_key_status != Pkcs12ReaderStatusCode::kSuccess) {
-    LOG(ERROR) << MakePkcs12ImportErrorMessage(import_key_status);
-    return false;
-  }
-
-  Pkcs12ReaderStatusCode import_cert_status =
-      ImportAllCerts(chaps_session.get(), std::move(certs), cka_id_value,
-                     key_handle, &pkcs12_helper_inc, is_software_backed);
-  if (import_cert_status != Pkcs12ReaderStatusCode::kSuccess) {
-    LOG(ERROR) << MakePkcs12ImportErrorMessage(import_cert_status);
-    return false;
-  }
-
-  return true;
-}
-
 std::unique_ptr<ChapsSlotSession> ChapsUtilImpl::GetChapsSlotSessionForSlot(
     PK11SlotInfo* slot) {
-  if (!slot || (!is_chaps_provided_slot_for_testing_ &&
-                !crypto::IsSlotProvidedByChaps(slot))) {
+  if (!is_chaps_provided_slot_for_testing_ &&
+      !crypto::IsSlotProvidedByChaps(slot)) {
     return nullptr;
   }
 
diff --git a/chrome/browser/chromeos/platform_keys/chaps_util_impl.h b/chrome/browser/chromeos/platform_keys/chaps_util_impl.h
index 3cd6631..f1ae8bc 100644
--- a/chrome/browser/chromeos/platform_keys/chaps_util_impl.h
+++ b/chrome/browser/chromeos/platform_keys/chaps_util_impl.h
@@ -10,14 +10,13 @@
 #include "base/functional/callback_forward.h"
 #include "chrome/browser/chromeos/platform_keys/chaps_slot_session.h"
 #include "chrome/browser/chromeos/platform_keys/chaps_util.h"
-#include "chrome/browser/chromeos/platform_keys/pkcs12_reader.h"
 #include "crypto/scoped_nss_types.h"
 
 namespace chromeos {
 namespace platform_keys {
 
 // Default implementation of the ChapsUtil class. Communicates with the chapsd
-// daemon using ChapsSlotSession. Should be used on a worker thread.
+// daemon using ChapsSlotSession.
 class ChapsUtilImpl : public ChapsUtil {
  public:
   ChapsUtilImpl(
@@ -30,19 +29,6 @@
       crypto::ScopedSECKEYPublicKey* out_public_key,
       crypto::ScopedSECKEYPrivateKey* out_private_key) override;
 
-  bool ImportPkcs12Certificate(PK11SlotInfo* slot,
-                               const std::vector<uint8_t>& pkcs12_data,
-                               const std::string& password,
-                               bool is_software_backed) override;
-
-  // Public for testing, allows replacing ChapsPkcs12Helper.
-  bool ImportPkcs12CertificateImpl(
-      PK11SlotInfo* slot,
-      const std::vector<uint8_t>& pkcs12_data,
-      const std::string& password,
-      const bool is_software_backed,
-      const Pkcs12Reader& pkcs12_helper1 = Pkcs12Reader());
-
   // If called with true, every slot is assumed to be a chaps-provided slot.
   void SetIsChapsProvidedSlotForTesting(
       bool is_chaps_provided_slot_for_testing) {
diff --git a/chrome/browser/chromeos/platform_keys/chaps_util_impl_unittest.cc b/chrome/browser/chromeos/platform_keys/chaps_util_impl_unittest.cc
index 0c5cf3c..44850fc 100644
--- a/chrome/browser/chromeos/platform_keys/chaps_util_impl_unittest.cc
+++ b/chrome/browser/chromeos/platform_keys/chaps_util_impl_unittest.cc
@@ -4,32 +4,29 @@
 
 #include "chrome/browser/chromeos/platform_keys/chaps_util_impl.h"
 
-#include "base/base64.h"
-#include "base/files/file_util.h"
-
 #include <pkcs11t.h>
 #include <secmodt.h>
 
 #include <map>
+#include <memory>
 #include <utility>
 #include <vector>
 
 #include "base/memory/raw_ptr.h"
 #include "chrome/browser/chromeos/platform_keys/chaps_slot_session.h"
-#include "chrome/browser/chromeos/platform_keys/pkcs12_reader.h"
 #include "crypto/nss_key_util.h"
 #include "crypto/scoped_nss_types.h"
 #include "crypto/scoped_test_nss_db.h"
-#include "net/test/test_data_directory.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
-#include "third_party/boringssl/src/include/openssl/bn.h"
 
 namespace chromeos {
 namespace platform_keys {
 namespace {
 
+using ::testing::Optional;
+
 const size_t kKeySizeBits = 2048;
 
 // TODO(b/202374261): Move these into a shared header.
@@ -38,8 +35,9 @@
 // Chaps sets this for keys that are software-backed.
 constexpr CK_ATTRIBUTE_TYPE kKeyInSoftware = CKA_VENDOR_DEFINED + 5;
 
+const auto kOptCkTrue = Optional(CK_TRUE);
+const auto kOptCkFalse = Optional(CK_FALSE);
 enum AttrValueType { kNotDefined, kCkBool, kCkUlong, kCkBytes };
-const char kPkcs12FilePassword[] = "12345";
 
 // Class helper to keep relations between all possible attribute's types,
 // attribute's names and attribute's value types.
@@ -49,64 +47,44 @@
   ~AttributesParsingOptions() = default;
 
   static std::string GetName(const CK_ATTRIBUTE& attribute) {
-    if (!GetPkcs12ObjectAttrMap().contains(attribute.type)) {
-      ADD_FAILURE() << "Attribute value's type is unknown hex:" << std::hex
-                    << attribute.type;
+    if (!GetOptionsMap().contains(attribute.type)) {
+      ADD_FAILURE() << "Attribute name is unknown :" << attribute.type;
       return "";
     }
-    return std::get<std::string>(GetPkcs12ObjectAttrMap().at(attribute.type));
+    return std::get<std::string>(GetOptionsMap().at(attribute.type));
   }
 
   static AttrValueType GetValueType(const CK_ATTRIBUTE& attribute) {
-    if (!GetPkcs12ObjectAttrMap().contains(attribute.type)) {
-      ADD_FAILURE() << "Attribute value's type is unknown hex:" << std::hex
-                    << attribute.type;
+    if (!GetOptionsMap().contains(attribute.type)) {
+      ADD_FAILURE() << "Attribute value's type is unknown :" << attribute.type;
       return AttrValueType::kNotDefined;
+    } else {
+      return std::get<AttrValueType>(GetOptionsMap().at(attribute.type));
     }
-    return std::get<AttrValueType>(GetPkcs12ObjectAttrMap().at(attribute.type));
   }
 
  private:
   static const std::map<CK_ATTRIBUTE_TYPE,
                         std::pair<AttrValueType, std::string>>&
-  GetPkcs12ObjectAttrMap() {
-    // Map which keeps relation between PKCS12 object attribute type, attribute
-    // name and attribute value's type.
+  GetOptionsMap() {
+    // Map which keeps relation between attribute type, attribute name
+    // and attribute value's type.
     static std::map<CK_ATTRIBUTE_TYPE, std::pair<AttrValueType, std::string>>
-        attr_map;
-    if (attr_map.empty()) {
-      attr_map[CKA_TOKEN] = {kCkBool, "CKA_TOKEN"};
-      attr_map[CKA_PRIVATE] = {kCkBool, "CKA_PRIVATE"};
-      attr_map[CKA_VERIFY] = {kCkBool, "CKA_VERIFY"};
-      attr_map[CKA_MODULUS_BITS] = {kCkUlong, "CKA_MODULUS_BITS"};
-      attr_map[CKA_PUBLIC_EXPONENT] = {kCkBytes, "CKA_PUBLIC_EXPONENT"};
-      attr_map[CKA_SENSITIVE] = {kCkBool, "CKA_SENSITIVE"};
-      attr_map[CKA_EXTRACTABLE] = {kCkBool, "CKA_EXTRACTABLE"};
-      attr_map[CKA_SIGN] = {kCkBool, "CKA_SIGN"};
-      attr_map[kForceSoftwareAttribute] = {kCkBool, "kForceSoftwareAttribute"};
-      attr_map[CKA_CLASS] = {kCkUlong, "CKA_CLASS"};
-      attr_map[CKA_KEY_TYPE] = {kCkUlong, "CKA_KEY_TYPE"};
-      attr_map[CKA_UNWRAP] = {kCkBool, "CKA_UNWRAP"};
-      attr_map[CKA_DECRYPT] = {kCkBool, "CKA_DECRYPT"};
-      attr_map[CKA_MODULUS] = {kCkBytes, "CKA_MODULUS"};
-      attr_map[CKA_SIGN_RECOVER] = {kCkBool, "CKA_SIGN_RECOVER"};
-      attr_map[CKA_ID] = {kCkBytes, "CKA_ID"};
-      attr_map[CKA_PUBLIC_EXPONENT] = {kCkBytes, "CKA_PUBLIC_EXPONENT"};
-      attr_map[CKA_PRIVATE_EXPONENT] = {kCkBytes, "CKA_PRIVATE_EXPONENT"};
-      attr_map[CKA_PRIME_1] = {kCkBytes, "CKA_PRIME_1"};
-      attr_map[CKA_PRIME_2] = {kCkBytes, "CKA_PRIME_2"};
-      attr_map[CKA_EXPONENT_1] = {kCkBytes, "CKA_EXPONENT_1"};
-      attr_map[CKA_EXPONENT_2] = {kCkBytes, "CKA_EXPONENT_2"};
-      attr_map[CKA_COEFFICIENT] = {kCkBytes, "CKA_COEFFICIENT"};
-      attr_map[CKA_LABEL] = {kCkBytes, "CKA_LABEL"};
-      attr_map[CKA_VALUE] = {kCkBytes, "CKA_VALUE"};
-      attr_map[CKA_ISSUER] = {kCkBytes, "CKA_ISSUER"};
-      attr_map[CKA_SUBJECT] = {kCkBytes, "CKA_SUBJECT"};
-      attr_map[CKA_SERIAL_NUMBER] = {kCkBytes, "CKA_SERIAL_NUMBER"};
-      attr_map[CKA_NSS_EMAIL] = {kCkBytes, "CKA_NSS_EMAIL"};
-      attr_map[CKA_CERTIFICATE_TYPE] = {kCkBytes, "CKA_CERTIFICATE_TYPE"};
+        attr_type_to_options;
+    if (attr_type_to_options.empty()) {
+      attr_type_to_options[CKA_TOKEN] = {kCkBool, "CKA_TOKEN"};
+      attr_type_to_options[CKA_PRIVATE] = {kCkBool, "CKA_PRIVATE"};
+      attr_type_to_options[CKA_VERIFY] = {kCkBool, "CKA_VERIFY"};
+      attr_type_to_options[CKA_MODULUS_BITS] = {kCkUlong, "CKA_MODULUS_BITS"};
+      attr_type_to_options[CKA_PUBLIC_EXPONENT] = {kCkBytes,
+                                                   "CKA_PUBLIC_EXPONENT"};
+      attr_type_to_options[CKA_SENSITIVE] = {kCkBool, "CKA_SENSITIVE"};
+      attr_type_to_options[CKA_EXTRACTABLE] = {kCkBool, "CKA_EXTRACTABLE"};
+      attr_type_to_options[CKA_SIGN] = {kCkBool, "CKA_SIGN"};
+      attr_type_to_options[kForceSoftwareAttribute] = {
+          kCkBool, "kForceSoftwareAttribute"};
     }
-    return attr_map;
+    return attr_type_to_options;
   }
 };
 
@@ -129,7 +107,7 @@
         ck_bytes_value_ = ParseCkBytes(attribute);
         break;
       case kNotDefined:
-        ADD_FAILURE() << "Parser is not defined for attribute type:" << std::hex
+        ADD_FAILURE() << "Parser is not defined for attribute type:"
                       << attribute.type;
         break;
     }
@@ -239,31 +217,18 @@
 
   // The slot_id passed into FakeChapsSlotSessionFactory.
   absl::optional<CK_SLOT_ID> slot_id;
-
   // Attributes passed for the public key template to GenerateKeyPair.
   ObjectAttributes public_key_gen_attributes;
-
   // Attributes passed for the private key template to GenerateKeyPair.
   ObjectAttributes private_key_gen_attributes;
-
-  // The data passed into FakeChapsSlotSession::SetAttributeValue for the
-  // CKA_ID attribute of the public key. Empty if SetAttributeValue was never
-  // called for that attribute.
+  // The data passed into FakeChapsSlotSession::SetAttributeValue for the CKA_ID
+  // attribute of the public key. Empty if SetAttributeValue was never called
+  // for that attribute.
   std::vector<uint8_t> public_key_cka_id;
-
-  // The data passed into FakeChapsSlotSession::SetAttributeValue for the
-  // CKA_ID attribute of the private key. Empty if SetAttributeValue was never
-  // called for that attribute.
+  // The data passed into FakeChapsSlotSession::SetAttributeValue for the CKA_ID
+  // attribute of the private key. Empty if SetAttributeValue was never called
+  // for that attribute.
   std::vector<uint8_t> private_key_cka_id;
-
-  // The data passed into FakeChapsSlotSession::SetAttributeValue for creation
-  // of key object from PKCS12 container.
-  ObjectAttributes pkcs12_key_attributes;
-
-  // The data passed into FakeChapsSlotSession::SetAttributeValue for creation
-  // of certificates objects from PKCS12 container. PKCS12 container can hold
-  // multiple certificates.
-  std::vector<ObjectAttributes> pkcs12_cert_attributes;
 };
 
 // The FakeChapsSlotSession actually generating a key pair on a NSS slot.
@@ -382,30 +347,6 @@
     return CKR_OBJECT_HANDLE_INVALID;
   }
 
-  CK_RV CreateObject(CK_ATTRIBUTE_PTR pTemplate,
-                     CK_ULONG ulCount,
-                     CK_OBJECT_HANDLE_PTR phObject) override {
-    EXPECT_TRUE(session_ok_);
-    CK_RV configured_result = ApplyConfiguredResult();
-    if (configured_result != CKR_OK) {
-      return configured_result;
-    }
-
-    ObjectAttributes parsing_result =
-        ObjectAttributes::ParseFrom(pTemplate, ulCount);
-
-    AttributeData parsed_object_type =
-        parsing_result.parsed_attributes_map[CKA_CLASS];
-    if (parsed_object_type.CkULong() == CKO_PRIVATE_KEY) {
-      passed_data_->pkcs12_key_attributes = parsing_result;
-    }
-    if (parsed_object_type.CkULong() == CKO_CERTIFICATE) {
-      passed_data_->pkcs12_cert_attributes.push_back(parsing_result);
-    }
-
-    return CKR_OK;
-  }
-
  private:
   // Applies a result configured for the current operation, if any.
   CK_RV ApplyConfiguredResult() {
@@ -466,81 +407,6 @@
   const raw_ptr<PassedData, ExperimentalAsh> passed_data_;
 };
 
-// FakePkcs12Reader helper, by default it will call methods for the
-// original object.
-class FakePkcs12Reader : public Pkcs12Reader {
- public:
-  FakePkcs12Reader() = default;
-  ~FakePkcs12Reader() override = default;
-
-  Pkcs12ReaderStatusCode GetPkcs12KeyAndCerts(
-      const std::vector<uint8_t>& pkcs12_data,
-      const std::string& password,
-      bssl::UniquePtr<EVP_PKEY>& key,
-      bssl::UniquePtr<STACK_OF(X509)>& certs) const override {
-    if (get_pkcs12_key_and_cert_status != Pkcs12ReaderStatusCode::kSuccess) {
-      return get_pkcs12_key_and_cert_status;
-    }
-    return Pkcs12Reader::GetPkcs12KeyAndCerts(pkcs12_data, password, key,
-                                              certs);
-  }
-
-  Pkcs12ReaderStatusCode GetDerEncodedCert(X509* cert,
-                                           bssl::UniquePtr<uint8_t>& cert_der,
-                                           int& cert_der_size) const override {
-    if (get_cert_der_status != Pkcs12ReaderStatusCode::kSuccess) {
-      return get_cert_der_status;
-    }
-    return Pkcs12Reader::GetDerEncodedCert(cert, cert_der, cert_der_size);
-  }
-
-  Pkcs12ReaderStatusCode GetIssuerNameDer(
-      X509* cert,
-      base::span<const uint8_t>& issuer_name_data) const override {
-    if (get_issuer_name_der_status != Pkcs12ReaderStatusCode::kSuccess) {
-      return get_issuer_name_der_status;
-    }
-    return Pkcs12Reader::GetIssuerNameDer(cert, issuer_name_data);
-  }
-
-  Pkcs12ReaderStatusCode GetSubjectNameDer(
-      X509* cert,
-      base::span<const uint8_t>& subject_name_data) const override {
-    if (get_subject_name_der_status != Pkcs12ReaderStatusCode::kSuccess) {
-      return get_subject_name_der_status;
-    }
-    return Pkcs12Reader::GetSubjectNameDer(cert, subject_name_data);
-  }
-  Pkcs12ReaderStatusCode GetSerialNumberDer(
-      X509* cert,
-      bssl::UniquePtr<uint8_t>& serial_number_der,
-      int& serial_number_der_size) const override {
-    if (get_serial_number_der_status != Pkcs12ReaderStatusCode::kSuccess) {
-      return get_serial_number_der_status;
-    }
-    return Pkcs12Reader::GetSerialNumberDer(cert, serial_number_der,
-                                            serial_number_der_size);
-  }
-
-  std::vector<uint8_t> BignumToBytes(const BIGNUM* bignum) const override {
-    if (bignum_to_bytes_value) {
-      return bignum_to_bytes_value.value();
-    }
-    return Pkcs12Reader::BignumToBytes(bignum);
-  }
-
-  Pkcs12ReaderStatusCode get_pkcs12_key_and_cert_status =
-      Pkcs12ReaderStatusCode::kSuccess;
-  Pkcs12ReaderStatusCode get_cert_der_status = Pkcs12ReaderStatusCode::kSuccess;
-  Pkcs12ReaderStatusCode get_issuer_name_der_status =
-      Pkcs12ReaderStatusCode::kSuccess;
-  Pkcs12ReaderStatusCode get_subject_name_der_status =
-      Pkcs12ReaderStatusCode::kSuccess;
-  Pkcs12ReaderStatusCode get_serial_number_der_status =
-      Pkcs12ReaderStatusCode::kSuccess;
-  absl::optional<std::vector<uint8_t>> bignum_to_bytes_value = absl::nullopt;
-};
-
 class ChapsUtilImplTest : public ::testing::Test {
  public:
   ChapsUtilImplTest() {
@@ -556,25 +422,6 @@
   ~ChapsUtilImplTest() override = default;
 
  protected:
-  static std::vector<uint8_t> ReadTestFile(const std::string& file_name) {
-    base::FilePath file_path =
-        net::GetTestCertsDirectory().AppendASCII(file_name);
-    absl::optional<std::vector<uint8_t>> file_data = ReadFileToBytes(file_path);
-    EXPECT_TRUE(file_data.has_value());
-    if (!file_data.has_value()) {
-      return {};
-    }
-    return file_data.value();
-  }
-
-  static std::vector<uint8_t>& GetPkcs12Data() {
-    static std::vector<uint8_t> pkcs12_data_;
-    if (pkcs12_data_.empty()) {
-      pkcs12_data_ = ReadTestFile("client.p12");
-    }
-    return pkcs12_data_;
-  }
-
   crypto::ScopedTestNSSDB nss_test_db_;
   PassedData passed_data_;
 
@@ -606,24 +453,25 @@
   // Check attributes for public key.
   ObjectAttributes public_key_data = passed_data_.public_key_gen_attributes;
   const int expected_public_key_attributes = 5;
-  EXPECT_EQ(public_key_data.Size(), expected_public_key_attributes);
-  EXPECT_EQ(public_key_data.GetCkBool(CKA_TOKEN), CK_TRUE);
-  EXPECT_EQ(public_key_data.GetCkBool(CKA_PRIVATE), CK_FALSE);
-  EXPECT_EQ(public_key_data.GetCkBool(CKA_VERIFY), CK_TRUE);
-  EXPECT_EQ(public_key_data.GetCkULong(CKA_MODULUS_BITS), (CK_ULONG)2048);
-  EXPECT_EQ(public_key_data.GetCkByte(CKA_PUBLIC_EXPONENT),
-            (std::vector<CK_BYTE>{0x01, 0x00, 0x01}));
+  EXPECT_THAT(public_key_data.Size(), expected_public_key_attributes);
+  EXPECT_THAT(public_key_data.GetCkBool(CKA_TOKEN), kOptCkTrue);
+  EXPECT_THAT(public_key_data.GetCkBool(CKA_PRIVATE), kOptCkFalse);
+  EXPECT_THAT(public_key_data.GetCkBool(CKA_VERIFY), kOptCkTrue);
+  EXPECT_THAT(public_key_data.GetCkULong(CKA_MODULUS_BITS),
+              Optional((CK_ULONG)2048));
+  EXPECT_THAT(public_key_data.GetCkByte(CKA_PUBLIC_EXPONENT),
+              Optional(std::vector<CK_BYTE>{0x01, 0x00, 0x01}));
 
   // Check attributes for private key.
   ObjectAttributes private_key_data = passed_data_.private_key_gen_attributes;
   const int expected_private_key_attributes = 6;
-  EXPECT_EQ(private_key_data.Size(), expected_private_key_attributes);
-  EXPECT_EQ(private_key_data.GetCkBool(CKA_TOKEN), CK_TRUE);
-  EXPECT_EQ(private_key_data.GetCkBool(CKA_PRIVATE), CK_TRUE);
-  EXPECT_EQ(private_key_data.GetCkBool(CKA_SENSITIVE), CK_TRUE);
-  EXPECT_EQ(private_key_data.GetCkBool(CKA_EXTRACTABLE), CK_FALSE);
-  EXPECT_EQ(private_key_data.GetCkBool(kForceSoftwareAttribute), CK_TRUE);
-  EXPECT_EQ(private_key_data.GetCkBool(CKA_SIGN), CK_TRUE);
+  EXPECT_THAT(private_key_data.Size(), expected_private_key_attributes);
+  EXPECT_THAT(private_key_data.GetCkBool(CKA_TOKEN), kOptCkTrue);
+  EXPECT_THAT(private_key_data.GetCkBool(CKA_PRIVATE), kOptCkTrue);
+  EXPECT_THAT(private_key_data.GetCkBool(CKA_SENSITIVE), kOptCkTrue);
+  EXPECT_THAT(private_key_data.GetCkBool(CKA_EXTRACTABLE), kOptCkFalse);
+  EXPECT_THAT(private_key_data.GetCkBool(kForceSoftwareAttribute), kOptCkTrue);
+  EXPECT_THAT(private_key_data.GetCkBool(CKA_SIGN), kOptCkTrue);
 
   // Verify that ChapsUtil attempted to assign the correct CKA_ID to the public
   // and private key objects.
@@ -632,165 +480,6 @@
   EXPECT_EQ(passed_data_.private_key_cka_id, expected_cka_id);
 }
 
-// Verify that ChapsUtil passed the correct slot id to the factory.
-TEST_F(ChapsUtilImplTest, ImportPkcs12CertificateSuccessSlotOk) {
-  chaps_util_impl_->ImportPkcs12Certificate(
-      nss_test_db_.slot(), GetPkcs12Data(), kPkcs12FilePassword,
-      /*is_software_backed=*/true);
-
-  EXPECT_EQ(passed_data_.slot_id, PK11_GetSlotID(nss_test_db_.slot()));
-}
-
-// Successfully import public key and single certificate from PKCS12 file to
-// Chaps software slot.
-TEST_F(ChapsUtilImplTest, ImportPkcs12EnforceSoftwareBackedSuccess) {
-  using OPTIONAL_CK_BYTE_VECTOR = absl::optional<std::vector<CK_BYTE>>;
-  std::map<CK_ATTRIBUTE_TYPE, OPTIONAL_CK_BYTE_VECTOR> expected_key_data;
-  // Strings below have hardcoded fields from "client.p12" which is referenced
-  // by GetPkcs12Data(), they are Base64Encoded for the shorter representation.
-  // You can print original CkByte values from the key_data using this example:
-  // std::cout << base::Base64Encode(std::move(*key_data.GetCkByte(CKA_LABEL)));
-  expected_key_data[CKA_MODULUS] = base::Base64Decode(
-      "1JC7k5aWwwOpqoiNzoRHLRdmzH9h4kVmFlBU/vZ5e7hCSnnIbVJilMxDB+p0b7ozw1/"
-      "bHvsRqikARkMc0OnC4EMnm6BEopqiyOnNGBy1qXwol5Mw8T8zwlzJl7FQdQdlH7pMxuID8hZ"
-      "Eu8VkoEyLYJVJ1Ylaasc5BC0pHxZdNKk=");
-  expected_key_data[CKA_ID] =
-      base::Base64Decode("U65QueEa+ljfdKySfD6QbFrXEcM=");
-  expected_key_data[CKA_PUBLIC_EXPONENT] = base::Base64Decode("AQAB");
-  expected_key_data[CKA_PRIVATE_EXPONENT] = base::Base64Decode(
-      "y/k2hiFy+h+BqArxSMLWKgbStlll7GL7212qsh6B5J6jviOumHj98BsyF1577"
-      "NqY4VoSQmBaSxadFM9Bz5cBT8IrKr2/FjL1AC+wgdwUvGvbD426zN4Yb59cTf/"
-      "bhNkvd2xocFPHeMDETFD6ISEcV6YLbPAtNlom7qVxlSTn1KE=");
-  expected_key_data[CKA_PRIME_1] = base::Base64Decode(
-      "8W127p18wtuvUBxz7MtZgAPk/1OGLj1RJghuVYbHaCJ9sT5AzK8eNcRqCld/"
-      "bKABDdmYf3QHKYDx+vcrhcNF8w==");
-  expected_key_data[CKA_PRIME_2] = base::Base64Decode(
-      "4WVKE2h5oF7HYpX2sLgHXFhM77k6Hb1MalKk1MvXSYeKLnFf1Xh4Af2tUR73RmG/Mp/"
-      "evvUMu6h4AvlGvn+18w==");
-  expected_key_data[CKA_EXPONENT_1] = base::Base64Decode(
-      "SUZzCXstKaspq4PnP2B8upj0APalzBT6MzPt4PF2RknpokkFu9oOrjz9/"
-      "kOOPjbV+xEm8tAReGxVhVlNkVyyNw==");
-  expected_key_data[CKA_EXPONENT_2] = base::Base64Decode(
-      "DkFqwvl7n9H+yFR1ys2I4aVQEGVlsJXVbHAXrsHJtwPUkIVpK0Y4SN/"
-      "zg0rzFsd94UTNQMSc7o2EMaP0fn3zUw==");
-  expected_key_data[CKA_COEFFICIENT] = base::Base64Decode(
-      "mV2Q/My7RVOOsSZGDEouCYMcVahOFWS84IcpYRwR9ds0KZ4hKcdyMGNR5/4ryvr9XMA+DBR/"
-      "L9GBSWe6CeK3RQ==");
-
-  std::map<CK_ATTRIBUTE_TYPE, OPTIONAL_CK_BYTE_VECTOR> expected_cert_data;
-  // Strings below have hardcoded fields from "client.p12" which is referenced
-  // by GetPkcs12Data(), they are Base64Encoded for shorter representation.
-  // You can print original CkByte values from the key_data using this example:
-  // std::cout << base::Base64Encode(std::move(*key_data.GetCkByte(CKA_LABEL)));
-  expected_cert_data[CKA_CERTIFICATE_TYPE] = base::Base64Decode("AAAAAAAAAAA=");
-  expected_cert_data[CKA_ID] =
-      base::Base64Decode("U65QueEa+ljfdKySfD6QbFrXEcM=");
-  expected_cert_data[CKA_LABEL] = base::Base64Decode("dGVzdHVzZXJjZXJ0");
-  expected_cert_data[CKA_VALUE] = base::Base64Decode(
-      "MIICpTCCAg6gAwIBAgIBATANBgkqhkiG9w0BAQUFADBWMQswCQYDVQQGEwJBVTETMBEGA1UE"
-      "CBMKU29tZS1TdGF0ZTEhMB8GA1UEChMYSW50ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMQ8wDQYD"
-      "VQQDEwZ0ZXN0Y2EwIBcNMTAwNzMwMDEwMjEyWhgPMjA2MDA3MTcwMTAyMTJaMFwxCzAJBgNV"
-      "BAYTAkFVMRMwEQYDVQQIEwpTb21lLVN0YXRlMSEwHwYDVQQKExhJbnRlcm5ldCBXaWRnaXRz"
-      "IFB0eSBMdGQxFTATBgNVBAMTDHRlc3R1c2VyY2VydDCBnzANBgkqhkiG9w0BAQEFAAOBjQAw"
-      "gYkCgYEA1JC7k5aWwwOpqoiNzoRHLRdmzH9h4kVmFlBU/"
-      "vZ5e7hCSnnIbVJilMxDB+p0b7ozw1/"
-      "bHvsRqikARkMc0OnC4EMnm6BEopqiyOnNGBy1qXwol5Mw8T8zwlzJl7FQdQdlH7pMxuID8hZ"
-      "Eu8VkoEyLYJVJ1Ylaasc5BC0pHxZdNKkCAwEAAaN7MHkwCQYDVR0TBAIwADAsBglghkgBhvh"
-      "CAQ0EHxYdT3BlblNTTCBHZW5lcmF0ZWQgQ2VydGlmaWNhdGUwHQYDVR0OBBYEFHqEH18NKRV"
-      "bhkqTT8swZq22Dc4YMB8GA1UdIwQYMBaAFE8aGkwMhipgaDysVMfu3JaN29ILMA0GCSqGSIb"
-      "3DQEBBQUAA4GBAKMT7cwjZtgmkFrJPAa/"
-      "oOt1cdoBD7MqErx+tdvVN62q0h0Vl6UM3a94Ic0/"
-      "sv1V8RT5TUYUyyuepr2Gm58uqkcbI3qflveVcvi96n7fCCo6NwxbKHmpVOx+"
-      "wcPlHtjfek2KGQnee3mEN0YY/HOP5Rvj0Bh302kLrfgFx3xN1G5I");
-  expected_cert_data[CKA_ISSUER] = base::Base64Decode(
-      "MFYxCzAJBgNVBAYTAkFVMRMwEQYDVQQIEwpTb21lLVN0YXRlMSEwHwYDVQQKExhJbnRlcm5l"
-      "dCBXaWRnaXRzIFB0eSBMdGQxDzANBgNVBAMTBnRlc3RjYQ==");
-  expected_cert_data[CKA_SUBJECT] = base::Base64Decode(
-      "MFwxCzAJBgNVBAYTAkFVMRMwEQYDVQQIEwpTb21lLVN0YXRlMSEwHwYDVQQKExhJbnRlcm5l"
-      "dCBXaWRnaXRzIFB0eSBMdGQxFTATBgNVBAMTDHRlc3R1c2VyY2VydA==");
-  expected_cert_data[CKA_SERIAL_NUMBER] = base::Base64Decode("AgEB");
-  chaps_util_impl_->ImportPkcs12Certificate(
-      nss_test_db_.slot(), GetPkcs12Data(), kPkcs12FilePassword,
-      /*is_software_backed=*/true);
-
-  // Verify that ChapsUtil passed the correct slot id to the factory.
-  EXPECT_EQ(passed_data_.slot_id, PK11_GetSlotID(nss_test_db_.slot()));
-
-  // Verify that ChapsUtil passed the expected attributes.
-  // Check attributes for private key.
-  ObjectAttributes key_data = passed_data_.pkcs12_key_attributes;
-  const int expected_private_key_attributes = 19;
-  EXPECT_EQ(key_data.Size(), expected_private_key_attributes);
-  EXPECT_EQ(key_data.GetCkULong(CKA_CLASS), CKO_PRIVATE_KEY);
-  EXPECT_EQ(key_data.GetCkULong(CKA_KEY_TYPE), CKK_RSA);
-  EXPECT_EQ(key_data.GetCkBool(CKA_TOKEN), CK_TRUE);
-  EXPECT_EQ(key_data.GetCkBool(CKA_SENSITIVE), CK_TRUE);
-  EXPECT_EQ(key_data.GetCkBool(kForceSoftwareAttribute), CK_TRUE);
-  EXPECT_EQ(key_data.GetCkBool(CKA_PRIVATE), CK_TRUE);
-  EXPECT_EQ(key_data.GetCkBool(CKA_UNWRAP), CK_TRUE);
-  EXPECT_EQ(key_data.GetCkBool(CKA_DECRYPT), CK_TRUE);
-  EXPECT_EQ(key_data.GetCkBool(CKA_SIGN), CK_TRUE);
-  EXPECT_EQ(key_data.GetCkBool(CKA_SIGN_RECOVER), CK_TRUE);
-  EXPECT_EQ(key_data.GetCkByte(CKA_MODULUS), expected_key_data[CKA_MODULUS]);
-  EXPECT_EQ(key_data.GetCkByte(CKA_ID), expected_key_data[CKA_ID]);
-  EXPECT_EQ(key_data.GetCkByte(CKA_PUBLIC_EXPONENT),
-            expected_key_data[CKA_PUBLIC_EXPONENT]);
-  EXPECT_EQ(key_data.GetCkByte(CKA_PRIVATE_EXPONENT),
-            expected_key_data[CKA_PRIVATE_EXPONENT]);
-  EXPECT_EQ(key_data.GetCkByte(CKA_PRIME_1), expected_key_data[CKA_PRIME_1]);
-  EXPECT_EQ(key_data.GetCkByte(CKA_PRIME_2), expected_key_data[CKA_PRIME_2]);
-  EXPECT_EQ(key_data.GetCkByte(CKA_EXPONENT_1),
-            expected_key_data[CKA_EXPONENT_1]);
-  EXPECT_EQ(key_data.GetCkByte(CKA_EXPONENT_2),
-            expected_key_data[CKA_EXPONENT_2]);
-  EXPECT_EQ(key_data.GetCkByte(CKA_COEFFICIENT),
-            expected_key_data[CKA_COEFFICIENT]);
-
-  // Checking attributes for certificate.
-  ObjectAttributes cert_data = passed_data_.pkcs12_cert_attributes[0];
-  const int expected_cert_attributes = 10;
-  EXPECT_EQ(cert_data.Size(), expected_cert_attributes);
-  EXPECT_EQ(cert_data.GetCkBool(CKA_TOKEN), CK_TRUE);
-  EXPECT_EQ(cert_data.GetCkULong(CKA_CLASS), CKO_CERTIFICATE);
-  EXPECT_EQ(cert_data.GetCkByte(CKA_CERTIFICATE_TYPE),
-            expected_cert_data[CKA_CERTIFICATE_TYPE]);
-  EXPECT_EQ(cert_data.GetCkByte(CKA_ID), expected_cert_data[CKA_ID]);
-  EXPECT_EQ(cert_data.GetCkByte(CKA_VALUE), expected_cert_data[CKA_VALUE]);
-  EXPECT_EQ(cert_data.GetCkByte(CKA_ISSUER), expected_cert_data[CKA_ISSUER]);
-  EXPECT_EQ(cert_data.GetCkByte(CKA_SUBJECT), expected_cert_data[CKA_SUBJECT]);
-  EXPECT_EQ(cert_data.GetCkByte(CKA_SERIAL_NUMBER),
-            expected_cert_data[CKA_SERIAL_NUMBER]);
-  EXPECT_EQ(cert_data.GetCkByte(CKA_LABEL), expected_cert_data[CKA_LABEL]);
-}
-
-// This is same test as ImportPkcs12EnforceSoftBackSuccess but with
-// kKeyInSoftware = false, so key will be hardware backed.
-// Only number of stored attributes and required minimum of values is checked,
-// because we use same pkcs12 file "client.p12" and all values match already
-// checked in  ImportPkcs12EnforceSoftwareBackedSuccess test.
-TEST_F(ChapsUtilImplTest, ImportPkcs12HardwareBackedSuccess) {
-  chaps_util_impl_->ImportPkcs12Certificate(
-      nss_test_db_.slot(), GetPkcs12Data(), kPkcs12FilePassword,
-      /*is_software_backed=*/false);
-
-  // Verify that ChapsUtil passed the correct slot id to the factory.
-  EXPECT_EQ(passed_data_.slot_id, PK11_GetSlotID(nss_test_db_.slot()));
-
-  // Verify that ChapsUtil passed the expected attributes.
-  // Check only kForceSoftwareAttribute attribute for private key.
-  ObjectAttributes key_data = passed_data_.pkcs12_key_attributes;
-  const int expected_private_key_attributes = 19;
-  EXPECT_EQ(key_data.Size(), expected_private_key_attributes);
-  EXPECT_EQ(key_data.GetCkULong(CKA_CLASS), CKO_PRIVATE_KEY);
-  EXPECT_EQ(key_data.GetCkULong(CKA_KEY_TYPE), CKK_RSA);
-  EXPECT_EQ(key_data.GetCkBool(kForceSoftwareAttribute), CK_FALSE);
-
-  // Check only number of attributes for certificate.
-  ObjectAttributes cert_data = passed_data_.pkcs12_cert_attributes[0];
-  const int expected_cert_attributes = 10;
-  EXPECT_EQ(cert_data.Size(), expected_cert_attributes);
-}
-
 // The passed slot is not provided by chaps. The operation fails.
 TEST_F(ChapsUtilImplTest, NotChapsProvidedSlot) {
   chaps_util_impl_->SetIsChapsProvidedSlotForTesting(false);
@@ -882,103 +571,6 @@
   EXPECT_EQ(passed_data_.reopen_session_call_count, 1);
 }
 
-class ChapsUtilPKCS12ImportTest : public ChapsUtilImplTest {
- public:
-  ChapsUtilPKCS12ImportTest() {
-    GetPkcs12Data();
-    EXPECT_FALSE(GetPkcs12Data().empty());
-  }
-
-  bool RunImportPkcs12Certificate() {
-    return chaps_util_impl_->ImportPkcs12CertificateImpl(
-        nss_test_db_.slot(), GetPkcs12Data(), kPkcs12FilePassword,
-        /*is_software_backed=*/true, fake_pkcs12_reader_);
-  }
-
-  FakePkcs12Reader fake_pkcs12_reader_;
-};
-
-TEST_F(ChapsUtilPKCS12ImportTest, DefaultCasePKCS12ImportSuccessful) {
-  bool import_result = RunImportPkcs12Certificate();
-
-  EXPECT_EQ(import_result, true);
-}
-
-TEST_F(ChapsUtilPKCS12ImportTest, NoChapsSessionPKCS12ImportFailed) {
-  bool import_result = chaps_util_impl_->ImportPkcs12CertificateImpl(
-      /*slot=*/nullptr, GetPkcs12Data(), kPkcs12FilePassword,
-      /*is_software_backed=*/true, fake_pkcs12_reader_);
-
-  EXPECT_EQ(import_result, false);
-}
-
-// Failed import PKCS12 due to empty keys.
-TEST_F(ChapsUtilPKCS12ImportTest, EmptyKeyPtrPKCS12ImportFailed) {
-  fake_pkcs12_reader_.get_pkcs12_key_and_cert_status =
-      Pkcs12ReaderStatusCode::kKeyExtractionFailed;
-  bool import_result = RunImportPkcs12Certificate();
-  EXPECT_EQ(import_result, false);
-}
-
-// Failed import PKCS12 due to missed key attribute.
-TEST_F(ChapsUtilPKCS12ImportTest, MissedKeyAttributePKCS12ImportFailed) {
-  std::vector<uint8_t> empty_vector({});
-  // This will set all attributes to empty.
-  fake_pkcs12_reader_.bignum_to_bytes_value = absl::make_optional(empty_vector);
-  bool import_result = RunImportPkcs12Certificate();
-  EXPECT_EQ(import_result, false);
-}
-
-TEST_F(ChapsUtilPKCS12ImportTest, ImportOfKeyFailedPKCS12ImportFailed) {
-  // Mock CreateObject operations result.
-  passed_data_.operation_results[0] = CKR_GENERAL_ERROR;
-
-  bool import_result = RunImportPkcs12Certificate();
-  EXPECT_EQ(import_result, false);
-}
-
-TEST_F(ChapsUtilPKCS12ImportTest, FailedGetCertDerPKCS12ImportFailed) {
-  fake_pkcs12_reader_.get_cert_der_status =
-      Pkcs12ReaderStatusCode::kKeyExtractionFailed;
-
-  bool import_result = RunImportPkcs12Certificate();
-  EXPECT_EQ(import_result, false);
-}
-
-TEST_F(ChapsUtilPKCS12ImportTest, FailedGetIssuerNameDerPKCS12ImportFailed) {
-  fake_pkcs12_reader_.get_issuer_name_der_status =
-      Pkcs12ReaderStatusCode::kPkcs12CertIssuerDerNameFailed;
-
-  bool import_result = RunImportPkcs12Certificate();
-  EXPECT_EQ(import_result, false);
-}
-
-TEST_F(ChapsUtilPKCS12ImportTest, FailedGetSubjectNameDerPKCS12ImportFailed) {
-  fake_pkcs12_reader_.get_subject_name_der_status =
-      Pkcs12ReaderStatusCode::kPkcs12CertSubjectNameDerFailed;
-
-  bool import_result = RunImportPkcs12Certificate();
-  EXPECT_EQ(import_result, false);
-}
-
-TEST_F(ChapsUtilPKCS12ImportTest, FailedGetSerialNumberDerPKCS12ImportFailed) {
-  fake_pkcs12_reader_.get_serial_number_der_status =
-      Pkcs12ReaderStatusCode::kPkcs12CertSerialNumberDerFailed;
-
-  bool import_result = RunImportPkcs12Certificate();
-  EXPECT_EQ(import_result, false);
-}
-
-TEST_F(ChapsUtilPKCS12ImportTest, CertObjectCreationFailedPKCS12ImportFailed) {
-  // Mock CreateObject operations result for key import.
-  passed_data_.operation_results[0] = CKR_OK;
-  // Mock CreateObject operations result for the certificate import.
-  passed_data_.operation_results[1] = CKR_GENERAL_ERROR;
-
-  bool import_result = RunImportPkcs12Certificate();
-  EXPECT_EQ(import_result, false);
-}
-
 }  // namespace
 }  // namespace platform_keys
 }  // namespace chromeos
diff --git a/chrome/browser/chromeos/platform_keys/pkcs12_reader.cc b/chrome/browser/chromeos/platform_keys/pkcs12_reader.cc
deleted file mode 100644
index 258a6d1c..0000000
--- a/chrome/browser/chromeos/platform_keys/pkcs12_reader.cc
+++ /dev/null
@@ -1,146 +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 <vector>
-
-#include "chrome/browser/chromeos/platform_keys/pkcs12_reader.h"
-#include "third_party/boringssl/src/include/openssl/bn.h"
-#include "third_party/boringssl/src/include/openssl/mem.h"
-#include "third_party/boringssl/src/include/openssl/pkcs8.h"
-
-namespace chromeos::platform_keys {
-
-std::vector<uint8_t> Pkcs12Reader::BignumToBytes(const BIGNUM* bignum) const {
-  std::vector<uint8_t> result(BN_num_bytes(bignum));
-  BN_bn2bin(bignum, result.data());
-
-  return result;
-}
-
-Pkcs12ReaderStatusCode Pkcs12Reader::GetPkcs12KeyAndCerts(
-    const std::vector<uint8_t>& pkcs12_data,
-    const std::string& password,
-    bssl::UniquePtr<EVP_PKEY>& key,
-    bssl::UniquePtr<STACK_OF(X509)>& certs) const {
-  CBS pkcs12;
-  CBS_init(&pkcs12, reinterpret_cast<const uint8_t*>(pkcs12_data.data()),
-           pkcs12_data.size());
-  if (!pkcs12.data || pkcs12.len <= 0) {
-    return Pkcs12ReaderStatusCode::kMissedPkcs12Data;
-  }
-
-  EVP_PKEY* key_ptr = nullptr;
-  certs = bssl::UniquePtr<STACK_OF(X509)>(sk_X509_new_null());
-  const int get_key_and_cert_result = PKCS12_get_key_and_certs(
-      &key_ptr, certs.get(), &pkcs12, password.c_str());
-  key = bssl::UniquePtr<EVP_PKEY>(key_ptr);
-  if (!get_key_and_cert_result || !key_ptr) {
-    return Pkcs12ReaderStatusCode::kFailedToParsePkcs12Data;
-  }
-  return Pkcs12ReaderStatusCode::kSuccess;
-}
-
-Pkcs12ReaderStatusCode Pkcs12Reader::GetDerEncodedCert(
-    X509* cert,
-    bssl::UniquePtr<uint8_t>& cert_der,
-    int& cert_der_size) const {
-  if (!cert) {
-    return Pkcs12ReaderStatusCode::kPkcs12CertDerMissed;
-  }
-
-  uint8_t* cert_der_ptr = nullptr;
-  cert_der_size = i2d_X509(cert, &cert_der_ptr);
-  cert_der = bssl::UniquePtr<uint8_t>(cert_der_ptr);
-  if (cert_der_size <= 0) {
-    return Pkcs12ReaderStatusCode::kPkcs12CertDerFailed;
-  }
-  return Pkcs12ReaderStatusCode::kSuccess;
-}
-
-Pkcs12ReaderStatusCode Pkcs12Reader::GetIssuerNameDer(
-    X509* cert,
-    base::span<const uint8_t>& issuer_name_data) const {
-  if (!cert) {
-    return Pkcs12ReaderStatusCode::kPkcs12CertIssuerNameMissed;
-  }
-
-  X509_NAME* issuer_name = X509_get_issuer_name(cert);
-  if (!issuer_name) {
-    return Pkcs12ReaderStatusCode::kPkcs12CertIssuerNameMissed;
-  }
-
-  const uint8_t* name_der;
-  size_t name_der_size;
-  if (!X509_NAME_get0_der(issuer_name, &name_der, &name_der_size)) {
-    return Pkcs12ReaderStatusCode::kPkcs12CertIssuerDerNameFailed;
-  }
-  issuer_name_data = {name_der, name_der_size};
-  return Pkcs12ReaderStatusCode::kSuccess;
-}
-
-Pkcs12ReaderStatusCode Pkcs12Reader::GetSubjectNameDer(
-    X509* cert,
-    base::span<const uint8_t>& subject_name_data) const {
-  if (!cert) {
-    return Pkcs12ReaderStatusCode::kPkcs12CertSubjectNameMissed;
-  }
-
-  X509_NAME* subject_name = X509_get_subject_name(cert);
-  if (!subject_name) {
-    return Pkcs12ReaderStatusCode::kPkcs12CertSubjectNameMissed;
-  }
-
-  const uint8_t* name_der;
-  size_t name_der_size;
-  if (!X509_NAME_get0_der(subject_name, &name_der, &name_der_size)) {
-    return Pkcs12ReaderStatusCode::kPkcs12CertSubjectNameDerFailed;
-  }
-  subject_name_data = {name_der, name_der_size};
-  return Pkcs12ReaderStatusCode::kSuccess;
-}
-
-Pkcs12ReaderStatusCode Pkcs12Reader::GetSerialNumberDer(
-    X509* cert,
-    bssl::UniquePtr<uint8_t>& der_serial_number,
-    int& der_serial_number_size) const {
-  if (!cert) {
-    return Pkcs12ReaderStatusCode::kPkcs12CertSerialNumberMissed;
-  }
-
-  const ASN1_INTEGER* serial_number = X509_get0_serialNumber(cert);
-  uint8_t* der_serial_number_ptr = nullptr;
-  der_serial_number_size =
-      i2d_ASN1_INTEGER(serial_number, &der_serial_number_ptr);
-  der_serial_number = bssl::UniquePtr<uint8_t>(der_serial_number_ptr);
-  if (der_serial_number_size < 0) {
-    return Pkcs12ReaderStatusCode::kPkcs12CertSerialNumberDerFailed;
-  }
-  return Pkcs12ReaderStatusCode::kSuccess;
-}
-
-Pkcs12ReaderStatusCode Pkcs12Reader::GetLabel(X509* cert,
-                                              std::string& label) const {
-  if (!cert) {
-    return Pkcs12ReaderStatusCode::kPkcs12CertIssuerNameMissed;
-  }
-
-  // This is basic implementation which is using common name from the
-  // Subject name for the label.
-  // TODO(b/284144984): Replace with proper implementation and update tests.
-  X509_NAME* subject_name = X509_get_subject_name(cert);
-  if (!subject_name) {
-    return Pkcs12ReaderStatusCode::kPkcs12CertIssuerNameMissed;
-  }
-
-  char temp_label[512] = "";
-  int get_label_result = X509_NAME_get_text_by_NID(
-      subject_name, NID_commonName, temp_label, sizeof(temp_label));
-  if (!get_label_result) {
-    return Pkcs12ReaderStatusCode::kPkcs12LabelCreationFailed;
-  }
-
-  label = temp_label;
-  return Pkcs12ReaderStatusCode::kSuccess;
-}
-
-}  // namespace chromeos::platform_keys
diff --git a/chrome/browser/chromeos/platform_keys/pkcs12_reader.h b/chrome/browser/chromeos/platform_keys/pkcs12_reader.h
deleted file mode 100644
index 313361cf..0000000
--- a/chrome/browser/chromeos/platform_keys/pkcs12_reader.h
+++ /dev/null
@@ -1,90 +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_CHROMEOS_PLATFORM_KEYS_PKCS12_READER_H_
-#define CHROME_BROWSER_CHROMEOS_PLATFORM_KEYS_PKCS12_READER_H_
-
-#include "base/containers/span.h"
-#include "chrome/browser/chromeos/platform_keys/chaps_slot_session.h"
-#include "third_party/boringssl/src/include/openssl/pkcs7.h"
-
-namespace chromeos::platform_keys {
-
-enum class Pkcs12ReaderStatusCode {
-  kSuccess = 0,
-  kCreateKeyFailed = 1,
-  kCertificateDataMissed = 2,
-  kCreateCertFailed = 3,
-  kKeyDataMissed = 4,
-  kKeyExtractionFailed = 5,
-  kChapsSessionMissed = 6,
-  kPkcs12CertDerMissed = 7,
-  kPkcs12CertDerFailed = 8,
-  kPkcs12CertIssuerNameMissed = 9,
-  kPkcs12CertIssuerDerNameFailed = 10,
-  kPkcs12CertSubjectNameMissed = 11,
-  kPkcs12CertSubjectNameDerFailed = 12,
-  kPkcs12CertSerialNumberMissed = 13,
-  kPkcs12CertSerialNumberDerFailed = 14,
-  kKeyAttrDataMissing = 15,
-  kFailureDuringCertImport = 16,
-  kFailedToParsePkcs12Data = 17,
-  kMissedPkcs12Data = 18,
-  kPkcs12LabelCreationFailed = 19,
-};
-
-// Class helper for operations with X509 certificates data which are required
-// for storing keys and certificates in Chaps.
-class Pkcs12Reader {
- public:
-  Pkcs12Reader() = default;
-
-  virtual ~Pkcs12Reader() = default;
-
-  // Populates key and certificates (`key`, `certs`) from the PKCS#12 object
-  // `pkcs12_data` protected by the `password`. Returns status code.
-  virtual Pkcs12ReaderStatusCode GetPkcs12KeyAndCerts(
-      const std::vector<uint8_t>& pkcs12_data,
-      const std::string& password,
-      bssl::UniquePtr<EVP_PKEY>& key,
-      bssl::UniquePtr<STACK_OF(X509)>& certs) const;
-
-  // Populates der encoded certificate and its size (`cert_der`,
-  // `cert_der_size`) from X509 (`cert`). Returns status code.
-  virtual Pkcs12ReaderStatusCode GetDerEncodedCert(
-      X509* cert,
-      bssl::UniquePtr<uint8_t>& cert_der,
-      int& cert_der_size) const;
-
-  // Populates der encoded issuer name and its size (`issuer_name_data`) from
-  // X509 (`cert`). `issuer_name_data` remains valid only as long as the cert is
-  // alive because it is only referencing data. Returns status code.
-  virtual Pkcs12ReaderStatusCode GetIssuerNameDer(
-      X509* cert,
-      base::span<const uint8_t>& issuer_name_data) const;
-
-  // Populates der encoded subject name and its size (`subject_name_data`) from
-  // X509 (`cert`). `subject_name_data` remains valid only as long as the cert
-  // is alive because it is only referencing data. Returns status code.
-  virtual Pkcs12ReaderStatusCode GetSubjectNameDer(
-      X509* cert,
-      base::span<const uint8_t>& subject_name_data) const;
-
-  // Populates der encoded serial number and its size (`serial_number_der`,
-  // `serial_number_der_size`) from X509 (`cert`). Returns status code.
-  virtual Pkcs12ReaderStatusCode GetSerialNumberDer(
-      X509* cert,
-      bssl::UniquePtr<uint8_t>& serial_number_der,
-      int& serial_number_der_size) const;
-
-  // Populates label (`label`) from X509 (`cert`). Returns status code.
-  virtual Pkcs12ReaderStatusCode GetLabel(X509* cert, std::string& label) const;
-
-  // Converts BIGNUM (`bignum`) to bytes.
-  virtual std::vector<uint8_t> BignumToBytes(const BIGNUM* bignum) const;
-};
-
-}  // namespace chromeos::platform_keys
-
-#endif  // CHROME_BROWSER_CHROMEOS_PLATFORM_KEYS_PKCS12_READER_H_
diff --git a/chrome/browser/chromeos/platform_keys/pkcs12_reader_unittest.cc b/chrome/browser/chromeos/platform_keys/pkcs12_reader_unittest.cc
deleted file mode 100644
index b3cefec9..0000000
--- a/chrome/browser/chromeos/platform_keys/pkcs12_reader_unittest.cc
+++ /dev/null
@@ -1,329 +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 <vector>
-
-#include "chrome/browser/chromeos/platform_keys/pkcs12_reader.h"
-#include "testing/gmock/include/gmock/gmock.h"
-#include "third_party/boringssl/src/include/openssl/mem.h"
-#include "third_party/boringssl/src/include/openssl/x509.h"
-
-namespace chromeos::platform_keys {
-namespace {
-
-const char kPkcs12FilePassword[] = "12345";
-
-// Custom X509 object creation allows to avoid calls to X509_free()
-// after every test where X509 objects are required.
-struct X509Deleter {
-  void operator()(X509* cert) { X509_free(cert); }
-};
-using ScopedX509 = std::unique_ptr<X509, X509Deleter>;
-ScopedX509 X509New() {
-  return ScopedX509(X509_new());
-}
-
-// Custom X509_NAME object creation allows to avoid calls to X509_NAME_free()
-// after every test where X509_NAME objects are required.
-struct X509NameDeleter {
-  void operator()(X509_NAME* name) { X509_NAME_free(name); }
-};
-using ScopedX509_NAME = std::unique_ptr<X509_NAME, X509NameDeleter>;
-ScopedX509_NAME X509NameNew() {
-  return ScopedX509_NAME(X509_NAME_new());
-}
-
-// Tests for testing methods in chaps_util_helper.cc
-// ChapsUtilImplTest is testing successful import and values, these tests
-// are mainly checking errors handling.
-class Pkcs12ReaderTest : public ::testing::Test {
- public:
-  Pkcs12ReaderTest() { pkcs12Reader_ = std::make_unique<Pkcs12Reader>(); }
-  Pkcs12ReaderTest(const Pkcs12ReaderTest&) = delete;
-  Pkcs12ReaderTest& operator=(const Pkcs12ReaderTest&) = delete;
-  ~Pkcs12ReaderTest() override = default;
-
-  Pkcs12ReaderStatusCode GetSerialNumberDer(X509* cert) {
-    int serial_number_der_size;
-    bssl::UniquePtr<uint8_t> serial_number_der;
-    return pkcs12Reader_->GetSerialNumberDer(cert, serial_number_der,
-                                             serial_number_der_size);
-  }
-
-  Pkcs12ReaderStatusCode GetIssuerNameDer(X509* cert) {
-    base::span<const uint8_t> issuer_name_data;
-    return pkcs12Reader_->GetIssuerNameDer(cert, issuer_name_data);
-  }
-
-  Pkcs12ReaderStatusCode GetSubjectNameDer(X509* cert) {
-    base::span<const uint8_t> subject_name_data;
-    return pkcs12Reader_->GetSubjectNameDer(cert, subject_name_data);
-  }
-
-  Pkcs12ReaderStatusCode GetDerEncodedCert(X509* cert) {
-    int cert_der_size;
-    bssl::UniquePtr<uint8_t> cert_der_ptr;
-    return pkcs12Reader_->GetDerEncodedCert(cert, cert_der_ptr, cert_der_size);
-  }
-
-  Pkcs12ReaderStatusCode GetLabel(X509* cert) {
-    std::string label;
-    return pkcs12Reader_->GetLabel(cert, label);
-  }
-
-  void SetFieldToX509Name(X509_NAME* X509_name,
-                          const char field[],
-                          unsigned char value[]) {
-    X509_NAME_add_entry_by_txt(X509_name,
-                               /*field=*/field,
-                               /*type=*/MBSTRING_ASC,
-                               /*bytes=*/value,
-                               /*len=*/-1,
-                               /*loc=*/-1,
-                               /*set=*/0);
-  }
-
-  void SetOrgDataToX509Name(X509_NAME* X509_name) {
-    // Country
-    unsigned char country_name[] = "DE";
-    SetFieldToX509Name(X509_name, "C", country_name);
-
-    // Company/Organization
-    unsigned char org_name[] = "Test company";
-    SetFieldToX509Name(X509_name, "O", org_name);
-
-    // Common name
-    unsigned char common_name[] = "common_name";
-    SetFieldToX509Name(X509_name, "CN", common_name);
-  }
-
- protected:
-  std::unique_ptr<Pkcs12Reader> pkcs12Reader_;
-};
-
-TEST_F(Pkcs12ReaderTest, EmptyBigNumReturnsEmptyVector) {
-  BIGNUM* bignum = new BIGNUM();
-  BN_zero(bignum);
-  std::vector<uint8_t> expected_empty_vector({});
-
-  EXPECT_EQ(pkcs12Reader_->BignumToBytes(bignum), expected_empty_vector);
-}
-
-TEST_F(Pkcs12ReaderTest, MaxBigNumConvertedCorrectly) {
-  BIGNUM* bignum = new BIGNUM();
-  BN_set_u64(bignum, 0xFFFFFFFFFFFFFFFF);
-  std::vector<uint8_t> expected_data({
-      0xFF,
-      0xFF,
-      0xFF,
-      0xFF,
-      0xFF,
-      0xFF,
-      0xFF,
-      0xFF,
-  });
-
-  std::vector<uint8_t> bignumToBytes = pkcs12Reader_->BignumToBytes(bignum);
-
-  EXPECT_EQ(bignumToBytes, expected_data);
-}
-
-TEST_F(Pkcs12ReaderTest, BigNumZeroConvertedToEmptyVector) {
-  BIGNUM* bignum = new BIGNUM();
-  BN_set_u64(bignum, 0x00000000000000);
-  std::vector<uint8_t> expected_data({});
-
-  std::vector<uint8_t> bignumToBytes = pkcs12Reader_->BignumToBytes(bignum);
-
-  EXPECT_EQ(bignumToBytes, expected_data);
-}
-
-TEST_F(Pkcs12ReaderTest, BigNumWithFrontZerosConvertedCorrectly) {
-  BIGNUM* bignum = new BIGNUM();
-  BN_set_u64(bignum, 0x00000000000100);
-  std::vector<uint8_t> expected_data({0x01, 0x00});
-
-  std::vector<uint8_t> bignumToBytes = pkcs12Reader_->BignumToBytes(bignum);
-
-  EXPECT_EQ(bignumToBytes, expected_data);
-}
-
-TEST_F(Pkcs12ReaderTest, EmptyBigNumConvertedCorrectly) {
-  BIGNUM* bignum = new BIGNUM();
-  std::vector<uint8_t> expected_data({});
-
-  std::vector<uint8_t> bignumToBytes = pkcs12Reader_->BignumToBytes(bignum);
-
-  EXPECT_EQ(bignumToBytes, expected_data);
-}
-
-TEST_F(Pkcs12ReaderTest, CertsGetSerialNumber) {
-  // Empty certificate, operation will fail.
-  {
-    X509* cert = nullptr;
-
-    Pkcs12ReaderStatusCode result = GetSerialNumberDer(cert);
-    EXPECT_EQ(result, Pkcs12ReaderStatusCode::kPkcs12CertSerialNumberMissed);
-  }
-
-  // Empty serial number, operation will succeed.
-  {
-    ScopedX509 cert = X509New();
-
-    Pkcs12ReaderStatusCode result = GetSerialNumberDer(cert.get());
-    EXPECT_EQ(result, Pkcs12ReaderStatusCode::kSuccess);
-  }
-
-  // Certificate with normal serial number, operation will succeed.
-  // Check only import success, values are checked in ChapsUtilImplTest.
-  {
-    ScopedX509 cert = X509New();
-    ASN1_INTEGER_set(X509_get_serialNumber(cert.get()), 1);
-    int serial_number_der_size;
-    bssl::UniquePtr<uint8_t> serial_number_der;
-
-    Pkcs12ReaderStatusCode result = pkcs12Reader_->GetSerialNumberDer(
-        cert.get(), serial_number_der, serial_number_der_size);
-    EXPECT_EQ(result, Pkcs12ReaderStatusCode::kSuccess);
-  }
-}
-
-TEST_F(Pkcs12ReaderTest, GetIssuerNameDer) {
-  // Empty certificate, operation will fail.
-  {
-    Pkcs12ReaderStatusCode result = GetIssuerNameDer(nullptr);
-    EXPECT_EQ(result, Pkcs12ReaderStatusCode::kPkcs12CertIssuerNameMissed);
-  }
-
-  // Empty object for the issuer, operation will succeed.
-  {
-    ScopedX509 cert = X509New();
-
-    Pkcs12ReaderStatusCode result = GetIssuerNameDer(cert.get());
-    EXPECT_EQ(result, Pkcs12ReaderStatusCode::kSuccess);
-  }
-
-  // Certificate with normal issuer name, operation will succeed.
-  // Check only import success, values are checked in ChapsUtilImplTest.
-  {
-    ScopedX509 cert = X509New();
-    ScopedX509_NAME issuer = X509NameNew();
-
-    // This only sets org name, country and common name.
-    SetOrgDataToX509Name(issuer.get());
-    X509_set_issuer_name(cert.get(), issuer.get());
-    base::span<const uint8_t> issuer_name_data;
-
-    Pkcs12ReaderStatusCode result =
-        pkcs12Reader_->GetIssuerNameDer(cert.get(), issuer_name_data);
-    EXPECT_EQ(result, Pkcs12ReaderStatusCode::kSuccess);
-  }
-}
-
-TEST_F(Pkcs12ReaderTest, GetSubjectNameDer) {
-  // Empty certificate, operation will fail.
-  {
-    Pkcs12ReaderStatusCode result = GetSubjectNameDer(nullptr);
-    EXPECT_EQ(result, Pkcs12ReaderStatusCode::kPkcs12CertSubjectNameMissed);
-  }
-
-  // Empty object for the subject name, operation will succeed.
-  {
-    ScopedX509 cert = X509New();
-
-    Pkcs12ReaderStatusCode result = GetIssuerNameDer(cert.get());
-    EXPECT_EQ(result, Pkcs12ReaderStatusCode::kSuccess);
-  }
-
-  // Certificate with normal subject name, operation will succeed.
-  // Check only import success, values are checked in ChapsUtilImplTest.
-  {
-    ScopedX509 cert = X509New();
-    ScopedX509_NAME subject = X509NameNew();
-
-    // This only sets org name, country and common name.
-    SetOrgDataToX509Name(subject.get());
-    X509_set_subject_name(cert.get(), subject.get());
-    base::span<const uint8_t> subject_name_data;
-
-    Pkcs12ReaderStatusCode result =
-        pkcs12Reader_->GetSubjectNameDer(cert.get(), subject_name_data);
-    EXPECT_EQ(result, Pkcs12ReaderStatusCode::kSuccess);
-  }
-}
-
-TEST_F(Pkcs12ReaderTest, GetCertDer) {
-  // No certificate, operation will fail.
-  {
-    Pkcs12ReaderStatusCode result = GetDerEncodedCert(nullptr);
-    EXPECT_EQ(result, Pkcs12ReaderStatusCode::kPkcs12CertDerMissed);
-  }
-
-  // Empty certificate, operation will fail.
-  {
-    ScopedX509 cert = X509New();
-
-    Pkcs12ReaderStatusCode result = GetDerEncodedCert(cert.get());
-    EXPECT_EQ(result, Pkcs12ReaderStatusCode::kPkcs12CertDerFailed);
-  }
-}
-
-TEST_F(Pkcs12ReaderTest, GetPkcs12KeyAndCerts) {
-  // No pkcs12 data, operation will fail.
-  {
-    bssl::UniquePtr<EVP_PKEY> key;
-    bssl::UniquePtr<STACK_OF(X509)> certs;
-    const std::vector<uint8_t>& pkcs12_data = {};
-
-    Pkcs12ReaderStatusCode result = pkcs12Reader_->GetPkcs12KeyAndCerts(
-        pkcs12_data, kPkcs12FilePassword, key, certs);
-    EXPECT_EQ(result, Pkcs12ReaderStatusCode::kMissedPkcs12Data);
-  }
-
-  // Wrong pkcs12 data's, operation will fail.
-  {
-    bssl::UniquePtr<EVP_PKEY> key;
-    bssl::UniquePtr<STACK_OF(X509)> certs;
-    const std::vector<uint8_t>& wrong_pkcs12_data = {0, 0, 0, 0, 0,
-                                                     0, 0, 0, 0, 0};
-
-    Pkcs12ReaderStatusCode result = pkcs12Reader_->GetPkcs12KeyAndCerts(
-        wrong_pkcs12_data, kPkcs12FilePassword, key, certs);
-    EXPECT_EQ(result, Pkcs12ReaderStatusCode::kFailedToParsePkcs12Data);
-  }
-}
-
-TEST_F(Pkcs12ReaderTest, GetLabel) {
-  // Empty certificate, operation will fail.
-  {
-    Pkcs12ReaderStatusCode result = GetLabel(nullptr);
-    EXPECT_EQ(result, Pkcs12ReaderStatusCode::kPkcs12CertIssuerNameMissed);
-  }
-
-  // Empty object for the issuer, operation will succeed.
-  {
-    ScopedX509 cert = X509New();
-
-    Pkcs12ReaderStatusCode result = GetLabel(cert.get());
-    EXPECT_EQ(result, Pkcs12ReaderStatusCode::kSuccess);
-  }
-
-  // Certificate with normal issuer name, operation will succeed.
-  // Check only import success, values are checked in ChapsUtilImplTest.
-  {
-    ScopedX509 cert = X509New();
-    ScopedX509_NAME subject = X509NameNew();
-
-    // This only sets org name, country and common name.
-    SetOrgDataToX509Name(subject.get());
-    X509_set_subject_name(cert.get(), subject.get());
-    std::string label;
-
-    Pkcs12ReaderStatusCode result = pkcs12Reader_->GetLabel(cert.get(), label);
-    EXPECT_EQ(result, Pkcs12ReaderStatusCode::kSuccess);
-  }
-}
-
-}  // namespace
-}  // namespace chromeos::platform_keys
diff --git a/chrome/browser/device_reauth/android/device_authenticator_android.cc b/chrome/browser/device_reauth/android/device_authenticator_android.cc
index ed0cb0a..f0c4af0 100644
--- a/chrome/browser/device_reauth/android/device_authenticator_android.cc
+++ b/chrome/browser/device_reauth/android/device_authenticator_android.cc
@@ -74,6 +74,7 @@
     case device_reauth::DeviceAuthRequester::kDeviceLockPage:
     case device_reauth::DeviceAuthRequester::kPaymentMethodsReauthInSettings:
     case device_reauth::DeviceAuthRequester::kVirtualCardAutofill:
+    case device_reauth::DeviceAuthRequester::kPaymentsAutofillOptIn:
       return false;
   }
 }
diff --git a/chrome/browser/device_reauth/mac/device_authenticator_mac_unittest.mm b/chrome/browser/device_reauth/mac/device_authenticator_mac_unittest.mm
index a33f02c..903f9a2 100644
--- a/chrome/browser/device_reauth/mac/device_authenticator_mac_unittest.mm
+++ b/chrome/browser/device_reauth/mac/device_authenticator_mac_unittest.mm
@@ -20,6 +20,10 @@
 #include "device/fido/mac/touch_id_context.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
 namespace {
 
 using MockAuthResultCallback =
@@ -64,7 +68,7 @@
 
   void SimulateReauthSuccess() {
     if (is_biometric_available()) {
-      touch_id_enviroment()->SimulateTouchIdPromptSuccess();
+      touch_id_environment()->SimulateTouchIdPromptSuccess();
     } else {
       EXPECT_CALL(system_authenticator(), AuthenticateUserWithNonBiometrics)
           .WillOnce(testing::Return(true));
@@ -73,7 +77,7 @@
 
   void SimulateReauthFailure() {
     if (is_biometric_available()) {
-      touch_id_enviroment()->SimulateTouchIdPromptFailure();
+      touch_id_environment()->SimulateTouchIdPromptFailure();
     } else {
       EXPECT_CALL(system_authenticator(), AuthenticateUserWithNonBiometrics)
           .WillOnce(testing::Return(false));
@@ -92,7 +96,7 @@
 
   base::test::TaskEnvironment& task_environment() { return task_environment_; }
 
-  device::fido::mac::ScopedTouchIdTestEnvironment* touch_id_enviroment() {
+  device::fido::mac::ScopedTouchIdTestEnvironment* touch_id_environment() {
     return &touch_id_test_environment_;
   }
 
@@ -133,8 +137,8 @@
 
   // Since the delay is smaller than kAuthValidityPeriod there shouldn't be
   // another prompt, so the auth should be reported as successful. If there is a
-  // call to touchIdContext test will fail as TouchIdEnviroment will crash since
-  // there is no prompt expected.
+  // call to touchIdContext test will fail as TouchIdEnvironment will crash
+  // since there is no prompt expected.
   task_environment().FastForwardBy(
       PasswordAccessAuthenticator::kAuthValidityPeriod / 2);
 
@@ -168,19 +172,19 @@
       result_callback().Get());
 }
 
-// If prevoius authentication failed kAuthValidityPeriod isn't started and
+// If previous authentication failed kAuthValidityPeriod isn't started and
 // reauthentication will be needed.
 TEST_P(DeviceAuthenticatorMacTest, ReauthenticationIfPreviousFailed) {
   SimulateReauthFailure();
 
-  // First authetication fails, no last_good_auth_timestamp_ should be
+  // First authentication fails, no last_good_auth_timestamp_ should be
   // recorded, which fill force reauthentication.
   EXPECT_CALL(result_callback(), Run(/*success=*/false));
   authenticator()->AuthenticateWithMessage(
       /*message=*/u"Chrome is trying to show passwords.",
       result_callback().Get());
 
-  // Although it passed less than kAuthValidityPeriod no valid authenticaion
+  // Although it passed less than kAuthValidityPeriod no valid authentication
   // should be recorded as reauth will fail.
   SimulateReauthFailure();
   task_environment().FastForwardBy(
@@ -193,25 +197,25 @@
 }
 
 // If pending authentication can be canceled.
-TEST_P(DeviceAuthenticatorMacTest, CancelPendngAuthentication) {
+TEST_P(DeviceAuthenticatorMacTest, CancelPendingAuthentication) {
   // Non-biometric reauth is modal, and hence cannot be requested twice.
   if (!is_biometric_available()) {
     return;
   }
-  touch_id_enviroment()->SimulateTouchIdPromptSuccess();
-  touch_id_enviroment()->DoNotResolveNextPrompt();
+  touch_id_environment()->SimulateTouchIdPromptSuccess();
+  touch_id_environment()->DoNotResolveNextPrompt();
 
   authenticator()->AuthenticateWithMessage(
       /*message=*/u"Chrome is trying to show passwords.",
       result_callback().Get());
 
   // Authentication should fail as it will take 10 seconds to authenticate, and
-  // there will be a cancelation in the meantime.
+  // there will be a cancellation in the meantime.
   EXPECT_CALL(result_callback(), Run(/*success=*/false));
   authenticator()->Cancel(DeviceAuthRequester::kPasswordsInSettings);
 }
 
-TEST_P(DeviceAuthenticatorMacTest, BiometricAuthenticationAvailablity) {
+TEST_P(DeviceAuthenticatorMacTest, BiometricAuthenticationAvailability) {
   EXPECT_CALL(system_authenticator(), CheckIfBiometricsAvailable);
   EXPECT_EQ(authenticator()->CanAuthenticateWithBiometrics(),
             is_biometric_available());
diff --git a/chrome/browser/devtools/devtools_browsertest.cc b/chrome/browser/devtools/devtools_browsertest.cc
index b896bf4..387514e 100644
--- a/chrome/browser/devtools/devtools_browsertest.cc
+++ b/chrome/browser/devtools/devtools_browsertest.cc
@@ -3046,7 +3046,7 @@
   ui_test_utils::WaitForBrowserToClose(browser());
 }
 
-#if !BUILDFLAG(IS_CHROMEOS_ASH)
+#if !BUILDFLAG(IS_CHROMEOS)
 // Skip for ChromeOS because the keep alive is not created for ChromeOS.
 // See https://crbug.com/1174627.
 class KeepAliveDevToolsTest : public InProcessBrowserTest {
@@ -3072,7 +3072,7 @@
   EXPECT_FALSE(KeepAliveRegistry::GetInstance()->IsOriginRegistered(
       KeepAliveOrigin::REMOTE_DEBUGGING));
 }
-#endif  // !BUILDFLAG(IS_CHROMEOS_ASH)
+#endif  // !BUILDFLAG(IS_CHROMEOS)
 
 class DevToolsPolicyTest : public InProcessBrowserTest {
  protected:
diff --git a/chrome/browser/enterprise/connectors/service_provider_config.cc b/chrome/browser/enterprise/connectors/service_provider_config.cc
index 623ff53..8b3f749 100644
--- a/chrome/browser/enterprise/connectors/service_provider_config.cc
+++ b/chrome/browser/enterprise/connectors/service_provider_config.cc
@@ -69,6 +69,10 @@
 }  // namespace
 
 const ServiceProviderConfig* GetServiceProviderConfig() {
+  // The policy schema validates that the provider name is an expected value, so
+  // when one is added to this dictionary it also needs to be added to the
+  // corresponding policy definitions.
+  // LINT.IfChange
   static constexpr ServiceProviderConfig kServiceProviderConfig =
       base::MakeFixedFlatMap<base::StringPiece, ServiceProvider>({
           {
@@ -106,6 +110,13 @@
               },
           },
       });
+  // LINT.ThenChange(//components/policy/resources/templates/policy_definitions/Miscellaneous)
+  // The following policies should have their service_provider entries updated:
+  //   //components/policy/resources/templates/policy_definitions/Miscellaneous/OnBulkDataEntryEnterpriseConnector.yaml,
+  //   //components/policy/resources/templates/policy_definitions/Miscellaneous/OnFileAttachedEnterpriseConnector.yaml,
+  //   //components/policy/resources/templates/policy_definitions/Miscellaneous/OnFileDownloadedEnterpriseConnector.yaml,
+  //   //components/policy/resources/templates/policy_definitions/Miscellaneous/OnPrintEnterpriseConnector.yaml
+  // )
   return &kServiceProviderConfig;
 }
 
diff --git a/chrome/browser/extensions/api/autofill_private/autofill_private_api.cc b/chrome/browser/extensions/api/autofill_private/autofill_private_api.cc
index 265dfda..a956d2e7 100644
--- a/chrome/browser/extensions/api/autofill_private/autofill_private_api.cc
+++ b/chrome/browser/extensions/api/autofill_private/autofill_private_api.cc
@@ -18,7 +18,6 @@
 #include "chrome/browser/extensions/api/autofill_private/autofill_util.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/common/extensions/api/autofill_private.h"
-#include "chrome/grit/chromium_strings.h"
 #include "components/autofill/content/browser/content_autofill_client.h"
 #include "components/autofill/content/browser/content_autofill_driver.h"
 #include "components/autofill/content/browser/content_autofill_driver_factory.h"
@@ -36,6 +35,8 @@
 #include "components/autofill/core/common/autofill_payments_features.h"
 #include "components/autofill/core/common/autofill_prefs.h"
 #include "components/signin/public/identity_manager/account_info.h"
+#include "components/strings/grit/components_chromium_strings.h"
+#include "components/strings/grit/components_google_chrome_strings.h"
 #include "components/strings/grit/components_strings.h"
 #include "content/public/browser/web_contents.h"
 #include "extensions/browser/extension_function.h"
diff --git a/chrome/browser/extensions/extension_keeplist_chromeos.cc b/chrome/browser/extensions/extension_keeplist_chromeos.cc
index ab92bb8..4298a84 100644
--- a/chrome/browser/extensions/extension_keeplist_chromeos.cc
+++ b/chrome/browser/extensions/extension_keeplist_chromeos.cc
@@ -65,21 +65,20 @@
 base::span<const base::StringPiece> ExtensionsRunInOSOnlyAllowlist() {
   static const base::StringPiece kKeeplist[] = {
 #if BUILDFLAG(IS_CHROMEOS_ASH)
+    extension_misc::kAccessibilityCommonExtensionId,
+    extension_misc::kEnhancedNetworkTtsExtensionId,
     extension_misc::kEspeakSpeechSynthesisExtensionId,
     extension_misc::kGoogleSpeechSynthesisExtensionId,
-    extension_misc::kEnhancedNetworkTtsExtensionId,
-    extension_misc::kSelectToSpeakExtensionId,
-    extension_misc::kAccessibilityCommonExtensionId,
-    extension_misc::kSwitchAccessExtensionId,
-    extension_misc::kSigninProfileTestExtensionId,
     extension_misc::kGuestModeTestExtensionId,
     extension_misc::kHelpAppExtensionId,
-    extension_misc::kAutotestPrivateTestExtensionId,
+    extension_misc::kSelectToSpeakExtensionId,
+    extension_misc::kSigninProfileTestExtensionId,
+    extension_misc::kSwitchAccessExtensionId,
     file_manager::kImageLoaderExtensionId,
 #endif
-    extension_misc::kKeyboardExtensionId,
-    extension_misc::kChromeVoxExtensionId,
     extension_misc::kBruSecurityKeyForwarderExtensionId,
+    extension_misc::kChromeVoxExtensionId,
+    extension_misc::kKeyboardExtensionId,
   };
 
   return base::make_span(kKeeplist);
@@ -91,10 +90,10 @@
     arc::kPlayStoreAppId,
     extension_misc::kFilesManagerAppId,
 #endif
-    extension_misc::kGoogleKeepAppId,
     extension_misc::kCalculatorAppId,
+    extension_misc::kGoogleKeepAppId,
+    extension_misc::kIdentityApiUiAppId,
     extension_misc::kInAppPaymentsSupportAppId,
-    extension_misc::kIdentityApiUiAppId
   };
 
   return base::make_span(kKeeplist);
diff --git a/chrome/browser/flag-metadata.json b/chrome/browser/flag-metadata.json
index 28bacb1..1b1ffa39 100644
--- a/chrome/browser/flag-metadata.json
+++ b/chrome/browser/flag-metadata.json
@@ -1410,6 +1410,13 @@
     "expiry_milestone": 120
   },
   {
+    "name": "cws-info-fast-check",
+    "owners": [
+      "anunoy"
+    ],
+    "expiry_milestone": 125
+  },
+  {
     "name": "darken-websites-checkbox-in-themes-setting",
     "owners": [ "nemco@google.com", "wenyufu@google.com", "twellington" ],
     "expiry_milestone": 115
@@ -4876,7 +4883,7 @@
   },
   {
     "name": "lacros-color-management",
-    "owners": [ "mrfemi", "jshargo" ],
+    "owners": [ "mrfemi", "chromeos-gfx-compositor@google.com" ],
     // This flag is used for chrome color management
     "expiry_milestone": 120
   },
@@ -5639,6 +5646,11 @@
     "expiry_milestone": 120
   },
   {
+    "name": "omnibox-cr23-suggestion-hover-fill-shape",
+    "owners": [ "khalidpeer", "manukh", "chrome-omnibox-team@google.com" ],
+    "expiry_milestone": 120
+  },
+  {
     "name": "omnibox-cr23-umbrella",
     "owners": [ "manukh", "yohanes", "khalid", "chrome-omnibox-team@google.com" ],
     "expiry_milestone": 120
@@ -6763,6 +6775,15 @@
     "expiry_milestone": 123
   },
   {
+    "name": "safety-check-extensions",
+    "owners": [
+      "zackhan",
+      "anunoy",
+      "psarouthakis@google.com"
+    ],
+    "expiry_milestone": 125
+  },
+  {
     "name": "safety-check-notification-permissions",
     "owners": ["sideyilmaz"],
     "expiry_milestone": 120
@@ -7407,6 +7428,11 @@
     "expiry_milestone": 118
   },
   {
+    "name": "thumbnail-placeholder",
+    "owners": [ "ckitagawa", "fredmello" ],
+    "expiry_milestone": 118
+  },
+  {
     "name": "time-of-day-screen-saver",
     "owners": [ "esum", "jasontt", "assistive-eng@google.com" ],
     "expiry_milestone": 120
@@ -7880,6 +7906,16 @@
     "expiry_milestone": 124
   },
   {
+    "name": "webapk-install-failure-notification",
+    "owners": [ "eirage"],
+    "expiry_milestone": 119
+  },
+  {
+    "name": "webapk-install-failure-retry",
+    "owners": [ "eirage"],
+    "expiry_milestone": 119
+  },
+  {
     "name": "webpage-alternative-text-zoom",
     "owners": [ "rkgibson@google.com", "bling-flags@google.com" ],
     "expiry_milestone": 120
diff --git a/chrome/browser/flag_descriptions.cc b/chrome/browser/flag_descriptions.cc
index c3f82061..b8048060 100644
--- a/chrome/browser/flag_descriptions.cc
+++ b/chrome/browser/flag_descriptions.cc
@@ -1540,6 +1540,17 @@
     "`chrome.identity` functions. Browser Tab can be displayed either in a New "
     "Tab or a Popup Window via the feature paramters.";
 
+const char kCWSInfoFastCheckName[] = "CWS Info Fast Check";
+const char kCWSInfoFastCheckDescription[] =
+    "When enabled, Chrome checks and fetches metadata for installed extensions "
+    "more frequently.";
+
+const char kSafetyCheckExtensionsName[] = "Extensions Module in Safety Check";
+const char kSafetyCheckExtensionsDescription[] =
+    "When enabled, adds the Extensions Module to Safety Check on "
+    "desktop. The module will be shown if there are potentially unsafe "
+    "extensions to review.";
+
 #if BUILDFLAG(IS_CHROMEOS_ASH)
 extern const char kExtensionWebFileHandlersName[] =
     "Extensions Web File Handlers";
@@ -2223,6 +2234,12 @@
 const char kOmniboxCR23SteadyStateIconsDescription[] =
     "Updates Omnibox steady state icons to comply with CR23 guidelines.";
 
+const char kOmniboxCR23SuggestionHoverFillShapeName[] =
+    "Omnibox Suggestion Hover Fill Shape";
+const char kOmniboxCR23SuggestionHoverFillShapeDescription[] =
+    "Updates Omnibox suggestion hover fill shape to comply with CR23 "
+    "guidelines.";
+
 const char kOmniboxIgnoreIntermediateResultsName[] =
     "Ignore intermediate Autocomplete results.";
 const char kOmniboxIgnoreIntermediateResultsDescription[] =
@@ -4277,6 +4294,10 @@
 const char kTabGroupsForTabletsName[] = "Tab groups on tablets";
 const char kTabGroupsForTabletsDescription[] = "Enable tab groups on tablets.";
 
+const char kThumbnailPlaceholderName[] = "Thumbnail placeholder";
+const char kThumbnailPlaceholderDescription[] =
+    "Display a placeholder image for missing thumbnails.";
+
 const char kDiscoverFeedMultiColumnAndroidName[] =
     "Multi-column Discover feed Android.";
 const char kDiscoverFeedMultiColumnAndroidDescription[] =
@@ -4399,6 +4420,16 @@
     "Use Credential Management API for passkeys. Requires Android 14 or "
     "higher.";
 
+const char kWebApkInstallFailureNotificationName[] =
+    "Web app install failure notification";
+const char kWebApkInstallFailureNotificationDescription[] =
+    "Enables showing a notification when web app install failed";
+
+const char kWebApkInstallFailureRetryName[] = "Web app install retry";
+const char kWebApkInstallFailureRetryDescription[] =
+    "Allows user to retry failed web app installs with the failure "
+    "notification";
+
 const char kWebFeedName[] = "Web Feed";
 const char kWebFeedDescription[] =
     "Allows users to keep up with and consume web content.";
@@ -6857,7 +6888,7 @@
 const char kLacrosColorManagementName[] = "Enable Chrome Color Management.";
 const char kLacrosColorManagementDescription[] =
     "Uses chrome-color-management wayland protocol to manage color spaces "
-    "for lacros.";
+    "for lacros. This is necessary for enabling HDR on compatible devices.";
 
 const char kLinkCapturingInfoBarName[] = "Enable link capturing info bar";
 const char kLinkCapturingInfoBarDescription[] =
diff --git a/chrome/browser/flag_descriptions.h b/chrome/browser/flag_descriptions.h
index 7e3abf2..03131706 100644
--- a/chrome/browser/flag_descriptions.h
+++ b/chrome/browser/flag_descriptions.h
@@ -857,6 +857,12 @@
 extern const char kWebAuthFlowInBrowserTabName[];
 extern const char kWebAuthFlowInBrowserTabDescription[];
 
+extern const char kCWSInfoFastCheckName[];
+extern const char kCWSInfoFastCheckDescription[];
+
+extern const char kSafetyCheckExtensionsName[];
+extern const char kSafetyCheckExtensionsDescription[];
+
 #if BUILDFLAG(IS_CHROMEOS_ASH)
 extern const char kExtensionWebFileHandlersName[];
 extern const char kExtensionWebFileHandlersDescription[];
@@ -1251,6 +1257,9 @@
 extern const char kOmniboxCR23SteadyStateIconsName[];
 extern const char kOmniboxCR23SteadyStateIconsDescription[];
 
+extern const char kOmniboxCR23SuggestionHoverFillShapeName[];
+extern const char kOmniboxCR23SuggestionHoverFillShapeDescription[];
+
 extern const char kOmniboxIgnoreIntermediateResultsName[];
 extern const char kOmniboxIgnoreIntermediateResultsDescription[];
 
@@ -2493,6 +2502,9 @@
 extern const char kTabGroupsForTabletsName[];
 extern const char kTabGroupsForTabletsDescription[];
 
+extern const char kThumbnailPlaceholderName[];
+extern const char kThumbnailPlaceholderDescription[];
+
 extern const char kTouchDragAndContextMenuName[];
 extern const char kTouchDragAndContextMenuDescription[];
 
@@ -2520,6 +2532,12 @@
 extern const char kWebAuthnAndroidCredManName[];
 extern const char kWebAuthnAndroidCredManDescription[];
 
+extern const char kWebApkInstallFailureNotificationName[];
+extern const char kWebApkInstallFailureNotificationDescription[];
+
+extern const char kWebApkInstallFailureRetryName[];
+extern const char kWebApkInstallFailureRetryDescription[];
+
 extern const char kWebFeedName[];
 extern const char kWebFeedDescription[];
 
diff --git a/chrome/browser/flags/android/chrome_feature_list.cc b/chrome/browser/flags/android/chrome_feature_list.cc
index e6fc12d..583c2c6 100644
--- a/chrome/browser/flags/android/chrome_feature_list.cc
+++ b/chrome/browser/flags/android/chrome_feature_list.cc
@@ -306,6 +306,7 @@
     &kTabToGTSAnimation,
     &kTestDefaultDisabled,
     &kTestDefaultEnabled,
+    &kThumbnailPlaceholder,
     &kToolbarMicIphAndroid,
     &kToolbarScrollAblationAndroid,
     &kTrustedWebActivityPostMessage,
@@ -1030,6 +1031,10 @@
              "TestDefaultEnabled",
              base::FEATURE_ENABLED_BY_DEFAULT);
 
+BASE_FEATURE(kThumbnailPlaceholder,
+             "ThumbnailPlaceholder",
+             base::FEATURE_DISABLED_BY_DEFAULT);
+
 BASE_FEATURE(kToolbarMicIphAndroid,
              "ToolbarMicIphAndroid",
              base::FEATURE_DISABLED_BY_DEFAULT);
diff --git a/chrome/browser/flags/android/chrome_feature_list.h b/chrome/browser/flags/android/chrome_feature_list.h
index 5b9e916c..d78d2701 100644
--- a/chrome/browser/flags/android/chrome_feature_list.h
+++ b/chrome/browser/flags/android/chrome_feature_list.h
@@ -178,6 +178,7 @@
 BASE_DECLARE_FEATURE(kTabToGTSAnimation);
 BASE_DECLARE_FEATURE(kTestDefaultDisabled);
 BASE_DECLARE_FEATURE(kTestDefaultEnabled);
+BASE_DECLARE_FEATURE(kThumbnailPlaceholder);
 BASE_DECLARE_FEATURE(kToolbarMicIphAndroid);
 BASE_DECLARE_FEATURE(kToolbarScrollAblationAndroid);
 BASE_DECLARE_FEATURE(kToolbarUseHardwareBitmapDraw);
diff --git a/chrome/browser/flags/android/java/src/org/chromium/chrome/browser/flags/ChromeFeatureList.java b/chrome/browser/flags/android/java/src/org/chromium/chrome/browser/flags/ChromeFeatureList.java
index e734231..bd8d94d 100644
--- a/chrome/browser/flags/android/java/src/org/chromium/chrome/browser/flags/ChromeFeatureList.java
+++ b/chrome/browser/flags/android/java/src/org/chromium/chrome/browser/flags/ChromeFeatureList.java
@@ -446,6 +446,7 @@
     public static final String TEST_DEFAULT_DISABLED = "TestDefaultDisabled";
     public static final String TEST_DEFAULT_ENABLED = "TestDefaultEnabled";
     public static final String THUMBNAIL_CACHE_REFACTOR = "ThumbnailCacheRefactor";
+    public static final String THUMBNAIL_PLACEHOLDER = "ThumbnailPlaceholder";
     public static final String TOOLBAR_MIC_IPH_ANDROID = "ToolbarMicIphAndroid";
     public static final String TOOLBAR_SCROLL_ABLATION_ANDROID = "ToolbarScrollAblationAndroid";
     public static final String TOOLBAR_USE_HARDWARE_BITMAP_DRAW = "ToolbarUseHardwareBitmapDraw";
diff --git a/chrome/browser/global_keyboard_shortcuts_mac_unittest.mm b/chrome/browser/global_keyboard_shortcuts_mac_unittest.mm
index f8c8b532..ecae30f15 100644
--- a/chrome/browser/global_keyboard_shortcuts_mac_unittest.mm
+++ b/chrome/browser/global_keyboard_shortcuts_mac_unittest.mm
@@ -16,6 +16,10 @@
 #include "ui/base/buildflags.h"
 #include "ui/events/keycodes/keyboard_code_conversion_mac.h"
 
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
 namespace {
 
 enum class CommandKeyState : bool {
diff --git a/chrome/browser/headless/test/data/protocol/sanity/print-to-pdf-tiny-page-expected.txt b/chrome/browser/headless/test/data/protocol/sanity/print-to-pdf-tiny-page-expected.txt
index 509e742..06873bf 100644
--- a/chrome/browser/headless/test/data/protocol/sanity/print-to-pdf-tiny-page-expected.txt
+++ b/chrome/browser/headless/test/data/protocol/sanity/print-to-pdf-tiny-page-expected.txt
@@ -2,7 +2,7 @@
 {
     error : {
         code : -32602
-        message : invalid print parameters
+        message : invalid print parameters: content area is empty
     }
     id : <number>
     sessionId : <string>
diff --git a/chrome/browser/lacros/desk_template_client_lacros.cc b/chrome/browser/lacros/desk_template_client_lacros.cc
index 98c3d94..915e365 100644
--- a/chrome/browser/lacros/desk_template_client_lacros.cc
+++ b/chrome/browser/lacros/desk_template_client_lacros.cc
@@ -181,6 +181,11 @@
   create_params.creation_source = Browser::CreationSource::kDeskTemplate;
   Browser* browser = Browser::Create(create_params);
 
+  // TODO(crbug.com/1442076): Remove after issue is root caused.
+  LOG(ERROR) << "window " << additional_state->restore_window_id
+             << " created by lacros with " << additional_state->urls.size()
+             << " tabs";
+
   for (size_t i = 0; i < additional_state->urls.size(); i++) {
     chrome::AddTabAt(
         browser, additional_state->urls.at(i), /*index=*/-1,
diff --git a/chrome/browser/mac/exception_processor_unittest.mm b/chrome/browser/mac/exception_processor_unittest.mm
index 88cc552..37801de 100644
--- a/chrome/browser/mac/exception_processor_unittest.mm
+++ b/chrome/browser/mac/exception_processor_unittest.mm
@@ -13,6 +13,10 @@
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
 namespace chrome {
 
 // Generate an NSException with the given name.
@@ -22,7 +26,7 @@
                                userInfo:nil];
 }
 
-// Helper to keep binning expectations readible.
+// Helper to keep binning expectations readable.
 size_t BinForExceptionNamed(NSString* name) {
   return BinForException(ExceptionNamed(name));
 }
diff --git a/chrome/browser/media_galleries/mac/mtp_device_delegate_impl_mac_unittest.mm b/chrome/browser/media_galleries/mac/mtp_device_delegate_impl_mac_unittest.mm
index 36f6caa..9f228b8 100644
--- a/chrome/browser/media_galleries/mac/mtp_device_delegate_impl_mac_unittest.mm
+++ b/chrome/browser/media_galleries/mac/mtp_device_delegate_impl_mac_unittest.mm
@@ -2,17 +2,17 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "base/memory/raw_ptr.h"
-
 #import <Foundation/Foundation.h>
 #import <ImageCaptureCore/ImageCaptureCore.h>
+#include "base/files/file_path.h"
 
+#include "base/apple/bridging.h"
 #include "base/files/file.h"
 #include "base/files/file_util.h"
 #include "base/files/scoped_temp_dir.h"
 #include "base/functional/bind.h"
 #include "base/mac/foundation_util.h"
-#include "base/mac/scoped_nsobject.h"
+#include "base/memory/raw_ptr.h"
 #include "base/strings/sys_string_conversions.h"
 #include "base/synchronization/waitable_event.h"
 #include "chrome/browser/media_galleries/mac/mtp_device_delegate_impl_mac.h"
@@ -21,6 +21,10 @@
 #include "content/public/test/browser_task_environment.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
 namespace {
 
 const char kDeviceId[] = "id";
@@ -35,7 +39,7 @@
 
 @interface MockMTPICCameraDevice : ICCameraDevice {
  @private
-  base::scoped_nsobject<NSMutableArray> _allMediaFiles;
+  NSMutableArray* __strong _allMediaFiles;
 }
 
 - (void)addMediaFile:(ICCameraFile*)file;
@@ -45,7 +49,10 @@
 @implementation MockMTPICCameraDevice
 
 - (instancetype)init {
-  return [super initWithDictionary:@{}];
+  if (self = [super initWithDictionary:@{}]) {
+    _allMediaFiles = [NSMutableArray array];
+  }
+  return self;
 }
 
 - (NSString*)mountPoint {
@@ -75,8 +82,6 @@
 }
 
 - (void)addMediaFile:(ICCameraFile*)file {
-  if (!_allMediaFiles.get())
-    _allMediaFiles.reset([[NSMutableArray alloc] init]);
   [_allMediaFiles addObject:file];
 }
 
@@ -85,8 +90,8 @@
            downloadDelegate:(id<ICCameraDeviceDownloadDelegate>)downloadDelegate
         didDownloadSelector:(SEL)selector
                 contextInfo:(void*)contextInfo {
-  base::FilePath saveDir(
-      base::SysNSStringToUTF8([options[ICDownloadsDirectoryURL] path]));
+  base::FilePath saveDir =
+      base::mac::NSURLToFilePath(options[ICDownloadsDirectoryURL]);
   std::string saveAsFilename =
       base::SysNSStringToUTF8(options[ICSaveAsFilename]);
   // It appears that the ImageCapture library adds an extension to the requested
@@ -95,8 +100,7 @@
   base::FilePath toBeSaved = saveDir.Append(saveAsFilename);
   ASSERT_TRUE(base::WriteFile(toBeSaved, kTestFileContents));
 
-  NSMutableDictionary* returnOptions =
-      [NSMutableDictionary dictionaryWithDictionary:options];
+  NSMutableDictionary* returnOptions = [options mutableCopy];
   returnOptions[ICSavedFilename] = base::SysUTF8ToNSString(saveAsFilename);
 
   [static_cast<NSObject<ICCameraDeviceDownloadDelegate>*>(downloadDelegate)
@@ -110,8 +114,8 @@
 
 @interface MockMTPICCameraFile : ICCameraFile {
  @private
-  base::scoped_nsobject<NSString> _name;
-  base::scoped_nsobject<NSDate> _date;
+  NSString* __strong _name;
+  NSDate* __strong _date;
 }
 
 - (instancetype)init:(NSString*)name;
@@ -122,29 +126,28 @@
 
 - (instancetype)init:(NSString*)name {
   if ((self = [super init])) {
-    base::scoped_nsobject<NSDateFormatter> iso8601day(
-        [[NSDateFormatter alloc] init]);
-    [iso8601day setDateFormat:@"yyyy-MM-dd"];
-    _name.reset([name retain]);
-    _date.reset([[iso8601day dateFromString:@"2012-12-12"] retain]);
+    NSDateFormatter* iso8601day = [[NSDateFormatter alloc] init];
+    iso8601day.dateFormat = @"yyyy-MM-dd";
+    _name = [name copy];
+    _date = [iso8601day dateFromString:@"2012-12-12"];
   }
   return self;
 }
 
 - (NSString*)name {
-  return _name.get();
+  return _name;
 }
 
 - (NSString*)UTI {
-  return base::mac::CFToNSCast(kUTTypeImage);
+  return base::apple::CFToNSPtrCast(kUTTypeImage);
 }
 
 - (NSDate*)modificationDate {
-  return _date.get();
+  return _date;
 }
 
 - (NSDate*)creationDate {
-  return _date.get();
+  return _date;
 }
 
 - (off_t)fileSize {
@@ -155,7 +158,7 @@
 
 class MTPDeviceDelegateImplMacTest : public testing::Test {
  public:
-  MTPDeviceDelegateImplMacTest() : camera_(NULL), delegate_(nullptr) {}
+  MTPDeviceDelegateImplMacTest() = default;
 
   MTPDeviceDelegateImplMacTest(const MTPDeviceDelegateImplMacTest&) = delete;
   MTPDeviceDelegateImplMacTest& operator=(const MTPDeviceDelegateImplMacTest&) =
@@ -280,10 +283,10 @@
 
   base::ScopedTempDir temp_dir_;
   storage_monitor::ImageCaptureDeviceManager manager_;
-  MockMTPICCameraDevice* camera_;
+  MockMTPICCameraDevice* __strong camera_ = nullptr;
 
   // This object needs special deletion inside the above |task_runner_|.
-  raw_ptr<MTPDeviceDelegateImplMac> delegate_;
+  raw_ptr<MTPDeviceDelegateImplMac> delegate_ = nullptr;
 
   base::File::Error error_;
   base::File::Info info_;
@@ -508,9 +511,8 @@
   info.last_accessed = t1;
   info.creation_time = t1;
   std::string kTestFileName("filename");
-  base::scoped_nsobject<MockMTPICCameraFile> picture1(
-      [[MockMTPICCameraFile alloc]
-          init:base::SysUTF8ToNSString(kTestFileName)]);
+  MockMTPICCameraFile* picture1 =
+      [[MockMTPICCameraFile alloc] init:base::SysUTF8ToNSString(kTestFileName)];
   [camera_ addMediaFile:picture1];
   delegate_->ItemAdded(kTestFileName, info);
   delegate_->NoMoreItems();
diff --git a/chrome/browser/metrics/power/coalition_resource_usage_provider_mac_unittest.mm b/chrome/browser/metrics/power/coalition_resource_usage_provider_mac_unittest.mm
index 5ecaa81e..a01aa75 100644
--- a/chrome/browser/metrics/power/coalition_resource_usage_provider_mac_unittest.mm
+++ b/chrome/browser/metrics/power/coalition_resource_usage_provider_mac_unittest.mm
@@ -10,6 +10,10 @@
 #include "components/power_metrics/resource_coalition_mac.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
 TEST(CoalitionResourceUsageProviderTest, Availability) {
   base::HistogramTester histogram_tester;
   CoalitionResourceUsageProvider provider;
diff --git a/chrome/browser/page_load_metrics/observers/core/ukm_page_load_metrics_observer.cc b/chrome/browser/page_load_metrics/observers/core/ukm_page_load_metrics_observer.cc
index 7ea8540..e3ae83d 100644
--- a/chrome/browser/page_load_metrics/observers/core/ukm_page_load_metrics_observer.cc
+++ b/chrome/browser/page_load_metrics/observers/core/ukm_page_load_metrics_observer.cc
@@ -1337,10 +1337,6 @@
   if (smoothness_data.worst_smoothness_after5sec >= 0)
     builder.SetWorstCaseAfter5Sec(smoothness_data.worst_smoothness_after5sec);
   builder.Record(ukm::UkmRecorder::Get());
-
-  base::UmaHistogramPercentage(
-      "Graphics.Smoothness.PerSession.MaxPercentDroppedFrames_1sWindow",
-      smoothness_data.worst_smoothness);
 }
 
 void UkmPageLoadMetricsObserver::RecordPageEndMetrics(
diff --git a/chrome/browser/profiles/chrome_browser_main_extra_parts_profiles.cc b/chrome/browser/profiles/chrome_browser_main_extra_parts_profiles.cc
index d42328f..69db713 100644
--- a/chrome/browser/profiles/chrome_browser_main_extra_parts_profiles.cc
+++ b/chrome/browser/profiles/chrome_browser_main_extra_parts_profiles.cc
@@ -168,6 +168,7 @@
 #include "chrome/browser/translate/translate_ranker_factory.h"
 #include "chrome/browser/ui/cookie_controls/cookie_controls_service_factory.h"
 #include "chrome/browser/ui/find_bar/find_bar_state_factory.h"
+#include "chrome/browser/ui/media_router/cast_notification_controller_lacros_factory.h"
 #include "chrome/browser/ui/prefs/prefs_tab_helper.h"
 #include "chrome/browser/ui/tabs/pinned_tab_service_factory.h"
 #include "chrome/browser/ui/toolbar/toolbar_actions_model_factory.h"
@@ -744,6 +745,9 @@
   if (base::FeatureList::IsEnabled(media::kUseMediaHistoryStore)) {
     media_history::MediaHistoryKeyedServiceFactory::GetInstance();
   }
+#if BUILDFLAG(IS_CHROMEOS_LACROS)
+  media_router::CastNotificationControllerLacrosFactory::GetInstance();
+#endif
   media_router::ChromeLocalPresentationManagerFactory::GetInstance();
   media_router::ChromeMediaRouterFactory::GetInstance();
 #if !BUILDFLAG(IS_ANDROID)
diff --git a/chrome/browser/profiles/profile_keyed_service_browsertest.cc b/chrome/browser/profiles/profile_keyed_service_browsertest.cc
index aac06c0e..753bb52 100644
--- a/chrome/browser/profiles/profile_keyed_service_browsertest.cc
+++ b/chrome/browser/profiles/profile_keyed_service_browsertest.cc
@@ -340,6 +340,9 @@
     "BookmarksApiWatcher",
     "BrailleDisplayPrivateAPI",
     "BrowsingTopicsService",
+#if BUILDFLAG(IS_CHROMEOS_LACROS)
+    "CastNotificationControllerLacros",
+#endif  // BUIDLFLAG(IS_CHROMEOS_LACROS)
 #if BUILDFLAG(ENABLE_SUPERVISED_USERS)
     "ChildAccountService",
 #endif  // BUILDFLAG(ENABLE_SUPERVISED_USERS)
diff --git a/chrome/browser/renderer_host/chrome_render_widget_host_view_mac_history_swiper_unit_test.mm b/chrome/browser/renderer_host/chrome_render_widget_host_view_mac_history_swiper_unit_test.mm
index bde819ba..12449ba 100644
--- a/chrome/browser/renderer_host/chrome_render_widget_host_view_mac_history_swiper_unit_test.mm
+++ b/chrome/browser/renderer_host/chrome_render_widget_host_view_mac_history_swiper_unit_test.mm
@@ -4,7 +4,6 @@
 
 #import "chrome/browser/renderer_host/chrome_render_widget_host_view_mac_history_swiper.h"
 
-#include "base/mac/scoped_nsobject.h"
 #import "chrome/browser/ui/cocoa/test/cocoa_test_helper.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "third_party/blink/public/common/input/web_input_event.h"
@@ -13,6 +12,10 @@
 #import "third_party/ocmock/ocmock_extensions.h"
 #include "ui/events/blink/did_overscroll_params.h"
 
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
 @interface HistorySwiper (MacHistorySwiperTest)
 - (BOOL)browserCanNavigateInDirection:
         (history_swiper::NavigationDirection)forward
@@ -40,8 +43,8 @@
       got_backwards_hint_ = true;
     }] backwardsSwipeNavigationLikely];
 
-    base::scoped_nsobject<HistorySwiper> historySwiper(
-        [[HistorySwiper alloc] initWithDelegate:mockDelegate]);
+    HistorySwiper* historySwiper =
+        [[HistorySwiper alloc] initWithDelegate:mockDelegate];
     id mockHistorySwiper = [OCMockObject partialMockForObject:historySwiper];
     [[[mockHistorySwiper stub] andReturnBool:YES]
         browserCanNavigateInDirection:history_swiper::kForwards
@@ -76,7 +79,7 @@
         magic_mouse_history_swipe_ = true;
     }] initiateMagicMouseHistorySwipe:NO event:[OCMArg any]];
 
-    historySwiper_ = [mockHistorySwiper retain];
+    historySwiper_ = mockHistorySwiper;
 
     begin_count_ = 0;
     end_count_ = 0;
@@ -86,12 +89,6 @@
     got_backwards_hint_ = false;
   }
 
-  void TearDown() override {
-    [view_ release];
-    [historySwiper_ release];
-    CocoaTest::TearDown();
-  }
-
   // These methods send all 3 types of events: gesture, scroll, and touch.
   void startGestureInMiddle();
   void moveGestureInMiddle();
@@ -105,8 +102,8 @@
   void sendBeginGestureEventInMiddle();
   void sendEndGestureEventAtPoint(NSPoint point);
 
-  HistorySwiper* historySwiper_;
-  NSView* view_;
+  HistorySwiper* __strong historySwiper_;
+  NSView* __strong view_;
   int begin_count_;
   int end_count_;
   bool navigated_right_;
diff --git a/chrome/browser/resources/browsing_topics/browsing_topics_internals.html b/chrome/browser/resources/browsing_topics/browsing_topics_internals.html
index 19f2683..78acc2b 100644
--- a/chrome/browser/resources/browsing_topics/browsing_topics_internals.html
+++ b/chrome/browser/resources/browsing_topics/browsing_topics_internals.html
@@ -71,16 +71,21 @@
       <div id="privacy-sandbox-settings3-enabled-div">PrivacySandboxSettings3: </div>
       <div id="override-privacy-sandbox-settings-local-testing-enabled-div">OverridePrivacySandboxSettingsLocalTesting: </div>
       <div id="browsing-topics-bypass-ip-is-publicly-routable-check-enabled-div">BrowsingTopicsBypassIPIsPubliclyRoutableCheck: </div>
+      <div id="browsing-topics-xhr-enabled-div">BrowsingTopicsXHR: </div>
+      <div id="browsing-topics-document-api-enabled-div">BrowsingTopicsDocumentAPI: </div>
+      <div id="config-version-div">Configuration version: </div>
+      <div id="browsing-topics-parameters-enabled-div">BrowsingTopicsParameters: </div>
       <div id="number-of-epochs-to-expose-div">BrowsingTopicsParameters:number_of_epochs_to_expose: </div>
       <div id="time-period-per-epoch-div">BrowsingTopicsParameters:time_period_per_epoch: </div>
       <div id="number-of-top-topics-per-epoch-div">BrowsingTopicsParameters:number_of_top_topics_per_epoch: </div>
       <div id="use-random-topic-probability-percent-div">BrowsingTopicsParameters:use_random_topic_probability_percent: </div>
+      <div id="max-epoch-introduction-delay-div">BrowsingTopicsParameters:max_epoch_introduction_delay: </div>
       <div id="number-of-epochs-of-observation-data-to-use-for-filtering-div">BrowsingTopicsParameters:number_of_epochs_of_observation_data_to_use_for_filtering: </div>
       <div id="max-number-of-api-usage-context-domains-to-keep-per-topic-div">BrowsingTopicsParameters:max_number_of_api_usage_context_domains_to_keep_per_topic: </div>
       <div id="max-number-of-api-usage-context-entries-to-load-per-epoch-div">BrowsingTopicsParameters:max_number_of_api_usage_context_entries_to_load_per_epoch: </div>
       <div id="max-number-of-api-usage-context-domains-to-store-per-page-load-div">BrowsingTopicsParameters:max_number_of_api_usage_context_domains_to_store_per_page_load: </div>
-      <div id="config-version-div">BrowsingTopicsParameters:config_version: </div>
       <div id="taxonomy-version-div">BrowsingTopicsParameters:taxonomy_version: </div>
+      <div id="disabled-topics-list-div">BrowsingTopicsParameters:disabled_topics_list: </div>
     </div>
   </div>
   <div id="consent-info-slot" slot="panel" class="panel">
diff --git a/chrome/browser/resources/browsing_topics/browsing_topics_internals.ts b/chrome/browser/resources/browsing_topics/browsing_topics_internals.ts
index bc2f0e6a..5b74b11c 100644
--- a/chrome/browser/resources/browsing_topics/browsing_topics_internals.ts
+++ b/chrome/browser/resources/browsing_topics/browsing_topics_internals.ts
@@ -128,7 +128,10 @@
    'privacy-sandbox-ads-apis-override-enabled-div',
    'privacy-sandbox-settings3-enabled-div',
    'override-privacy-sandbox-settings-local-testing-enabled-div',
-   'browsing-topics-bypass-ip-is-publicly-routable-check-enabled-div']
+   'browsing-topics-bypass-ip-is-publicly-routable-check-enabled-div',
+   'browsing-topics-xhr-enabled-div',
+   'browsing-topics-document-api-enabled-div',
+   'browsing-topics-parameters-enabled-div']
       .forEach(id => {
         const div = document.querySelector<HTMLElement>(`#${id}`);
         assert(div);
@@ -137,13 +140,14 @@
       });
 
   // Number fields
-  ['number-of-epochs-to-expose-div', 'number-of-top-topics-per-epoch-div',
+  ['config-version-div', 'number-of-epochs-to-expose-div',
+   'number-of-top-topics-per-epoch-div',
    'use-random-topic-probability-percent-div',
    'number-of-epochs-of-observation-data-to-use-for-filtering-div',
    'max-number-of-api-usage-context-domains-to-keep-per-topic-div',
    'max-number-of-api-usage-context-entries-to-load-per-epoch-div',
    'max-number-of-api-usage-context-domains-to-store-per-page-load-div',
-   'config-version-div', 'taxonomy-version-div']
+   'taxonomy-version-div', 'disabled-topics-list-div']
       .forEach(id => {
         const div = document.querySelector<HTMLElement>(`#${id}`);
         assert(div);
@@ -152,13 +156,14 @@
       });
 
   // Time duration fields
-  ['time-period-per-epoch-div'].forEach(id => {
-    const div = document.querySelector<HTMLElement>(`#${id}`);
-    assert(div);
-    div.textContent! += formatTimeDuration(
-        (config[fieldNameFromId(id) as keyof typeof config] as TimeDelta)
-            .microseconds);
-  });
+  ['time-period-per-epoch-div', 'max-epoch-introduction-delay-div'].forEach(
+      id => {
+        const div = document.querySelector<HTMLElement>(`#${id}`);
+        assert(div);
+        div.textContent! += formatTimeDuration(
+            (config[fieldNameFromId(id) as keyof typeof config] as TimeDelta)
+                .microseconds);
+      });
 }
 
 async function asyncGetBrowsingTopicsState(calculateNow: boolean) {
diff --git a/chrome/browser/resources/location_internals/BUILD.gn b/chrome/browser/resources/location_internals/BUILD.gn
index 4363668..a853d6e1 100644
--- a/chrome/browser/resources/location_internals/BUILD.gn
+++ b/chrome/browser/resources/location_internals/BUILD.gn
@@ -13,7 +13,11 @@
 
   non_web_component_files = [ "location_internals.ts" ]
 
+  web_component_files = [ "diagnose_info_table.ts" ]
+
   ts_deps = [ "//ui/webui/resources/js:build_ts" ]
 
   ts_composite = true
+
+  html_to_wrapper_template = "native"
 }
diff --git a/chrome/browser/resources/location_internals/diagnose_info_table.html b/chrome/browser/resources/location_internals/diagnose_info_table.html
new file mode 100644
index 0000000..77de623
--- /dev/null
+++ b/chrome/browser/resources/location_internals/diagnose_info_table.html
@@ -0,0 +1,30 @@
+<!--
+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.
+-->
+
+<style>
+  table {
+    border-collapse: collapse;
+    margin-top: 20px;
+  }
+
+  table caption {
+    font-size: 18px;
+    font-weight: bold;
+    text-align: start;
+  }
+
+  td,
+  th {
+    border: 1px solid #ddd;
+    padding: 8px;
+    text-align: start;
+  }
+</style>
+<table>
+  <caption></caption>
+  <thead></thead>
+  <tbody></tbody>
+</table>
diff --git a/chrome/browser/resources/location_internals/diagnose_info_table.ts b/chrome/browser/resources/location_internals/diagnose_info_table.ts
new file mode 100644
index 0000000..f3c811e
--- /dev/null
+++ b/chrome/browser/resources/location_internals/diagnose_info_table.ts
@@ -0,0 +1,62 @@
+// 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 {assert} from '//resources/js/assert_ts.js';
+import {CustomElement} from '//resources/js/custom_element.js';
+
+import {getTemplate} from './diagnose_info_table.html.js';
+
+export class DiagnoseInfoTableElement extends CustomElement {
+  static get is() {
+    return 'diagnose-info-table';
+  }
+
+  static override get template() {
+    return getTemplate();
+  }
+
+  private tableCaption_: HTMLElement;
+  private tableHead_: HTMLElement;
+  private tableBody_: HTMLElement;
+
+  constructor() {
+    super();
+    this.tableHead_ = this.getRequiredElement<HTMLElement>('thead');
+    this.tableBody_ = this.getRequiredElement<HTMLElement>('tbody');
+    this.tableCaption_ = this.getRequiredElement<HTMLElement>('caption');
+  }
+
+  createTableData(input: Array<Record<string, string>>) {
+    assert(input.length > 0);
+    const tableHeadFirstRow = document.createElement('tr');
+    this.tableHead_.appendChild(tableHeadFirstRow);
+    for (let i: number = 0; i < input.length; i++) {
+      const object = input[i];
+      const tableBodyRow = document.createElement('tr');
+      for (const name in object) {
+        if (i === 0) {
+          const nameCell = document.createElement('th');
+          nameCell.textContent = name;
+          tableHeadFirstRow.appendChild(nameCell);
+        }
+        const valueCell = document.createElement('td');
+        valueCell.textContent = object[name]!;
+        tableBodyRow.appendChild(valueCell);
+      }
+      this.tableBody_.appendChild(tableBodyRow);
+    }
+  }
+
+  updateCaption(name: string) {
+    this.tableCaption_.textContent = name;
+  }
+}
+
+declare global {
+  interface HTMLElementTagNameMap {
+    'diagnose-info-table': DiagnoseInfoTableElement;
+  }
+}
+
+customElements.define(DiagnoseInfoTableElement.is, DiagnoseInfoTableElement);
diff --git a/chrome/browser/resources/location_internals/location_internals.css b/chrome/browser/resources/location_internals/location_internals.css
index fd1fefa..75c0b9f6 100644
--- a/chrome/browser/resources/location_internals/location_internals.css
+++ b/chrome/browser/resources/location_internals/location_internals.css
@@ -4,26 +4,8 @@
 
 #container {
   display: flex;
+  flex-wrap: wrap;
   justify-content: space-around;
   padding-inline-end: 20px;
   padding-inline-start: 20px;
 }
-
-table {
-  border-collapse: collapse;
-  font-family: arial, sans-serif;
-  margin-top: 20px;
-}
-
-table caption {
-  font-size: 18px;
-  font-weight: bold;
-  text-align: start;
-}
-
-td,
-th {
-  border: 1px solid #ddd;
-  padding: 8px;
-  text-align: start;
-}
diff --git a/chrome/browser/resources/location_internals/location_internals.html b/chrome/browser/resources/location_internals/location_internals.html
index 1b2086fc..e2004b0 100644
--- a/chrome/browser/resources/location_internals/location_internals.html
+++ b/chrome/browser/resources/location_internals/location_internals.html
@@ -1,48 +1,22 @@
-<!doctype html>
+<!DOCTYPE html>
+<!--
+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.
+-->
 <html dir="$i18n{textdirection}" lang="$i18n{language}">
-
-<head>
-  <meta charset="utf-8">
-  <title>Location Internals</title>
-  <link rel="stylesheet" href="chrome://resources/css/text_defaults.css">
-  <link rel="stylesheet" href="location_internals.css">
-</head>
-
-<body>
-  <h2 class="header">Location Internals</h2>
-  <header class="page-header">
-    <button id="watch-btn">Start Watching Position</button>
-  </header>
-  <div id="container">
-
-    <table id="watch-position">
-      <caption>WatchPosition</caption>
-      <thead>
-        <tr>
-          <th>Timestamp</th>
-          <th>Position</th>
-          <th>Accuracy</th>
-          <th>Altitude</th>
-          <th>AltitudeAccuracy</th>
-          <th>Heading</th>
-          <th>Speed</th>
-        </tr>
-      </thead>
-      <tbody>
-        <tr>
-          <td id="watch-position-timestamp"></td>
-          <td id="watch-position-position"></td>
-          <td id="watch-position-accuracy"></td>
-          <td id="watch-position-altitude"></td>
-          <td id="watch-position-altitude-accuracy"></td>
-          <td id="watch-position-heading"></td>
-          <td id="watch-position-speed"></td>
-        </tr>
-      </tbody>
-    </table>
-
-  </div>
-  <script type="module" src="location_internals.js"></script>
-</body>
-
+  <head>
+    <meta charset="utf-8">
+    <title>Location Internals</title>
+    <link rel="stylesheet" href="chrome://resources/css/text_defaults.css">
+    <link rel="stylesheet" href="location_internals.css">
+  </head>
+  <body>
+    <h2 class="header">Location Internals</h2>
+    <header class="page-header">
+      <button id="watch-btn">Start Watching Position</button>
+    </header>
+    <div id="container"></div>
+    <script type="module" src="location_internals.js"></script>
+  </body>
 </html>
diff --git a/chrome/browser/resources/location_internals/location_internals.ts b/chrome/browser/resources/location_internals/location_internals.ts
index 368aa4c..1dad970 100644
--- a/chrome/browser/resources/location_internals/location_internals.ts
+++ b/chrome/browser/resources/location_internals/location_internals.ts
@@ -2,16 +2,16 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-import {assert} from 'chrome://resources/js/assert_ts.js';
-import {getRequiredElement} from 'chrome://resources/js/util_ts.js';
+import './diagnose_info_table.js';
 
-let watchButton: HTMLElement;
-let watchTable: HTMLElement;
+import {$, getRequiredElement} from 'chrome://resources/js/util_ts.js';
+
+const WATCH_TABLE_ID = 'watch-position-table';
+
 let watchId: number = -1;
 
 document.addEventListener('DOMContentLoaded', () => {
-  watchButton = getRequiredElement<HTMLElement>('watch-btn');
-  watchTable = getRequiredElement<HTMLElement>('watch-position');
+  const watchButton = getRequiredElement<HTMLElement>('watch-btn');
   watchButton.addEventListener('click', () => {
     if (watchId === -1) {
       watchId = navigator.geolocation.watchPosition(logSuccess, logError, {
@@ -29,51 +29,37 @@
 });
 
 function logSuccess(position: GeolocationPosition) {
-  assert(watchTable);
-  const timeCell = getRequiredElement<HTMLElement>('watch-position-timestamp');
-  const positionCell =
-      getRequiredElement<HTMLElement>('watch-position-position');
+  const data: Record<string, string> = {};
+  data['timestamp'] = new Date(position.timestamp).toLocaleString();
 
-  timeCell.textContent = new Date(position.timestamp).toLocaleString();
-  positionCell.textContent =
-      `${position.coords.latitude} ° , ${position.coords.longitude} ° `;
-
-  if (position.coords.accuracy) {
-    const accuracyCell =
-        getRequiredElement<HTMLElement>('watch-position-accuracy');
-    accuracyCell.textContent = position.coords.accuracy.toString();
+  for (const key in position.coords) {
+    const value = position.coords[key as keyof GeolocationCoordinates];
+    if (typeof value === 'number' || typeof value === 'string') {
+      data[key] = value.toString();
+    }
   }
-
-  if (position.coords.altitude) {
-    const altitudeCell =
-        getRequiredElement<HTMLElement>('watch-position-altitude');
-    altitudeCell.textContent = position.coords.altitude.toString();
-  }
-
-  if (position.coords.altitudeAccuracy) {
-    const altitudeAccuracyCell =
-        getRequiredElement<HTMLElement>('watch-position-altitude-accuracy');
-    altitudeAccuracyCell.textContent =
-        position.coords.altitudeAccuracy.toString();
-  }
-
-  if (position.coords.heading) {
-    const headingCell =
-        getRequiredElement<HTMLElement>('watch-position-heading');
-    headingCell.textContent = position.coords.heading.toString();
-  }
-
-  if (position.coords.speed) {
-    const speedCell = getRequiredElement<HTMLElement>('watch-position-speed');
-    speedCell.textContent = position.coords.speed.toString();
-  }
+  updateTable(WATCH_TABLE_ID, [data]);
 }
 
 function logError(error: GeolocationPositionError) {
-  assert(watchTable);
-  const timeCell = getRequiredElement<HTMLElement>('watch-position-timestamp');
-  const positionCell =
-      getRequiredElement<HTMLElement>('watch-position-position');
-  timeCell.textContent = new Date().toLocaleString();
-  positionCell.textContent = `${error.message}, code: ${error.code}`;
+  const data: Record<string, string> = {};
+  data['timestamp'] = new Date().toLocaleString();
+  data['fail reason'] = `${error.message}, code: ${error.code}`;
+  updateTable(WATCH_TABLE_ID, [data]);
+}
+
+function updateTable(name: string, data: Array<Record<string, string>>) {
+  removeTable(name);
+  const newTableElement = document.createElement('diagnose-info-table');
+  newTableElement.id = name;
+  newTableElement.createTableData(data);
+  newTableElement.updateCaption(name);
+  getRequiredElement<HTMLElement>('container').appendChild(newTableElement);
+}
+
+function removeTable(name: string) {
+  const oldTableElement = $(name);
+  if (oldTableElement) {
+    oldTableElement.remove();
+  }
 }
diff --git a/chrome/browser/resources/pdf/open_pdf_params_parser.ts b/chrome/browser/resources/pdf/open_pdf_params_parser.ts
index d4f292b..2526669d 100644
--- a/chrome/browser/resources/pdf/open_pdf_params_parser.ts
+++ b/chrome/browser/resources/pdf/open_pdf_params_parser.ts
@@ -38,6 +38,7 @@
 export class OpenPdfParamsParser {
   private getNamedDestinationCallback_: GetNamedDestinationCallback;
   private getPageBoundingBoxCallback_: GetPageBoundingBoxCallback;
+  private pageCount_?: number;
   private viewportDimensions_?: Size;
 
   /**
@@ -107,11 +108,17 @@
    * the specified fitting type mode and position.
    * @param paramValue Params to parse.
    * @param pageNumber Page number for bounding box, if there is a fit bounding
-   *     box param.
+   *     box param. `pageNumber` is 1-indexed and must be bounded by 1 and the
+   *     number of pages in the PDF, inclusive.
    * @return Map with view parameters (view and viewPosition).
    */
   private async parseViewParam_(paramValue: string, pageNumber: number):
       Promise<OpenPdfParams> {
+    assert(pageNumber > 0);
+    if (this.pageCount_) {
+      assert(pageNumber <= this.pageCount_);
+    }
+
     const viewModeComponents = paramValue.toLowerCase().split(',');
     if (viewModeComponents.length === 0) {
       return {};
@@ -121,7 +128,7 @@
     const viewMode = viewModeComponents[0];
     let acceptsPositionParam = false;
 
-    // Note that pageNumber is 1-indexed, but PDF Viewer is 0-indexed.
+    // Note that `pageNumber` is 1-indexed, but PDF Viewer is 0-indexed.
     switch (viewMode) {
       case ViewMode.FIT:
         params['view'] = FittingType.FIT_TO_PAGE;
@@ -135,21 +142,27 @@
         acceptsPositionParam = true;
         break;
       case ViewMode.FIT_B:
-        params['view'] = FittingType.FIT_TO_BOUNDING_BOX;
-        params['boundingBox'] =
-            await this.getPageBoundingBoxCallback_(pageNumber - 1);
+        if (this.pageCount_) {
+          params['view'] = FittingType.FIT_TO_BOUNDING_BOX;
+          params['boundingBox'] =
+              await this.getPageBoundingBoxCallback_(pageNumber - 1);
+        }
         break;
       case ViewMode.FIT_BH:
-        params['view'] = FittingType.FIT_TO_BOUNDING_BOX_WIDTH;
-        params['boundingBox'] =
-            await this.getPageBoundingBoxCallback_(pageNumber - 1);
-        acceptsPositionParam = true;
+        if (this.pageCount_) {
+          params['view'] = FittingType.FIT_TO_BOUNDING_BOX_WIDTH;
+          params['boundingBox'] =
+              await this.getPageBoundingBoxCallback_(pageNumber - 1);
+          acceptsPositionParam = true;
+        }
         break;
       case ViewMode.FIT_BV:
-        params['view'] = FittingType.FIT_TO_BOUNDING_BOX_HEIGHT;
-        params['boundingBox'] =
-            await this.getPageBoundingBoxCallback_(pageNumber - 1);
-        acceptsPositionParam = true;
+        if (this.pageCount_) {
+          params['view'] = FittingType.FIT_TO_BOUNDING_BOX_HEIGHT;
+          params['boundingBox'] =
+              await this.getPageBoundingBoxCallback_(pageNumber - 1);
+          acceptsPositionParam = true;
+        }
         break;
       case ViewMode.FIT_R:
       case ViewMode.XYZ:
@@ -248,6 +261,11 @@
     return params;
   }
 
+  /** Store the number of pages. */
+  setPageCount(pageCount: number) {
+    this.pageCount_ = pageCount;
+  }
+
   /** Store current viewport's dimensions. */
   setViewportDimensions(dimensions: Size) {
     this.viewportDimensions_ = dimensions;
@@ -298,23 +316,26 @@
 
     const urlParams = this.parseUrlParams_(url);
 
-    let pageNumber;
+    // `pageNumber` is 1-based.
+    let pageNumber = 1;
     if (urlParams.has('page')) {
-      // |pageNumber| is 1-based, but goToPage() take a zero-based page index.
       pageNumber = parseInt(urlParams.get('page')!, 10);
-      if (!Number.isNaN(pageNumber) && pageNumber > 0) {
+      if (!Number.isNaN(pageNumber) && this.pageCount_) {
+        // If necessary, clip `pageNumber` to stay within bounds.
+        if (pageNumber < 1) {
+          pageNumber = 1;
+        } else if (pageNumber > this.pageCount_) {
+          pageNumber = this.pageCount_;
+        }
+        // goToPage() takes a zero-based page index.
         params['page'] = pageNumber - 1;
       }
     }
 
-    if (!pageNumber || pageNumber < 1) {
-      pageNumber = 1;
-    }
-
     if (urlParams.has('view')) {
       Object.assign(
           params,
-          await this.parseViewParam_(urlParams.get('view')!, pageNumber!));
+          await this.parseViewParam_(urlParams.get('view')!, pageNumber));
     }
 
     if (urlParams.has('zoom')) {
diff --git a/chrome/browser/resources/pdf/pdf_viewer_base.ts b/chrome/browser/resources/pdf/pdf_viewer_base.ts
index 49abb225..80a290e 100644
--- a/chrome/browser/resources/pdf/pdf_viewer_base.ts
+++ b/chrome/browser/resources/pdf/pdf_viewer_base.ts
@@ -359,6 +359,7 @@
     this.documentDimensions = documentDimensions;
     this.isUserInitiatedEvent = false;
     this.viewport_!.setDocumentDimensions(this.documentDimensions);
+    this.paramsParser!.setPageCount(documentDimensions.pageDimensions.length);
     this.paramsParser!.setViewportDimensions(this.viewport_!.size);
     this.isUserInitiatedEvent = true;
   }
diff --git a/chrome/browser/resources/side_panel/bookmarks/power_bookmarks_edit_dialog.html b/chrome/browser/resources/side_panel/bookmarks/power_bookmarks_edit_dialog.html
index dbe6cc1..5b232922 100644
--- a/chrome/browser/resources/side_panel/bookmarks/power_bookmarks_edit_dialog.html
+++ b/chrome/browser/resources/side_panel/bookmarks/power_bookmarks_edit_dialog.html
@@ -28,6 +28,10 @@
     background-color: var(--cr-hover-background-color);
   }
 
+  :host-context([chrome-refresh-2023]) .folder-row:focus-visible {
+    outline-color: var(--cr-focus-outline-color);
+  }
+
   .folder-row[selected] {
     background-color: var(--google-blue-50);
   }
@@ -50,6 +54,24 @@
     }
   }
 
+  :host-context([chrome-refresh-2023]) .folder-row[selected] {
+    background-color: var(
+        --color-side-panel-bookmarks-selected-folder-background);
+  }
+
+  :host-context([chrome-refresh-2023]) .folder-row[selected] > .cr-icon {
+    background-color: var(--color-side-panel-bookmarks-selected-folder-icon);
+  }
+
+  :host-context([chrome-refresh-2023]) .folder-row[selected] > .folder-title {
+    color: var(--color-side-panel-bookmarks-selected-folder-foreground);
+  }
+
+  :host-context([chrome-refresh-2023]) .folder-row[selected] > .subpage-arrow {
+    --cr-icon-button-fill-color: var(
+        --color-side-panel-bookmarks-selected-folder-icon);
+  }
+
   .folder-selector {
     border: 1px solid var(--scrollable-border-color);
     border-radius: 2px;
@@ -58,6 +80,10 @@
     overflow: auto;
   }
 
+  :host-context([chrome-refresh-2023]) .folder-selector {
+    border-color: var(--color-side-panel-dialog-divider);
+  }
+
   .folder-title {
     width: 100%;
   }
@@ -71,6 +97,11 @@
     white-space: nowrap;
   }
 
+  :host-context([chrome-refresh-2023]) .input-label {
+    font-size: 13px;
+    color: var(--cr-primary-text-color);
+  }
+
   .input-row {
     align-items: baseline;
     display: grid;
@@ -91,6 +122,10 @@
   .name-input {
     --cr-input-error-display: none;
   }
+
+  :host-context([chrome-refresh-2023]) .cr-icon {
+    --cr-icon-color: var(--cr-secondary-text-color);
+  }
 </style>
 
 <cr-dialog id="dialog">
diff --git a/chrome/browser/resources/side_panel/reading_list/app.html b/chrome/browser/resources/side_panel/reading_list/app.html
index f2a7e1e..9f7e2f4 100644
--- a/chrome/browser/resources/side_panel/reading_list/app.html
+++ b/chrome/browser/resources/side_panel/reading_list/app.html
@@ -5,28 +5,6 @@
     height: 100vh;
   }
 
-  #header {
-    align-items: center;
-    color: var(--cr-primary-text-color);
-    flex-grow: 1;
-    font-size: 15px;
-    line-height: var(--mwb-item-height);
-    margin: 0;
-    padding-inline-start: var(--mwb-list-item-horizontal-margin);
-  }
-
-  cr-icon-button {
-    margin-inline-end: 4px;
-    margin-top: 4px;
-    --cr-icon-button-fill-color: var(--mwb-icon-button-fill-color);
-  }
-
-  @media (prefers-color-scheme: dark) {
-    cr-icon-button {
-      --cr-icon-button-ripple-opacity: 0.15;
-    }
-  }
-
   /* Transition required to ensure focus highlight after button press.
    * See crbug/1358900.
    */
@@ -54,14 +32,6 @@
     transition: background-color 300ms cubic-bezier(0.4, 0, 0.2, 1);
   }
 
-  .mwb-list-item:focus-within {
-    background-color: var(--mwb-list-item-hover-background-color);
-  }
-
-  .mwb-list-item:active {
-    background-color: var(--mwb-list-item-selected-background-color);
-  }
-
   sp-heading {
     margin: 8px var(--mwb-list-item-horizontal-margin);
   }
diff --git a/chrome/browser/resources/side_panel/shared/sp_shared_style.css b/chrome/browser/resources/side_panel/shared/sp_shared_style.css
index 64f21d3..520f13c 100644
--- a/chrome/browser/resources/side_panel/shared/sp_shared_style.css
+++ b/chrome/browser/resources/side_panel/shared/sp_shared_style.css
@@ -125,3 +125,19 @@
   margin: 0;
   width: 100%;
 }
+
+:host-context([chrome-refresh-2023]) cr-dialog {
+  --cr-dialog-background-color: var(--color-side-panel-dialog-background);
+  --cr-primary-text-color: var(--color-side-panel-dialog-primary-foreground);
+  --cr-secondary-text-color: var(
+      --color-side-panel-dialog-secondary-foreground);
+  --cr-dialog-title-font-size: 16px;
+  --cr-dialog-title-slot-padding-bottom: 8px;
+  font-weight: 500;
+}
+
+:host-context([chrome-refresh-2023]) cr-dialog::part(dialog) {
+  --scroll-border-color: var(--color-side-panel-dialog-divider);
+  border-radius: 12px;
+  box-shadow: var(--cr-elevation-3);
+}
diff --git a/chrome/browser/search/background/ntp_background_service.cc b/chrome/browser/search/background/ntp_background_service.cc
index 29c60f13..00506a9 100644
--- a/chrome/browser/search/background/ntp_background_service.cc
+++ b/chrome/browser/search/background/ntp_background_service.cc
@@ -4,6 +4,7 @@
 
 #include "chrome/browser/search/background/ntp_background_service.h"
 
+#include "base/barrier_callback.h"
 #include "base/command_line.h"
 #include "base/functional/bind.h"
 #include "base/observer_list.h"
@@ -17,6 +18,7 @@
 #include "content/public/browser/browser_context.h"
 #include "content/public/browser/browser_thread.h"
 #include "net/base/load_flags.h"
+#include "net/http/http_status_code.h"
 #include "services/network/public/cpp/resource_request.h"
 #include "services/network/public/cpp/shared_url_loader_factory.h"
 #include "services/network/public/cpp/simple_url_loader.h"
@@ -111,6 +113,15 @@
           data:
             "The Backdrop protocol buffer messages. No user data is included."
           destination: GOOGLE_OWNED_SERVICE
+          internal {
+            contacts {
+            email: "chrome-desktop-ntp@google.com"
+            }
+          }
+          user_data {
+            type: NONE
+          }
+          last_reviewed: "2023-06-13"
         }
         policy {
           cookies_allowed: NO
@@ -196,6 +207,8 @@
   if (collections_image_info_loader_ != nullptr)
     return;
 
+  pending_image_url_header_loaders_.clear();
+
   net::NetworkTrafficAnnotationTag traffic_annotation =
       net::DefineNetworkTrafficAnnotation("backdrop_collection_images_download",
                                           R"(
@@ -212,6 +225,15 @@
           data:
             "The Backdrop protocol buffer messages. No user data is included."
           destination: GOOGLE_OWNED_SERVICE
+          internal {
+            contacts {
+            email: "chrome-desktop-ntp@google.com"
+            }
+          }
+          user_data {
+            type: NONE
+          }
+          last_reviewed: "2023-06-13"
         }
         policy {
           cookies_allowed: NO
@@ -275,11 +297,112 @@
     return;
   }
 
-  for (int i = 0; i < images_response.images_size(); ++i) {
-    collection_images_.push_back(CollectionImage::CreateFromProto(
-        requested_collection_id_, images_response.images(i), image_options_));
+  if (base::FeatureList::IsEnabled(
+          ntp_features::kNtpBackgroundImageErrorDetection)) {
+    const auto image_error_detection_callback = base::BarrierCallback<bool>(
+        images_response.images_size(),
+        base::BindOnce(&NtpBackgroundService::CollectionImagesURLsVerified,
+                       base::Unretained(this)));
+    for (int i = 0; i < images_response.images_size(); ++i) {
+      VerifyCollectionImageURL(images_response.images(i),
+                               image_error_detection_callback);
+    }
+  } else {
+    for (int i = 0; i < images_response.images_size(); ++i) {
+      collection_images_.push_back(CollectionImage::CreateFromProto(
+          requested_collection_id_, images_response.images(i), image_options_));
+    }
+    NotifyObservers(FetchComplete::COLLECTION_IMAGE_INFO);
+  }
+}
+
+void NtpBackgroundService::VerifyCollectionImageURL(
+    const ntp::background::Image& image,
+    base::OnceCallback<void(bool)> callback) {
+  constexpr net::NetworkTrafficAnnotationTag traffic_annotation =
+      net::DefineNetworkTrafficAnnotation("backdrop_image_link_verification",
+                                          R"(
+        semantics {
+          sender: "Desktop NTP Background Selector"
+          description:
+            "The Chrome Desktop New Tab Page background selector displays a "
+            "rich set of wallpapers for users to choose from. Each wallpaper "
+            "belongs to a collection (e.g. Arts, Landscape etc.). "
+            "This fetches a wallpaper image's link to make sure its "
+            "resource is reachable."
+          trigger:
+            "Clicking a theme collection on the New Tab page."
+          data:
+            "The HTTP headers for each background image in the "
+            "theme collection."
+          destination: GOOGLE_OWNED_SERVICE
+          internal {
+            contacts {
+            email: "chrome-desktop-ntp@google.com"
+            }
+          }
+          user_data {
+            type: NONE
+          }
+          last_reviewed: "2023-06-13"
+        }
+        policy {
+          cookies_allowed: NO
+          setting:
+            "Users can control this feature by selecting a non-Google default "
+            "search engine in Chrome settings under 'Search Engine'."
+          chrome_policy {
+            DefaultSearchProviderEnabled {
+              policy_options {mode: MANDATORY}
+              DefaultSearchProviderEnabled: false
+            }
+          }
+        })");
+  auto resource_request = std::make_unique<network::ResourceRequest>();
+  resource_request->method = "GET";
+  resource_request->url = GURL(image.image_url());
+  resource_request->credentials_mode = network::mojom::CredentialsMode::kOmit;
+  std::unique_ptr<network::SimpleURLLoader> image_url_header_loader =
+      network::SimpleURLLoader::Create(std::move(resource_request),
+                                       traffic_annotation);
+  network::SimpleURLLoader* const image_url_header_loader_ptr =
+      image_url_header_loader.get();
+  image_url_header_loader_ptr->SetRetryOptions(
+      /*max_retries=*/3,
+      network::SimpleURLLoader::RetryMode::RETRY_ON_5XX |
+          network::SimpleURLLoader::RETRY_ON_NETWORK_CHANGE |
+          network::SimpleURLLoader::RETRY_ON_NAME_NOT_RESOLVED);
+  auto it = pending_image_url_header_loaders_.insert(
+      pending_image_url_header_loaders_.begin(),
+      std::move(image_url_header_loader));
+  image_url_header_loader_ptr->DownloadHeadersOnly(
+      url_loader_factory_.get(),
+      base::BindOnce(&NtpBackgroundService::OnImageURLHeadersFetchComplete,
+                     base::Unretained(this), std::move(it), image,
+                     std::move(callback)));
+}
+
+void NtpBackgroundService::OnImageURLHeadersFetchComplete(
+    ImageURLHeaderLoaderList::iterator it,
+    const ntp::background::Image& image,
+    base::OnceCallback<void(bool)> callback,
+    scoped_refptr<net::HttpResponseHeaders> headers) {
+  if (pending_image_url_header_loaders_.empty()) {
+    return;
   }
 
+  pending_image_url_header_loaders_.erase(it);
+  if (headers && headers->response_code() == net::HTTP_OK) {
+    collection_images_.push_back(CollectionImage::CreateFromProto(
+        requested_collection_id_, image, image_options_));
+    std::move(callback).Run(true);
+  } else {
+    std::move(callback).Run(false);
+  }
+}
+
+void NtpBackgroundService::CollectionImagesURLsVerified(
+    const std::vector<bool>& results) {
   NotifyObservers(FetchComplete::COLLECTION_IMAGE_INFO);
 }
 
@@ -307,6 +430,15 @@
             "The Backdrop protocol buffer messages. ApplicationLocale is "
             "included in the request."
           destination: GOOGLE_OWNED_SERVICE
+          internal {
+            contacts {
+            email: "chrome-desktop-ntp@google.com"
+            }
+          }
+          user_data {
+            type: NONE
+          }
+          last_reviewed: "2023-06-13"
         }
         policy {
           cookies_allowed: NO
diff --git a/chrome/browser/search/background/ntp_background_service.h b/chrome/browser/search/background/ntp_background_service.h
index d1b671e..59fa719 100644
--- a/chrome/browser/search/background/ntp_background_service.h
+++ b/chrome/browser/search/background/ntp_background_service.h
@@ -5,6 +5,7 @@
 #ifndef CHROME_BROWSER_SEARCH_BACKGROUND_NTP_BACKGROUND_SERVICE_H_
 #define CHROME_BROWSER_SEARCH_BACKGROUND_NTP_BACKGROUND_SERVICE_H_
 
+#include <list>
 #include <memory>
 #include <string>
 #include <vector>
@@ -16,6 +17,7 @@
 #include "components/keyed_service/core/keyed_service.h"
 #include "components/signin/public/identity_manager/access_token_info.h"
 #include "net/base/url_util.h"
+#include "net/http/http_response_headers.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
 #include "url/gurl.h"
 
@@ -128,6 +130,11 @@
   std::unique_ptr<network::SimpleURLLoader> collections_image_info_loader_;
   std::unique_ptr<network::SimpleURLLoader> next_image_loader_;
 
+  // Used to download the headers of an image in a collection.
+  using ImageURLHeaderLoaderList =
+      std::list<std::unique_ptr<network::SimpleURLLoader>>;
+  ImageURLHeaderLoaderList pending_image_url_header_loaders_;
+
   base::ObserverList<NtpBackgroundServiceObserver, true>::Unchecked observers_;
 
   // Callback that processes the response from the FetchCollectionInfo request,
@@ -147,6 +154,21 @@
   void OnNextImageInfoFetchComplete(
       const std::unique_ptr<std::string> response_body);
 
+  // Verifies that the resource at a collection image's URL can be reached.
+  void VerifyCollectionImageURL(const ntp::background::Image& image,
+                                base::OnceCallback<void(bool)> callback);
+  // Callback that processes the response from VerifyCollectionImageURL request,
+  // refreshing the contents of collection_images_ with images whose resources
+  // could be reached.
+  void OnImageURLHeadersFetchComplete(
+      ImageURLHeaderLoaderList::iterator it,
+      const ntp::background::Image& image,
+      base::OnceCallback<void(bool)> callback,
+      scoped_refptr<net::HttpResponseHeaders> headers);
+  // Callback that notifies observers that fetch of CollectionImages has
+  // completed.
+  void CollectionImagesURLsVerified(const std::vector<bool>& results);
+
   enum class FetchComplete {
     // Indicates that asynchronous fetch of CollectionInfo has completed.
     COLLECTION_INFO,
diff --git a/chrome/browser/search/background/ntp_background_service_unittest.cc b/chrome/browser/search/background/ntp_background_service_unittest.cc
index 7072496..fe1b31f3 100644
--- a/chrome/browser/search/background/ntp_background_service_unittest.cc
+++ b/chrome/browser/search/background/ntp_background_service_unittest.cc
@@ -10,9 +10,11 @@
 #include "base/command_line.h"
 #include "base/run_loop.h"
 #include "base/test/bind.h"
+#include "base/test/scoped_feature_list.h"
 #include "base/test/task_environment.h"
 #include "chrome/browser/browser_process.h"
 #include "chrome/browser/search/background/ntp_background_data.h"
+#include "components/search/ntp_features.h"
 #include "components/version_info/version_info.h"
 #include "content/public/test/browser_task_environment.h"
 #include "services/network/public/cpp/data_element.h"
@@ -26,21 +28,34 @@
 using testing::Eq;
 using testing::StartsWith;
 
-class NtpBackgroundServiceTest : public testing::Test {
+const char kTestImageUrl[] = "https://wallpapers.co/some_image";
+const char kTestActionUrl[] = "https://wallpapers.co/some_image/learn_more";
+
+class NtpBackgroundServiceTest : public testing::Test,
+                                 public ::testing::WithParamInterface<bool> {
  public:
   NtpBackgroundServiceTest()
       : task_environment_(content::BrowserTaskEnvironment::IO_MAINLOOP),
         test_shared_loader_factory_(
             base::MakeRefCounted<network::WeakWrapperSharedURLLoaderFactory>(
-                &test_url_loader_factory_)) {}
+                &test_url_loader_factory_)) {
+    feature_list_.InitWithFeatureState(
+        std::move(ntp_features::kNtpBackgroundImageErrorDetection),
+        BackgroundImageErrorDetectionEnabled());
+  }
 
   ~NtpBackgroundServiceTest() override {}
 
+  void SetUpResponseWithNetworkSuccess(const GURL& load_url,
+                                       const std::string& response = "") {
+    test_url_loader_factory_.AddResponse(load_url.spec(), response);
+  }
+
   void SetUpResponseWithData(const GURL& load_url,
                              const std::string& response) {
     test_url_loader_factory_.SetInterceptor(base::BindLambdaForTesting(
         [&](const network::ResourceRequest& request) {}));
-    test_url_loader_factory_.AddResponse(load_url.spec(), response);
+    SetUpResponseWithNetworkSuccess(load_url, response);
   }
 
   void SetUpResponseWithNetworkError(const GURL& load_url) {
@@ -61,6 +76,8 @@
     return &test_url_loader_factory_;
   }
 
+  bool BackgroundImageErrorDetectionEnabled() const { return GetParam(); }
+
  private:
   // Required to run tests from UI and threads.
   content::BrowserTaskEnvironment task_environment_;
@@ -69,9 +86,13 @@
   scoped_refptr<network::SharedURLLoaderFactory> test_shared_loader_factory_;
 
   std::unique_ptr<NtpBackgroundService> service_;
+
+  base::test::ScopedFeatureList feature_list_;
 };
 
-TEST_F(NtpBackgroundServiceTest, CorrectCollectionRequest) {
+INSTANTIATE_TEST_SUITE_P(All, NtpBackgroundServiceTest, ::testing::Bool());
+
+TEST_P(NtpBackgroundServiceTest, CorrectCollectionRequest) {
   g_browser_process->SetApplicationLocale("foo");
   service()->FetchCollectionInfo();
   base::RunLoop().RunUntilIdle();
@@ -95,7 +116,7 @@
             collection_request.filtering_label(2));
 }
 
-TEST_F(NtpBackgroundServiceTest, CollectionInfoNetworkError) {
+TEST_P(NtpBackgroundServiceTest, CollectionInfoNetworkError) {
   SetUpResponseWithNetworkError(service()->GetCollectionsLoadURLForTesting());
 
   ASSERT_TRUE(service()->collection_info().empty());
@@ -108,7 +129,7 @@
             ErrorType::NET_ERROR);
 }
 
-TEST_F(NtpBackgroundServiceTest, BadCollectionsResponse) {
+TEST_P(NtpBackgroundServiceTest, BadCollectionsResponse) {
   SetUpResponseWithData(service()->GetCollectionsLoadURLForTesting(),
                         "bad serialized GetCollectionsResponse");
 
@@ -122,11 +143,11 @@
             ErrorType::SERVICE_ERROR);
 }
 
-TEST_F(NtpBackgroundServiceTest, GoodCollectionsResponse) {
+TEST_P(NtpBackgroundServiceTest, GoodCollectionsResponse) {
   ntp::background::Collection collection;
   collection.set_collection_id("shapes");
   collection.set_collection_name("Shapes");
-  collection.add_preview()->set_image_url("https://wallpapers.co/some_image");
+  collection.add_preview()->set_image_url(kTestImageUrl);
   ntp::background::GetCollectionsResponse response;
   *response.add_collections() = collection;
   std::string response_string;
@@ -151,7 +172,7 @@
   EXPECT_EQ(service()->collection_error_info().error_type, ErrorType::NONE);
 }
 
-TEST_F(NtpBackgroundServiceTest, CollectionImagesNetworkError) {
+TEST_P(NtpBackgroundServiceTest, CollectionImagesNetworkError) {
   SetUpResponseWithNetworkError(service()->GetImagesURLForTesting());
 
   ASSERT_TRUE(service()->collection_images().empty());
@@ -164,7 +185,7 @@
             ErrorType::NET_ERROR);
 }
 
-TEST_F(NtpBackgroundServiceTest, BadCollectionImagesResponse) {
+TEST_P(NtpBackgroundServiceTest, BadCollectionImagesResponse) {
   SetUpResponseWithData(service()->GetImagesURLForTesting(),
                         "bad serialized GetImagesInCollectionResponse");
 
@@ -178,18 +199,62 @@
             ErrorType::SERVICE_ERROR);
 }
 
-TEST_F(NtpBackgroundServiceTest, GoodCollectionImagesResponse) {
+TEST_P(NtpBackgroundServiceTest, ImageInCollectionHasNetworkError) {
   ntp::background::Image image;
   image.set_asset_id(12345);
-  image.set_image_url("https://wallpapers.co/some_image");
+  image.set_image_url(kTestImageUrl);
   image.add_attribution()->set_text("attribution text");
-  image.set_action_url("https://wallpapers.co/some_image/learn_more");
+  image.set_action_url(kTestActionUrl);
   ntp::background::GetImagesInCollectionResponse response;
   *response.add_images() = image;
   std::string response_string;
   response.SerializeToString(&response_string);
 
   SetUpResponseWithData(service()->GetImagesURLForTesting(), response_string);
+  if (BackgroundImageErrorDetectionEnabled()) {
+    SetUpResponseWithNetworkError(GURL(image.image_url()));
+  }
+
+  ASSERT_TRUE(service()->collection_images().empty());
+
+  service()->FetchCollectionImageInfo("shapes");
+  base::RunLoop().RunUntilIdle();
+
+  CollectionImage collection_image;
+  collection_image.collection_id = "shapes";
+  collection_image.asset_id = image.asset_id();
+  collection_image.thumbnail_image_url =
+      GURL(image.image_url() + GetThumbnailImageOptionsForTesting());
+  collection_image.image_url =
+      GURL(image.image_url() + service()->GetImageOptionsForTesting());
+  collection_image.attribution.push_back(image.attribution(0).text());
+  collection_image.attribution_action_url = GURL(image.action_url());
+
+  if (BackgroundImageErrorDetectionEnabled()) {
+    EXPECT_TRUE(service()->collection_images().empty());
+  } else {
+    EXPECT_FALSE(service()->collection_images().empty());
+    EXPECT_THAT(service()->collection_images().at(0), Eq(collection_image));
+    EXPECT_EQ(service()->collection_images_error_info().error_type,
+              ErrorType::NONE);
+  }
+}
+
+TEST_P(NtpBackgroundServiceTest, GoodCollectionImagesResponse) {
+  ntp::background::Image image;
+  image.set_asset_id(12345);
+  image.set_image_url(kTestImageUrl);
+  image.add_attribution()->set_text("attribution text");
+  image.set_action_url(kTestActionUrl);
+  ntp::background::GetImagesInCollectionResponse response;
+  *response.add_images() = image;
+  std::string response_string;
+  response.SerializeToString(&response_string);
+
+  SetUpResponseWithData(service()->GetImagesURLForTesting(), response_string);
+  if (BackgroundImageErrorDetectionEnabled()) {
+    SetUpResponseWithNetworkSuccess(GURL(image.image_url()));
+  }
 
   ASSERT_TRUE(service()->collection_images().empty());
 
@@ -212,11 +277,11 @@
             ErrorType::NONE);
 }
 
-TEST_F(NtpBackgroundServiceTest, MultipleRequests) {
+TEST_P(NtpBackgroundServiceTest, MultipleRequests) {
   ntp::background::Collection collection;
   collection.set_collection_id("shapes");
   collection.set_collection_name("Shapes");
-  collection.add_preview()->set_image_url("https://wallpapers.co/some_image");
+  collection.add_preview()->set_image_url(kTestImageUrl);
   ntp::background::GetCollectionsResponse collection_response;
   *collection_response.add_collections() = collection;
   std::string collection_response_string;
@@ -224,7 +289,7 @@
 
   ntp::background::Image image;
   image.set_asset_id(12345);
-  image.set_image_url("https://wallpapers.co/some_image");
+  image.set_image_url(kTestImageUrl);
   image.add_attribution()->set_text("attribution text");
   ntp::background::GetImagesInCollectionResponse image_response;
   *image_response.add_images() = image;
@@ -235,6 +300,9 @@
                         collection_response_string);
   SetUpResponseWithData(service()->GetImagesURLForTesting(),
                         image_response_string);
+  if (BackgroundImageErrorDetectionEnabled()) {
+    SetUpResponseWithNetworkSuccess(GURL(image.image_url()));
+  }
 
   ASSERT_TRUE(service()->collection_info().empty());
   ASSERT_TRUE(service()->collection_images().empty());
@@ -266,7 +334,7 @@
   EXPECT_THAT(service()->collection_images().at(0), Eq(collection_image));
 }
 
-TEST_F(NtpBackgroundServiceTest, NextImageNetworkError) {
+TEST_P(NtpBackgroundServiceTest, NextImageNetworkError) {
   SetUpResponseWithNetworkError(service()->GetNextImageURLForTesting());
 
   service()->FetchNextCollectionImage("shapes", absl::nullopt);
@@ -276,7 +344,7 @@
               Eq(ErrorType::NET_ERROR));
 }
 
-TEST_F(NtpBackgroundServiceTest, BadNextImageResponse) {
+TEST_P(NtpBackgroundServiceTest, BadNextImageResponse) {
   SetUpResponseWithData(service()->GetNextImageURLForTesting(),
                         "bad serialized GetImageFromCollectionResponse");
 
@@ -287,12 +355,12 @@
               Eq(ErrorType::SERVICE_ERROR));
 }
 
-TEST_F(NtpBackgroundServiceTest, GoodNextImageResponse) {
+TEST_P(NtpBackgroundServiceTest, GoodNextImageResponse) {
   ntp::background::Image image;
   image.set_asset_id(12345);
-  image.set_image_url("https://wallpapers.co/some_image");
+  image.set_image_url(kTestImageUrl);
   image.add_attribution()->set_text("attribution text");
-  image.set_action_url("https://wallpapers.co/some_image/learn_more");
+  image.set_action_url(kTestActionUrl);
   ntp::background::GetImageFromCollectionResponse response;
   *response.mutable_image() = image;
   response.set_resume_token("resume1");
@@ -324,12 +392,12 @@
               Eq(ErrorType::NONE));
 }
 
-TEST_F(NtpBackgroundServiceTest, MultipleRequestsNextImage) {
+TEST_P(NtpBackgroundServiceTest, MultipleRequestsNextImage) {
   ntp::background::Image image;
   image.set_asset_id(12345);
-  image.set_image_url("https://wallpapers.co/some_image");
+  image.set_image_url(kTestImageUrl);
   image.add_attribution()->set_text("attribution text");
-  image.set_action_url("https://wallpapers.co/some_image/learn_more");
+  image.set_action_url(kTestActionUrl);
   ntp::background::GetImageFromCollectionResponse response;
   *response.mutable_image() = image;
   response.set_resume_token("resume1");
@@ -363,18 +431,21 @@
               Eq(ErrorType::NONE));
 }
 
-TEST_F(NtpBackgroundServiceTest, CheckValidAndInvalidBackdropUrls) {
+TEST_P(NtpBackgroundServiceTest, CheckValidAndInvalidBackdropUrls) {
   ntp::background::Image image;
   image.set_asset_id(12345);
-  image.set_image_url("https://wallpapers.co/some_image");
+  image.set_image_url(kTestImageUrl);
   image.add_attribution()->set_text("attribution text");
-  image.set_action_url("https://wallpapers.co/some_image/learn_more");
+  image.set_action_url(kTestActionUrl);
   ntp::background::GetImagesInCollectionResponse response;
   *response.add_images() = image;
   std::string response_string;
   response.SerializeToString(&response_string);
 
   SetUpResponseWithData(service()->GetImagesURLForTesting(), response_string);
+  if (BackgroundImageErrorDetectionEnabled()) {
+    SetUpResponseWithNetworkSuccess(GURL(image.image_url()));
+  }
 
   ASSERT_TRUE(service()->collection_images().empty());
 
@@ -390,7 +461,7 @@
       GURL("https://wallpapers.co/another_image")));
 }
 
-TEST_F(NtpBackgroundServiceTest, GetThumbnailUrl) {
+TEST_P(NtpBackgroundServiceTest, GetThumbnailUrl) {
   const GURL kInvalidUrl("foo");
   const GURL kValidUrl("https://www.foo.com");
   const GURL kValidThumbnailUrl("https://www.foo.com/thumbnail");
@@ -401,7 +472,7 @@
   EXPECT_EQ(GURL::EmptyGURL(), service()->GetThumbnailUrl(kInvalidUrl));
 }
 
-TEST_F(NtpBackgroundServiceTest, OverrideBaseUrl) {
+TEST_P(NtpBackgroundServiceTest, OverrideBaseUrl) {
   base::CommandLine::ForCurrentProcess()->AppendSwitchASCII(
       "collections-base-url", "https://foo.com");
   service()->FetchCollectionInfo();
diff --git a/chrome/browser/signin/signin_ui_delegate_impl_lacros.cc b/chrome/browser/signin/signin_ui_delegate_impl_lacros.cc
index 76fd34a..6e013b4 100644
--- a/chrome/browser/signin/signin_ui_delegate_impl_lacros.cc
+++ b/chrome/browser/signin/signin_ui_delegate_impl_lacros.cc
@@ -43,6 +43,9 @@
     case signin_metrics::AccessPoint::ACCESS_POINT_RECENT_TABS:
       return account_manager::AccountManagerFacade::AccountAdditionSource::
           kChromeSyncPromoAddAccount;
+    case signin_metrics::AccessPoint::ACCESS_POINT_MENU:
+      return account_manager::AccountManagerFacade::AccountAdditionSource::
+          kChromeMenuTurnOnSync;
     default:
       NOTREACHED() << "Add account is requested from an unknown access point "
                    << static_cast<int>(access_point);
@@ -73,6 +76,9 @@
     case signin_metrics::AccessPoint::ACCESS_POINT_WEB_SIGNIN:
       return account_manager::AccountManagerFacade::AccountAdditionSource::
           kContentAreaReauth;
+    case signin_metrics::AccessPoint::ACCESS_POINT_MENU:
+      return account_manager::AccountManagerFacade::AccountAdditionSource::
+          kChromeMenuTurnOnSync;
     default:
       NOTREACHED() << "Reauth is requested from an unknown access point "
                    << static_cast<int>(access_point);
diff --git a/chrome/browser/ui/BUILD.gn b/chrome/browser/ui/BUILD.gn
index 1c613120..e28c0df4 100644
--- a/chrome/browser/ui/BUILD.gn
+++ b/chrome/browser/ui/BUILD.gn
@@ -59,6 +59,8 @@
       "cocoa/applescript/tab_applescript.mm",
       "cocoa/applescript/window_applescript.h",
       "cocoa/applescript/window_applescript.mm",
+      "cocoa/main_menu_builder.h",
+      "cocoa/main_menu_builder.mm",
       "cocoa/scoped_menu_bar_lock.h",
       "cocoa/scoped_menu_bar_lock.mm",
       "cocoa/screentime/fake_webpage_controller.h",
@@ -3690,6 +3692,10 @@
 
   if (is_chromeos_lacros) {
     sources += [
+      "media_router/cast_notification_controller_lacros.cc",
+      "media_router/cast_notification_controller_lacros.h",
+      "media_router/cast_notification_controller_lacros_factory.cc",
+      "media_router/cast_notification_controller_lacros_factory.h",
       "startup/silent_sync_enabler.cc",
       "startup/silent_sync_enabler.h",
       "views/chrome_browser_main_extra_parts_views_lacros.cc",
@@ -4060,8 +4066,6 @@
       "cocoa/l10n_util.mm",
       "cocoa/last_active_browser_cocoa.cc",
       "cocoa/last_active_browser_cocoa.h",
-      "cocoa/main_menu_builder.h",
-      "cocoa/main_menu_builder.mm",
       "cocoa/main_menu_item.h",
       "cocoa/profiles/profile_menu_controller.h",
       "cocoa/profiles/profile_menu_controller.mm",
diff --git a/chrome/browser/ui/android/omnibox/BUILD.gn b/chrome/browser/ui/android/omnibox/BUILD.gn
index 288ea50..43eb20b 100644
--- a/chrome/browser/ui/android/omnibox/BUILD.gn
+++ b/chrome/browser/ui/android/omnibox/BUILD.gn
@@ -451,6 +451,7 @@
     "java/src/org/chromium/chrome/browser/omnibox/suggestions/header/HeaderViewBinderUnitTest.java",
     "java/src/org/chromium/chrome/browser/omnibox/suggestions/mostvisited/MostVisitedTilesProcessorUnitTest.java",
     "java/src/org/chromium/chrome/browser/omnibox/suggestions/tail/AlignmentManagerUnitTest.java",
+    "java/src/org/chromium/chrome/browser/omnibox/suggestions/tail/TailSuggestionProcessorUnitTest.java",
     "java/src/org/chromium/chrome/browser/omnibox/suggestions/tail/TailSuggestionViewBinderUnitTest.java",
     "java/src/org/chromium/chrome/browser/omnibox/voice/RecognitionTestHelper.java",
     "java/src/org/chromium/chrome/browser/omnibox/voice/VoiceRecognitionHandlerUnitTest.java",
diff --git a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/base/BaseSuggestionViewTest.java b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/base/BaseSuggestionViewTest.java
index 1c14abe..99c0abec 100644
--- a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/base/BaseSuggestionViewTest.java
+++ b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/base/BaseSuggestionViewTest.java
@@ -456,12 +456,6 @@
         executeLayoutTest(100, 10, View.LAYOUT_DIRECTION_LTR);
     }
 
-    @Test(expected = AssertionError.class)
-    public void layout_emptyContentViews() {
-        mContentView.setMinimumHeight(0);
-        executeLayoutTest(100, 10, View.LAYOUT_DIRECTION_LTR);
-    }
-
     @Test
     public void layout_minimumHeightWithNoFooterIsSemicompact() {
         mView.setLayoutDirection(View.LAYOUT_DIRECTION_LTR);
diff --git a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/base/SuggestionLayout.java b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/base/SuggestionLayout.java
index cf032bf..69d83b4 100644
--- a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/base/SuggestionLayout.java
+++ b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/base/SuggestionLayout.java
@@ -6,6 +6,7 @@
 
 import android.content.Context;
 import android.graphics.Rect;
+import android.view.View;
 import android.view.ViewGroup;
 
 import androidx.annotation.IntDef;
@@ -277,6 +278,7 @@
     private @Px int measureContentViewHeightPx(@Px int contentWidthPx) {
         int contentHeightPx = 0;
         boolean hasFooter = false;
+        View contentView = null;
 
         for (int index = 0; index < getChildCount(); ++index) {
             var view = getChildAt(index);
@@ -288,6 +290,7 @@
                 // Content views' width is constrained by how much space the decoration views
                 // allocate. These views may, as a result, wrap around to one or more extra lines of
                 // text.
+                contentView = view;
                 view.measure(MeasureSpec.makeMeasureSpec(contentWidthPx, MeasureSpec.EXACTLY),
                         MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
                 contentHeightPx = view.getMeasuredHeight();
@@ -300,13 +303,19 @@
         // suggestion height. Apply necessary corrections here. We currently expect the
         // CONTENT view to properly utilize the LAYOUT GRAVITY to position its content
         // around the center, if its measured height is smaller than our minimum.
-        assert contentHeightPx != 0 : "No content views, or content view is empty";
+        assert contentView != null : "No content views";
 
         // Pad suggestion around to guarantee appropriate spacing around suggestions.
         if (!hasFooter) contentHeightPx += mContentPaddingPx;
 
         // Guarantee that the suggestion height meets our required minimum tap target size.
-        return Math.max(contentHeightPx, hasFooter ? mCompactContentHeightPx : mContentHeightPx);
+        var height =
+                Math.max(contentHeightPx, hasFooter ? mCompactContentHeightPx : mContentHeightPx);
+        // Some views (e.g. TextView) won't render correctly unless measure specs are explicitly
+        // supplied, failing to properly center the content.
+        contentView.measure(MeasureSpec.makeMeasureSpec(contentWidthPx, MeasureSpec.EXACTLY),
+                MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY));
+        return height;
     }
 
     /**
diff --git a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/tail/AlignmentManager.java b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/tail/AlignmentManager.java
index 2536201..d6285a4 100644
--- a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/tail/AlignmentManager.java
+++ b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/tail/AlignmentManager.java
@@ -4,13 +4,11 @@
 
 package org.chromium.chrome.browser.omnibox.suggestions.tail;
 
+import android.util.ArraySet;
 import android.view.View;
 
 import org.chromium.ui.base.ViewUtils;
 
-import java.util.ArrayList;
-import java.util.List;
-
 /**
  * Coordinates horizontal alignment of the tail suggestions.
  * Tail suggestions are aligned to
@@ -30,9 +28,9 @@
  *    [             ... Omnibox Android]
  */
 class AlignmentManager {
-    private final List<View> mVisibleTailSuggestions = new ArrayList<>();
+    private final ArraySet<View> mVisibleTailSuggestions = new ArraySet<>();
     private int mLongestFullTextWidth;
-    private int mLongestQueryWidth;
+    private int mLongestCompletionWidth;
 
     /**
      * Compute additional suggestion text offset for tail suggestions.
@@ -42,21 +40,21 @@
      * - all tail suggestions are left aligned with each other.
      *
      * @param requestor View requesting the pad.
-     * @param queryTextWidth Length of the text that is displayed in the suggestion.
+     * @param completionTextWidth Length of the text that is displayed in the suggestion.
      * @param fullTextWidth Total length of the query (user input + tail).
      * @param textAreaWidth Maximum area size available for tail suggestion.
      * @return additional padding required to properly adjust tail suggestion.
      */
     int requestStartPadding(
-            View requestor, int queryTextWidth, int fullTextWidth, int textAreaWidth) {
+            View requestor, int completionTextWidth, int fullTextWidth, int textAreaWidth) {
         final int lastLongestFullTextWidth = mLongestFullTextWidth;
         mLongestFullTextWidth = Math.max(mLongestFullTextWidth, fullTextWidth);
-        mLongestQueryWidth = Math.max(mLongestQueryWidth, queryTextWidth);
+        mLongestCompletionWidth = Math.max(mLongestCompletionWidth, completionTextWidth);
 
         // If there is enough space to render entire user text, padding is equal to everything not
         // covered by query text.
         if (textAreaWidth >= mLongestFullTextWidth) {
-            return (fullTextWidth - queryTextWidth);
+            return (fullTextWidth - completionTextWidth);
         }
 
         // Only re-layout all children, if we found a new longest text.
@@ -64,7 +62,7 @@
             relayoutAllViewsExcept(requestor);
         }
 
-        return Math.max(textAreaWidth - mLongestQueryWidth, 0);
+        return Math.max(textAreaWidth - mLongestCompletionWidth, 0);
     }
 
     /**
@@ -87,7 +85,7 @@
     private void relayoutAllViewsExcept(View excluded) {
         final int count = mVisibleTailSuggestions.size();
         for (int index = 0; index < count; ++index) {
-            final View view = mVisibleTailSuggestions.get(index);
+            final View view = mVisibleTailSuggestions.valueAt(index);
             if (view == excluded) continue;
             ViewUtils.requestLayout(view, "AlignmentManger.relayoutAllViewsExcept");
         }
diff --git a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/tail/AlignmentManagerUnitTest.java b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/tail/AlignmentManagerUnitTest.java
index 342701b..7be97b9 100644
--- a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/tail/AlignmentManagerUnitTest.java
+++ b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/tail/AlignmentManagerUnitTest.java
@@ -5,20 +5,18 @@
 package org.chromium.chrome.browser.omnibox.suggestions.tail;
 
 import static org.junit.Assert.assertEquals;
-import static org.mockito.Mockito.inOrder;
+import static org.mockito.Mockito.clearInvocations;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
-
-import android.app.Activity;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
 
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
-import org.mockito.InOrder;
 import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-import org.robolectric.Robolectric;
-import org.robolectric.annotation.Config;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
 
 import org.chromium.base.test.BaseRobolectricTestRunner;
 
@@ -26,28 +24,18 @@
  * Tests for {@link AlignmentManager}.
  */
 @RunWith(BaseRobolectricTestRunner.class)
-@Config(manifest = Config.NONE)
 public class AlignmentManagerUnitTest {
     private static final int TEXT_AREA_WIDTH = 100;
 
-    @Mock
-    TailSuggestionView mTailView1;
-
-    @Mock
-    TailSuggestionView mTailView2;
-
-    @Mock
-    TailSuggestionView mTailView3;
-
-    private Activity mActivity;
+    public @Rule MockitoRule mockitoRule = MockitoJUnit.rule();
+    private @Mock TailSuggestionView mTailView1;
+    private @Mock TailSuggestionView mTailView2;
+    private @Mock TailSuggestionView mTailView3;
     private AlignmentManager mManager;
 
     @Before
     public void setUp() {
-        MockitoAnnotations.initMocks(this);
-        mActivity = Robolectric.buildActivity(Activity.class).setup().get();
         mManager = new AlignmentManager();
-
         mManager.registerView(mTailView1);
         mManager.registerView(mTailView2);
         mManager.registerView(mTailView3);
@@ -123,7 +111,6 @@
         final int fullText1Width = 90;
         final int fullText2Width = 120;
         final int fullText3Width = 150;
-        final InOrder inOrder = inOrder(mTailView1, mTailView2, mTailView3);
 
         // First query fits in the target area perfectly (fullText1Width < TEXT_AREA_WIDTH).
         assertEquals(
@@ -132,21 +119,24 @@
         // Second query does not fit, and is the longest one yet. Should force relayout.
         final int expectedAlignment1 = TEXT_AREA_WIDTH - query2Width;
         assertEquals(expectedAlignment1, paddingFor(mTailView2, query2Width, fullText2Width));
-        inOrder.verify(mTailView1, times(1)).requestLayout();
-        inOrder.verify(mTailView3, times(1)).requestLayout();
+        verify(mTailView1, times(1)).requestLayout();
+        verify(mTailView3, times(1)).requestLayout();
         // Confirm that on re-layout, first query gets aligned to the second.
         assertEquals(expectedAlignment1, paddingFor(mTailView1, query1Width, fullText1Width));
 
+        verifyNoMoreInteractions(mTailView1, mTailView2, mTailView3);
+        clearInvocations(mTailView1, mTailView2, mTailView3);
+
         // Third query does not fit, too, and is the next longest query. Should force relayout.
         final int expectedAlignment2 = TEXT_AREA_WIDTH - query3Width;
         assertEquals(expectedAlignment2, paddingFor(mTailView3, query3Width, fullText3Width));
-        inOrder.verify(mTailView1, times(1)).requestLayout();
-        inOrder.verify(mTailView2, times(1)).requestLayout();
+        verify(mTailView1, times(1)).requestLayout();
+        verify(mTailView2, times(1)).requestLayout();
         // Confirm that on re-layout, first two queries get aligned to the third.
         assertEquals(expectedAlignment2, paddingFor(mTailView1, query1Width, fullText1Width));
         assertEquals(expectedAlignment2, paddingFor(mTailView1, query2Width, fullText2Width));
 
-        inOrder.verifyNoMoreInteractions();
+        verifyNoMoreInteractions(mTailView1, mTailView2, mTailView3);
     }
 
     @Test
@@ -157,7 +147,6 @@
         final int fullText1Width = 90;
         final int fullText2Width = 150;
         final int fullText3Width = 120;
-        final InOrder inOrder = inOrder(mTailView1, mTailView2, mTailView3);
 
         // First query fits in the target area perfectly (fullText1Width < TEXT_AREA_WIDTH).
         assertEquals(
@@ -166,8 +155,8 @@
         // Second query does not fit, and is the longest one here. Should force relayout.
         final int expectedTargetAlignment = TEXT_AREA_WIDTH - query2Width;
         assertEquals(expectedTargetAlignment, paddingFor(mTailView2, query2Width, fullText2Width));
-        inOrder.verify(mTailView1, times(1)).requestLayout();
-        inOrder.verify(mTailView3, times(1)).requestLayout();
+        verify(mTailView1, times(1)).requestLayout();
+        verify(mTailView3, times(1)).requestLayout();
         // Confirm that on re-layout, first query gets aligned to the second.
         assertEquals(expectedTargetAlignment, paddingFor(mTailView1, query1Width, fullText1Width));
 
@@ -177,6 +166,6 @@
         assertEquals(expectedTargetAlignment, paddingFor(mTailView1, query1Width, fullText1Width));
         assertEquals(expectedTargetAlignment, paddingFor(mTailView1, query2Width, fullText2Width));
 
-        inOrder.verifyNoMoreInteractions();
+        verifyNoMoreInteractions(mTailView1, mTailView2, mTailView3);
     }
 }
diff --git a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/tail/TailSuggestionProcessor.java b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/tail/TailSuggestionProcessor.java
index 7e43168..62f5fca 100644
--- a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/tail/TailSuggestionProcessor.java
+++ b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/tail/TailSuggestionProcessor.java
@@ -6,6 +6,8 @@
 
 import android.content.Context;
 
+import androidx.annotation.Nullable;
+
 import org.chromium.chrome.browser.omnibox.R;
 import org.chromium.chrome.browser.omnibox.suggestions.SuggestionHost;
 import org.chromium.chrome.browser.omnibox.suggestions.base.BaseSuggestionViewProcessor;
@@ -20,7 +22,7 @@
 /** A class that handles model and view creation for the tail suggestions. */
 public class TailSuggestionProcessor extends BaseSuggestionViewProcessor {
     private final boolean mAlignTailSuggestions;
-    private AlignmentManager mAlignmentManager;
+    private @Nullable AlignmentManager mAlignmentManager;
 
     /**
      * @param context An Android context.
@@ -33,8 +35,7 @@
 
     @Override
     public boolean doesProcessSuggestion(AutocompleteMatch suggestion, int position) {
-        return mAlignTailSuggestions
-                && suggestion.getType() == OmniboxSuggestionType.SEARCH_SUGGEST_TAIL;
+        return suggestion.getType() == OmniboxSuggestionType.SEARCH_SUGGEST_TAIL;
     }
 
     @Override
@@ -51,12 +52,11 @@
     public void populateModel(AutocompleteMatch suggestion, PropertyModel model, int position) {
         super.populateModel(suggestion, model, position);
 
-        assert mAlignmentManager != null;
-
         model.set(TailSuggestionViewProperties.ALIGNMENT_MANAGER, mAlignmentManager);
         model.set(TailSuggestionViewProperties.FILL_INTO_EDIT, suggestion.getFillIntoEdit());
 
-        final SuggestionSpannable text = new SuggestionSpannable(suggestion.getDisplayText());
+        final SuggestionSpannable text =
+                new SuggestionSpannable("… " + suggestion.getDisplayText());
         applyHighlightToMatchRegions(text, suggestion.getDisplayTextClassifications());
         model.set(TailSuggestionViewProperties.TEXT, text);
 
@@ -71,6 +71,6 @@
     @Override
     public void onSuggestionsReceived() {
         super.onSuggestionsReceived();
-        mAlignmentManager = new AlignmentManager();
+        mAlignmentManager = mAlignTailSuggestions ? new AlignmentManager() : null;
     }
 }
diff --git a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/tail/TailSuggestionProcessorUnitTest.java b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/tail/TailSuggestionProcessorUnitTest.java
new file mode 100644
index 0000000..9d67d56
--- /dev/null
+++ b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/tail/TailSuggestionProcessorUnitTest.java
@@ -0,0 +1,91 @@
+// 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.
+
+package org.chromium.chrome.browser.omnibox.suggestions.tail;
+
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+
+import org.chromium.base.test.BaseRobolectricTestRunner;
+import org.chromium.chrome.browser.omnibox.suggestions.SuggestionHost;
+import org.chromium.components.omnibox.AutocompleteMatch;
+import org.chromium.components.omnibox.AutocompleteMatchBuilder;
+import org.chromium.components.omnibox.OmniboxSuggestionType;
+import org.chromium.components.omnibox.suggestions.OmniboxSuggestionUiType;
+import org.chromium.ui.modelutil.PropertyModel;
+
+/**
+ * Tests for {@link TailSuggestionProcessor}.
+ */
+@RunWith(BaseRobolectricTestRunner.class)
+public class TailSuggestionProcessorUnitTest {
+    public @Rule MockitoRule mMockitoRule = MockitoJUnit.rule();
+    private @Mock SuggestionHost mSuggestionHost;
+
+    private TailSuggestionProcessor mProcessor;
+    private AutocompleteMatch mSuggestion;
+    private PropertyModel mModel;
+
+    @Before
+    public void setUp() {
+        mProcessor = new TailSuggestionProcessor(RuntimeEnvironment.application, mSuggestionHost);
+    }
+
+    /** Create search suggestion for test. */
+    private void createSearchSuggestion(int type, String title) {
+        mSuggestion = AutocompleteMatchBuilder.searchWithType(type)
+                              .setDisplayText(title)
+                              .setFillIntoEdit("fill into edit: " + title)
+                              .build();
+        mModel = mProcessor.createModel();
+        mProcessor.populateModel(mSuggestion, mModel, 0);
+    }
+
+    @Test
+    @Config(qualifiers = "w400dp")
+    public void populateModel_tailSuggestion_phone() {
+        mProcessor.onSuggestionsReceived();
+        createSearchSuggestion(OmniboxSuggestionType.SEARCH_SUGGEST_TAIL, "tail");
+
+        Assert.assertTrue(mProcessor.doesProcessSuggestion(mSuggestion, 1));
+        // Alignment is suppressed on phones.
+        Assert.assertNull(mModel.get(TailSuggestionViewProperties.ALIGNMENT_MANAGER));
+        Assert.assertEquals("… tail", mModel.get(TailSuggestionViewProperties.TEXT).toString());
+        Assert.assertEquals(
+                "fill into edit: tail", mModel.get(TailSuggestionViewProperties.FILL_INTO_EDIT));
+    }
+
+    @Test
+    @Config(qualifiers = "w600dp-h820dp")
+    public void populateModel_tailSuggestion_tablet() {
+        mProcessor.onSuggestionsReceived();
+        createSearchSuggestion(OmniboxSuggestionType.SEARCH_SUGGEST_TAIL, "tail");
+
+        Assert.assertTrue(mProcessor.doesProcessSuggestion(mSuggestion, 1));
+        Assert.assertNotNull(mModel.get(TailSuggestionViewProperties.ALIGNMENT_MANAGER));
+        Assert.assertEquals("… tail", mModel.get(TailSuggestionViewProperties.TEXT).toString());
+        Assert.assertEquals(
+                "fill into edit: tail", mModel.get(TailSuggestionViewProperties.FILL_INTO_EDIT));
+    }
+
+    @Test
+    public void doesProcessSuggestion_nonTailSuggestion() {
+        mProcessor.onSuggestionsReceived();
+        createSearchSuggestion(OmniboxSuggestionType.SEARCH_SUGGEST, "search");
+        Assert.assertFalse(mProcessor.doesProcessSuggestion(mSuggestion, 1));
+    }
+
+    @Test
+    public void getViewTypeId_forFullTestCoverage() {
+        Assert.assertEquals(OmniboxSuggestionUiType.TAIL_SUGGESTION, mProcessor.getViewTypeId());
+    }
+}
diff --git a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/tail/TailSuggestionView.java b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/tail/TailSuggestionView.java
index 39c6c67..158a5123 100644
--- a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/tail/TailSuggestionView.java
+++ b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/tail/TailSuggestionView.java
@@ -37,7 +37,9 @@
      */
     void setAlignmentManager(AlignmentManager coordinator) {
         mAlignmentManager = coordinator;
-        mAlignmentManager.registerView(this);
+        if (mAlignmentManager != null) {
+            mAlignmentManager.registerView(this);
+        }
     }
 
     /**
diff --git a/chrome/browser/ui/ash/desks/desks_templates_app_launch_handler.cc b/chrome/browser/ui/ash/desks/desks_templates_app_launch_handler.cc
index 8f23888c..7bccdac8 100644
--- a/chrome/browser/ui/ash/desks/desks_templates_app_launch_handler.cc
+++ b/chrome/browser/ui/ash/desks/desks_templates_app_launch_handler.cc
@@ -299,12 +299,23 @@
     if (app_id != app_constants::kLacrosAppId)
       continue;
 
+    // Count the number of lacros windows ash intends to launch. Will be
+    // checked at lacros side to see if anything is missing between ash and
+    // lacros when restoring saved desk.
+    // TODO(crbug.com/1442076): Remove after issue is root caused.
+    int windows_count = 0;
+
     for (const auto& [restore_window_id, app_restore_data] : iter.second) {
       if (!app_restore_data->active_tab_index.has_value() ||
           app_restore_data->urls.empty()) {
         continue;
       }
 
+      // TODO(crbug.com/1442076): Remove after issue is root caused.
+      windows_count++;
+      LOG(ERROR) << "window " << restore_window_id << " launched by Ash with "
+                 << app_restore_data->urls.size() << " tabs";
+
       crosapi::BrowserManager::Get()->CreateBrowserWithRestoredData(
           app_restore_data->urls,
           app_restore_data->current_bounds.value_or(gfx::Rect()),
@@ -318,6 +329,9 @@
           app_restore_data->first_non_pinned_tab_index.value_or(0),
           GetBrowserAppName(app_restore_data, app_id), restore_window_id);
     }
+    // TODO(crbug.com/1442076): Remove after issue is root caused.
+    LOG(ERROR) << windows_count
+               << " windows launched by Ash in total for this desk";
   }
   restore_data()->RemoveApp(app_constants::kLacrosAppId);
 }
diff --git a/chrome/browser/ui/ash/glanceables/glanceables_tasks_client_impl.cc b/chrome/browser/ui/ash/glanceables/glanceables_tasks_client_impl.cc
index 759ac229..f9051be 100644
--- a/chrome/browser/ui/ash/glanceables/glanceables_tasks_client_impl.cc
+++ b/chrome/browser/ui/ash/glanceables/glanceables_tasks_client_impl.cc
@@ -256,7 +256,8 @@
   if (!request_sender_) {
     CHECK(create_request_sender_callback_);
     request_sender_ = std::move(create_request_sender_callback_)
-                          .Run({GaiaConstants::kTasksReadOnlyOAuth2Scope},
+                          .Run({GaiaConstants::kTasksReadOnlyOAuth2Scope,
+                                GaiaConstants::kTasksOAuth2Scope},
                                kTrafficAnnotationTag);
     CHECK(request_sender_);
   }
diff --git a/chrome/browser/ui/autofill/chrome_autofill_client.cc b/chrome/browser/ui/autofill/chrome_autofill_client.cc
index f2a2158..fddc23e 100644
--- a/chrome/browser/ui/autofill/chrome_autofill_client.cc
+++ b/chrome/browser/ui/autofill/chrome_autofill_client.cc
@@ -539,6 +539,14 @@
                    std::move(close_mandatory_reauth_callback));
 }
 
+void ChromeAutofillClient::ShowMandatoryReauthOptInConfirmation() {
+  MandatoryReauthBubbleControllerImpl::CreateForWebContents(web_contents());
+  // TODO(crbug.com/4555994): Pass in the bubble type as a parameter so we
+  // enforce that the confirmation bubble is shown.
+  MandatoryReauthBubbleControllerImpl::FromWebContents(web_contents())
+      ->ReshowBubble();
+}
+
 #if !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_IOS)
 void ChromeAutofillClient::HideVirtualCardEnrollBubbleAndIconIfVisible() {
   VirtualCardEnrollBubbleControllerImpl::CreateForWebContents(web_contents());
diff --git a/chrome/browser/ui/autofill/chrome_autofill_client.h b/chrome/browser/ui/autofill/chrome_autofill_client.h
index 208e2d3..1df151a 100644
--- a/chrome/browser/ui/autofill/chrome_autofill_client.h
+++ b/chrome/browser/ui/autofill/chrome_autofill_client.h
@@ -151,6 +151,7 @@
       base::OnceClosure accept_mandatory_reauth_callback,
       base::OnceClosure cancel_mandatory_reauth_callback,
       base::RepeatingClosure close_mandatory_reauth_callback) override;
+  void ShowMandatoryReauthOptInConfirmation() override;
 #if !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_IOS)
   void HideVirtualCardEnrollBubbleAndIconIfVisible() override;
 #endif
diff --git a/chrome/browser/ui/browser_command_controller.cc b/chrome/browser/ui/browser_command_controller.cc
index e8f1ea8a..fd0b8e2 100644
--- a/chrome/browser/ui/browser_command_controller.cc
+++ b/chrome/browser/ui/browser_command_controller.cc
@@ -32,6 +32,7 @@
 #include "chrome/browser/shell_integration.h"
 #include "chrome/browser/signin/identity_manager_factory.h"
 #include "chrome/browser/signin/signin_promo.h"
+#include "chrome/browser/signin/signin_ui_util.h"
 #include "chrome/browser/ui/apps/app_info_dialog.h"
 #include "chrome/browser/ui/bookmarks/bookmark_tab_helper.h"
 #include "chrome/browser/ui/browser.h"
@@ -76,6 +77,7 @@
 #include "components/sessions/content/session_tab_helper.h"
 #include "components/sessions/core/tab_restore_service.h"
 #include "components/signin/public/base/signin_buildflags.h"
+#include "components/signin/public/base/signin_metrics.h"
 #include "components/signin/public/base/signin_pref_names.h"
 #include "components/signin/public/identity_manager/identity_manager.h"
 #include "content/public/browser/native_web_keyboard_event.h"
@@ -637,6 +639,18 @@
     case IDC_SAVE_AUTOFILL_ADDRESS:
       SaveAutofillAddress(browser_);
       break;
+    case IDC_SHOW_SYNC_SETTINGS:
+      chrome::ShowSettingsSubPage(browser_, chrome::kSyncSetupSubPage);
+      break;
+    case IDC_TURN_ON_SYNC:
+      signin_ui_util::EnableSyncFromSingleAccountPromo(
+          browser_->profile(), GetAccountInfoFromProfile(browser_->profile()),
+          signin_metrics::AccessPoint::ACCESS_POINT_MENU);
+      break;
+    case IDC_SHOW_SIGNIN_WHEN_PAUSED:
+      signin_ui_util::ShowReauthForPrimaryAccountWithAuthError(
+          browser_->profile(), signin_metrics::AccessPoint::ACCESS_POINT_MENU);
+      break;
     case IDC_SHOW_PASSWORD_MANAGER:
       ShowPasswordManager(browser_);
       break;
@@ -1234,6 +1248,9 @@
                                         !guest_session);
   command_updater_.UpdateCommandEnabled(IDC_SHOW_PAYMENT_METHODS,
                                         !guest_session);
+  command_updater_.UpdateCommandEnabled(IDC_SHOW_SYNC_SETTINGS, true);
+  command_updater_.UpdateCommandEnabled(IDC_TURN_ON_SYNC, true);
+  command_updater_.UpdateCommandEnabled(IDC_SHOW_SIGNIN_WHEN_PAUSED, true);
   command_updater_.UpdateCommandEnabled(IDC_SHOW_ADDRESSES, !guest_session);
   command_updater_.UpdateCommandEnabled(IDC_HELP_MENU, true);
   command_updater_.UpdateCommandEnabled(IDC_HELP_PAGE_VIA_KEYBOARD, true);
diff --git a/chrome/browser/ui/browser_command_controller_browsertest.cc b/chrome/browser/ui/browser_command_controller_browsertest.cc
index e740afc..f826f73 100644
--- a/chrome/browser/ui/browser_command_controller_browsertest.cc
+++ b/chrome/browser/ui/browser_command_controller_browsertest.cc
@@ -30,6 +30,7 @@
 #include "components/sessions/core/tab_restore_service.h"
 #include "components/sessions/core/tab_restore_service_observer.h"
 #include "components/signin/public/identity_manager/identity_manager.h"
+#include "components/signin/public/identity_manager/identity_test_environment.h"
 #include "components/signin/public/identity_manager/identity_test_utils.h"
 #include "content/public/test/browser_test.h"
 #include "content/public/test/test_utils.h"
@@ -306,5 +307,32 @@
                        ExecuteProfileMenuCloseProfile) {
   EXPECT_TRUE(chrome::ExecuteCommand(browser(), IDC_CLOSE_PROFILE));
 }
+
+IN_PROC_BROWSER_TEST_F(BrowserCommandControllerBrowserTestRefreshOnly,
+                       ExecuteShowSyncSettings) {
+  EXPECT_TRUE(chrome::ExecuteCommand(browser(), IDC_SHOW_SYNC_SETTINGS));
+  content::WebContents* web_contents =
+      browser()->tab_strip_model()->GetActiveWebContents();
+  content::WaitForLoadStop(web_contents);
+  EXPECT_EQ(web_contents->GetURL().possibly_invalid_spec(),
+            "chrome://settings/syncSetup");
+}
+
+IN_PROC_BROWSER_TEST_F(BrowserCommandControllerBrowserTestRefreshOnly,
+                       ExecuteTurnOnSync) {
+  EXPECT_TRUE(chrome::ExecuteCommand(browser(), IDC_TURN_ON_SYNC));
+}
+
+IN_PROC_BROWSER_TEST_F(BrowserCommandControllerBrowserTestRefreshOnly,
+                       ExecuteShowSigninWhenPaused) {
+  signin::IdentityManager* identity_manager =
+      IdentityManagerFactory::GetForProfile(browser()->profile());
+  signin::MakePrimaryAccountAvailable(identity_manager, "user@example.com",
+                                      signin::ConsentLevel::kSync);
+  signin::SetRefreshTokenForPrimaryAccount(identity_manager);
+  signin::SetInvalidRefreshTokenForPrimaryAccount(identity_manager);
+  EXPECT_TRUE(chrome::ExecuteCommand(browser(), IDC_SHOW_SIGNIN_WHEN_PAUSED));
+}
+
 #endif
 }  // namespace chrome
diff --git a/chrome/browser/ui/cocoa/bookmarks/bookmark_menu_bridge_unittest.mm b/chrome/browser/ui/cocoa/bookmarks/bookmark_menu_bridge_unittest.mm
index d412f10..9848bb3b 100644
--- a/chrome/browser/ui/cocoa/bookmarks/bookmark_menu_bridge_unittest.mm
+++ b/chrome/browser/ui/cocoa/bookmarks/bookmark_menu_bridge_unittest.mm
@@ -24,13 +24,17 @@
 #import "testing/gtest_mac.h"
 #import "testing/platform_test.h"
 
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
 using base::ASCIIToUTF16;
 using bookmarks::BookmarkModel;
 using bookmarks::BookmarkNode;
 
 class BookmarkMenuBridgeTest : public BrowserWithTestWindowTest {
  public:
-  BookmarkMenuBridgeTest() {}
+  BookmarkMenuBridgeTest() = default;
 
   BookmarkMenuBridgeTest(const BookmarkMenuBridgeTest&) = delete;
   BookmarkMenuBridgeTest& operator=(const BookmarkMenuBridgeTest&) = delete;
@@ -40,7 +44,7 @@
 
     bookmarks::test::WaitForBookmarkModelToLoad(
         BookmarkModelFactory::GetForBrowserContext(profile()));
-    menu_.reset([[NSMenu alloc] initWithTitle:@"test"]);
+    menu_ = [[NSMenu alloc] initWithTitle:@"test"];
 
     bridge_ = std::make_unique<BookmarkMenuBridge>(profile(), menu_);
   }
@@ -82,9 +86,9 @@
   }
 
   NSMenuItem* AddTestMenuItem(NSMenu *menu, NSString *title, SEL selector) {
-    NSMenuItem* item = [[[NSMenuItem alloc] initWithTitle:title
-                                                   action:nullptr
-                                            keyEquivalent:@""] autorelease];
+    NSMenuItem* item = [[NSMenuItem alloc] initWithTitle:title
+                                                  action:nullptr
+                                           keyEquivalent:@""];
     if (selector)
       [item setAction:selector];
     [menu addItem:item];
@@ -92,7 +96,7 @@
   }
 
  protected:
-  base::scoped_nsobject<NSMenu> menu_;
+  NSMenu* __strong menu_;
   std::unique_ptr<BookmarkMenuBridge> bridge_;
 
  private:
@@ -127,7 +131,7 @@
   AddTestMenuItem(menu_, @"hi mom", nil);
   AddTestMenuItem(menu_, @"not", @selector(openBookmarkMenuItem:));
   NSMenuItem* test_item = AddTestMenuItem(menu_, @"hi mom", nil);
-  [test_item setSubmenu:[[[NSMenu alloc] initWithTitle:@"bar"] autorelease]];
+  [test_item setSubmenu:[[NSMenu alloc] initWithTitle:@"bar"]];
   AddTestMenuItem(menu_, @"not", @selector(openBookmarkMenuItem:));
   AddTestMenuItem(menu_, @"zippy", @selector(length));
   [menu_ addItem:[NSMenuItem separatorItem]];
@@ -282,15 +286,14 @@
   model->AddURL(folder, 1, u"Test 2", GURL("http://second-test"));
 
   UpdateRootMenu();
-  base::scoped_nsobject<NSMenu> old_menu(
-      [[[menu_ itemAtIndex:1] submenu] retain]);
+  NSMenu* old_menu = [[menu_ itemAtIndex:1] submenu];
   EXPECT_TRUE([old_menu delegate]);
 
   // If the menu was never built, ensure UpdateRootMenu() also clears delegates
   // from unbuilt submenus, since they will no longer be reachable.
   InvalidateMenu();
   UpdateRootMenu();
-  EXPECT_NE(old_menu.get(), [[menu_ itemAtIndex:1] submenu]);
+  EXPECT_NE(old_menu, [[menu_ itemAtIndex:1] submenu]);
   EXPECT_FALSE([old_menu delegate]);
 
   bridge_->UpdateMenu([[menu_ itemAtIndex:1] submenu], folder,
diff --git a/chrome/browser/ui/cocoa/bookmarks/bookmark_menu_cocoa_controller_unittest.mm b/chrome/browser/ui/cocoa/bookmarks/bookmark_menu_cocoa_controller_unittest.mm
index 5f0f8a56..e80ba9f 100644
--- a/chrome/browser/ui/cocoa/bookmarks/bookmark_menu_cocoa_controller_unittest.mm
+++ b/chrome/browser/ui/cocoa/bookmarks/bookmark_menu_cocoa_controller_unittest.mm
@@ -8,7 +8,6 @@
 #include <string>
 
 #include "base/containers/span.h"
-#import "base/mac/scoped_nsobject.h"
 #include "base/ranges/algorithm.h"
 #include "chrome/browser/bookmarks/bookmark_model_factory.h"
 #include "chrome/browser/ui/browser.h"
@@ -20,6 +19,10 @@
 #include "components/bookmarks/test/bookmark_test_helpers.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
 using bookmarks::BookmarkModel;
 using bookmarks::BookmarkNode;
 
@@ -74,8 +77,8 @@
 
     bookmarks::test::WaitForBookmarkModelToLoad(
         BookmarkModelFactory::GetForBrowserContext(profile()));
-    controller_.reset(
-        [[FakeBookmarkMenuController alloc] initWithProfile:profile()]);
+    controller_ =
+        [[FakeBookmarkMenuController alloc] initWithProfile:profile()];
   }
 
   TestingProfile::TestingFactories GetTestingFactories() override {
@@ -83,16 +86,16 @@
              BookmarkModelFactory::GetDefaultFactory()}};
   }
 
-  FakeBookmarkMenuController* controller() { return controller_.get(); }
+  FakeBookmarkMenuController* controller() { return controller_; }
 
  private:
   CocoaTestHelper cocoa_test_helper_;
-  base::scoped_nsobject<FakeBookmarkMenuController> controller_;
+  FakeBookmarkMenuController* __strong controller_;
 };
 
 TEST_F(BookmarkMenuCocoaControllerTest, TestOpenItem) {
   FakeBookmarkMenuController* c = controller();
-  NSMenuItem *item = [[[NSMenuItem alloc] init] autorelease];
+  NSMenuItem* item = [[NSMenuItem alloc] init];
   for (int i = 0; i < 2; i++) {
     [item setTag:i];
     ASSERT_EQ(c->_opened[i], NO);
diff --git a/chrome/browser/ui/cocoa/confirm_quit_panel_controller_unittest.mm b/chrome/browser/ui/cocoa/confirm_quit_panel_controller_unittest.mm
index 52729f3..70395c1 100644
--- a/chrome/browser/ui/cocoa/confirm_quit_panel_controller_unittest.mm
+++ b/chrome/browser/ui/cocoa/confirm_quit_panel_controller_unittest.mm
@@ -9,6 +9,10 @@
 #include "testing/gtest_mac.h"
 #include "ui/base/accelerators/platform_accelerator_cocoa.h"
 
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
 namespace {
 
 class ConfirmQuitPanelControllerTest : public CocoaTest {
@@ -43,9 +47,9 @@
 TEST_F(ConfirmQuitPanelControllerTest, KeyCombinationForMenuItem) {
   Class controller = [ConfirmQuitPanelController class];
 
-  NSMenuItem* item = [[[NSMenuItem alloc] initWithTitle:@""
-                                                 action:@selector(unused)
-                                          keyEquivalent:@""] autorelease];
+  NSMenuItem* item = [[NSMenuItem alloc] initWithTitle:@""
+                                                action:@selector(unused)
+                                         keyEquivalent:@""];
   item.keyEquivalent = @"q";
   item.keyEquivalentModifierMask = NSEventModifierFlagCommand;
   EXPECT_NSEQ(TestString(@"{Cmd}Q"),
diff --git a/chrome/browser/ui/cocoa/find_pasteboard_unittest.mm b/chrome/browser/ui/cocoa/find_pasteboard_unittest.mm
index e75fef7..8b8bb9a 100644
--- a/chrome/browser/ui/cocoa/find_pasteboard_unittest.mm
+++ b/chrome/browser/ui/cocoa/find_pasteboard_unittest.mm
@@ -4,7 +4,6 @@
 
 #import <Cocoa/Cocoa.h>
 
-#include "base/mac/scoped_nsobject.h"
 #include "base/memory/ref_counted.h"
 #import "chrome/browser/ui/cocoa/test/cocoa_test_helper.h"
 #include "testing/gtest/include/gtest/gtest.h"
@@ -12,6 +11,10 @@
 #include "ui/base/clipboard/clipboard_util_mac.h"
 #import "ui/base/cocoa/find_pasteboard.h"
 
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
 // A subclass of FindPasteboard that doesn't write to the real find pasteboard.
 @interface FindPasteboardTesting : FindPasteboard {
  @private
@@ -58,57 +61,56 @@
 
   void SetUp() override {
     CocoaTest::SetUp();
-    pasteboard_.reset([[FindPasteboardTesting alloc] init]);
-    ASSERT_TRUE(pasteboard_.get());
+    pasteboard_ = [[FindPasteboardTesting alloc] init];
+    ASSERT_TRUE(pasteboard_);
   }
 
   void TearDown() override {
-    pasteboard_.reset();
+    pasteboard_ = nil;
     CocoaTest::TearDown();
   }
 
  protected:
-  base::scoped_nsobject<FindPasteboardTesting> pasteboard_;
+  FindPasteboardTesting* __strong pasteboard_;
 };
 
 TEST_F(FindPasteboardTest, SettingTextUpdatesPboard) {
-  [pasteboard_.get() setFindText:@"text"];
-  EXPECT_EQ(NSOrderedSame,
-            [[pasteboard_.get() findPasteboardText] compare:@"text"]);
+  [pasteboard_ setFindText:@"text"];
+  EXPECT_EQ(NSOrderedSame, [[pasteboard_ findPasteboardText] compare:@"text"]);
 }
 
 TEST_F(FindPasteboardTest, ReadingFromPboardUpdatesFindText) {
-  [pasteboard_.get() setFindPasteboardText:@"text"];
-  [pasteboard_.get() loadTextFromPasteboard:nil];
-  EXPECT_EQ(NSOrderedSame, [[pasteboard_.get() findText] compare:@"text"]);
+  [pasteboard_ setFindPasteboardText:@"text"];
+  [pasteboard_ loadTextFromPasteboard:nil];
+  EXPECT_EQ(NSOrderedSame, [[pasteboard_ findText] compare:@"text"]);
 }
 
 TEST_F(FindPasteboardTest, SendsNotificationWhenTextChanges) {
   __block int notification_count = 0;
   [NSNotificationCenter.defaultCenter
       addObserverForName:kFindPasteboardChangedNotification
-                  object:pasteboard_.get()
+                  object:pasteboard_
                    queue:nil
               usingBlock:^(NSNotification* note) {
                 ++notification_count;
               }];
   EXPECT_EQ(0, notification_count);
-  [pasteboard_.get() setFindText:@"text"];
+  [pasteboard_ setFindText:@"text"];
   EXPECT_EQ(1, notification_count);
-  [pasteboard_.get() setFindText:@"text"];
+  [pasteboard_ setFindText:@"text"];
   EXPECT_EQ(1, notification_count);
-  [pasteboard_.get() setFindText:@"other text"];
+  [pasteboard_ setFindText:@"other text"];
   EXPECT_EQ(2, notification_count);
 
-  [pasteboard_.get() setFindPasteboardText:@"other text"];
-  [pasteboard_.get() loadTextFromPasteboard:nil];
+  [pasteboard_ setFindPasteboardText:@"other text"];
+  [pasteboard_ loadTextFromPasteboard:nil];
   EXPECT_EQ(2, notification_count);
 
-  [pasteboard_.get() setFindPasteboardText:@"otherer text"];
-  [pasteboard_.get() loadTextFromPasteboard:nil];
+  [pasteboard_ setFindPasteboardText:@"otherer text"];
+  [pasteboard_ loadTextFromPasteboard:nil];
   EXPECT_EQ(3, notification_count);
 
-  [[NSNotificationCenter defaultCenter] removeObserver:pasteboard_.get()];
+  [NSNotificationCenter.defaultCenter removeObserver:pasteboard_];
 }
 
 }  // namespace
diff --git a/chrome/browser/ui/cocoa/history_menu_bridge_unittest.mm b/chrome/browser/ui/cocoa/history_menu_bridge_unittest.mm
index 5166e66..1632d958 100644
--- a/chrome/browser/ui/cocoa/history_menu_bridge_unittest.mm
+++ b/chrome/browser/ui/cocoa/history_menu_bridge_unittest.mm
@@ -35,11 +35,15 @@
 #include "third_party/skia/include/core/SkBitmap.h"
 #include "ui/gfx/codec/png_codec.h"
 
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
 namespace {
 
 class MockTRS : public sessions::TabRestoreServiceImpl {
  public:
-  MockTRS(Profile* profile)
+  explicit MockTRS(Profile* profile)
       : sessions::TabRestoreServiceImpl(
             std::make_unique<ChromeTabRestoreServiceClient>(profile),
             profile->GetPrefs(),
@@ -99,14 +103,14 @@
 
 class MockBridge : public HistoryMenuBridge {
  public:
-  MockBridge(Profile* profile)
+  explicit MockBridge(Profile* profile)
       : HistoryMenuBridge(profile),
         menu_([[NSMenu alloc] initWithTitle:@"History"]) {}
 
-  NSMenu* HistoryMenu() override { return menu_.get(); }
+  NSMenu* HistoryMenu() override { return menu_; }
 
  private:
-  base::scoped_nsobject<NSMenu> menu_;
+  NSMenu* __strong menu_;
 };
 
 class HistoryMenuBridgeTest : public BrowserWithTestWindowTest {
@@ -154,8 +158,9 @@
                             NSString* title,
                             SEL selector,
                             int tag) {
-    NSMenuItem* item = [[[NSMenuItem alloc] initWithTitle:title action:NULL
-                                            keyEquivalent:@""] autorelease];
+    NSMenuItem* item = [[NSMenuItem alloc] initWithTitle:title
+                                                  action:nullptr
+                                           keyEquivalent:@""];
     [item setTag:tag];
     if (selector) {
       [item setAction:selector];
@@ -218,13 +223,13 @@
   NSInteger always_visible_items[] = {IDC_HOME, IDC_BACK, IDC_FORWARD};
   for (size_t i = 0; i < std::size(always_visible_items); i++) {
     // Create a fake item with tag.
-    base::scoped_nsobject<NSMenuItem> item([[NSMenuItem alloc] init]);
-    item.get().tag = always_visible_items[i];
+    NSMenuItem* item = [[NSMenuItem alloc] init];
+    item.tag = always_visible_items[i];
     EXPECT_TRUE(test->ShouldMenuItemBeVisible(item));
   }
 
-  // Check visibilty of items belong to regular mode. They should be visible for
-  // regular mode, not for incognito mode.
+  // Check visibility of items belong to regular mode. They should be visible
+  // for regular mode, not for incognito mode.
   NSInteger regular_visible_items[] = {
       HistoryMenuBridge::kRecentlyClosedSeparator,
       HistoryMenuBridge::kRecentlyClosedTitle,
@@ -234,16 +239,16 @@
       IDC_SHOW_HISTORY};
   for (size_t i = 0; i < std::size(regular_visible_items); i++) {
     // Create a fake item with tag.
-    base::scoped_nsobject<NSMenuItem> item([[NSMenuItem alloc] init]);
-    item.get().tag = regular_visible_items[i];
+    NSMenuItem* item = [[NSMenuItem alloc] init];
+    item.tag = regular_visible_items[i];
     EXPECT_EQ(!is_incognito, test->ShouldMenuItemBeVisible(item));
   }
 }
 
 // Edge case test for clearing until the end of a menu.
 TEST_F(HistoryMenuBridgeTest, ClearHistoryMenuUntilEnd) {
-  NSMenu* menu = [[[NSMenu alloc] initWithTitle:@"history foo"] autorelease];
-  AddItemToMenu(menu, @"HEADER", NULL, HistoryMenuBridge::kVisitedTitle);
+  NSMenu* menu = [[NSMenu alloc] initWithTitle:@"history foo"];
+  AddItemToMenu(menu, @"HEADER", nullptr, HistoryMenuBridge::kVisitedTitle);
 
   NSInteger tag = HistoryMenuBridge::kVisited;
   AddItemToMenu(menu, @"alpha", @selector(openHistoryMenuItem:), tag);
@@ -260,13 +265,14 @@
 
 // Skip menu items that are not hooked up to |-openHistoryMenuItem:|.
 TEST_F(HistoryMenuBridgeTest, ClearHistoryMenuSkipping) {
-  NSMenu* menu = [[[NSMenu alloc] initWithTitle:@"history foo"] autorelease];
-  AddItemToMenu(menu, @"HEADER", NULL, HistoryMenuBridge::kVisitedTitle);
+  NSMenu* menu = [[NSMenu alloc] initWithTitle:@"history foo"];
+  AddItemToMenu(menu, @"HEADER", nullptr, HistoryMenuBridge::kVisitedTitle);
 
   NSInteger tag = HistoryMenuBridge::kVisited;
   AddItemToMenu(menu, @"alpha", @selector(openHistoryMenuItem:), tag);
   AddItemToMenu(menu, @"bravo", @selector(openHistoryMenuItem:), tag);
-  AddItemToMenu(menu, @"TITLE", NULL, HistoryMenuBridge::kRecentlyClosedTitle);
+  AddItemToMenu(menu, @"TITLE", nullptr,
+                HistoryMenuBridge::kRecentlyClosedTitle);
   AddItemToMenu(menu, @"charlie", @selector(openHistoryMenuItem:), tag);
 
   ClearMenuSection(menu, tag);
@@ -280,8 +286,8 @@
 
 // Edge case test for clearing an empty menu.
 TEST_F(HistoryMenuBridgeTest, ClearHistoryMenuEmpty) {
-  NSMenu* menu = [[[NSMenu alloc] initWithTitle:@"history foo"] autorelease];
-  AddItemToMenu(menu, @"HEADER", NULL, HistoryMenuBridge::kVisited);
+  NSMenu* menu = [[NSMenu alloc] initWithTitle:@"history foo"];
+  AddItemToMenu(menu, @"HEADER", nullptr, HistoryMenuBridge::kVisited);
 
   ClearMenuSection(menu, HistoryMenuBridge::kVisited);
 
@@ -292,7 +298,7 @@
 
 // Test that AddItemToMenu() properly adds HistoryItem objects as menus.
 TEST_F(HistoryMenuBridgeTest, AddItemToMenu) {
-  NSMenu* menu = [[[NSMenu alloc] initWithTitle:@"history foo"] autorelease];
+  NSMenu* menu = [[NSMenu alloc] initWithTitle:@"history foo"];
 
   const std::u16string short_url = u"http://foo/";
   const std::u16string long_url =
diff --git a/chrome/browser/ui/cocoa/history_menu_cocoa_controller_unittest.mm b/chrome/browser/ui/cocoa/history_menu_cocoa_controller_unittest.mm
index f9e925f..05079b5e 100644
--- a/chrome/browser/ui/cocoa/history_menu_cocoa_controller_unittest.mm
+++ b/chrome/browser/ui/cocoa/history_menu_cocoa_controller_unittest.mm
@@ -6,7 +6,6 @@
 
 #include <memory>
 
-#include "base/mac/scoped_nsobject.h"
 #include "base/strings/string_util.h"
 #include "base/strings/sys_string_conversions.h"
 #include "chrome/app/chrome_command_ids.h"
@@ -16,22 +15,19 @@
 #include "chrome/test/base/testing_profile.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
 @interface FakeHistoryMenuController : HistoryMenuCocoaController {
  @public
+  // ivars are initialized to zero, so these all start out as NO.
   BOOL _opened[3];
 }
 @end
 
 @implementation FakeHistoryMenuController
 
-- (instancetype)initTest {
-  if ((self = [super init])) {
-    _opened[1] = NO;
-    _opened[2] = NO;
-  }
-  return self;
-}
-
 - (void)openURLForItem:(const HistoryMenuBridge::HistoryItem*)item {
   _opened[item->session_id.id()] = YES;
 }
@@ -47,7 +43,6 @@
     bridge_ = std::make_unique<HistoryMenuBridge>(profile());
     bridge_->controller_.reset(
         [[FakeHistoryMenuController alloc] initWithBridge:bridge_.get()]);
-    [controller() initTest];
   }
 
   void TearDown() override {
@@ -84,8 +79,8 @@
 };
 
 TEST_F(HistoryMenuCocoaControllerTest, OpenURLForItem) {
-  base::scoped_nsobject<NSMenu> menu([[NSMenu alloc] initWithTitle:@"History"]);
-  CreateItems(menu.get());
+  NSMenu* menu = [[NSMenu alloc] initWithTitle:@"History"];
+  CreateItems(menu);
 
   std::map<NSMenuItem*, std::unique_ptr<HistoryMenuBridge::HistoryItem>>&
       items = menu_item_map();
diff --git a/chrome/browser/ui/cocoa/history_overlay_controller_unittest.mm b/chrome/browser/ui/cocoa/history_overlay_controller_unittest.mm
index 5e7c7fd..a06c0f4a4 100644
--- a/chrome/browser/ui/cocoa/history_overlay_controller_unittest.mm
+++ b/chrome/browser/ui/cocoa/history_overlay_controller_unittest.mm
@@ -6,13 +6,17 @@
 
 #import "chrome/browser/ui/cocoa/test/cocoa_test_helper.h"
 
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
 class HistoryOverlayControllerTest : public CocoaTest {
  public:
   void SetUp() override {
     CocoaTest::SetUp();
 
     // The overlay controller shows the panel as a subview of the given view.
-    test_view_.reset([[NSView alloc] initWithFrame:NSMakeRect(10, 10, 10, 10)]);
+    test_view_ = [[NSView alloc] initWithFrame:NSMakeRect(10, 10, 10, 10)];
 
     // We add it to the test_window for authenticity.
     [[test_window() contentView] addSubview:test_view_];
@@ -23,15 +27,15 @@
   }
 
  private:
-  base::scoped_nsobject<NSView> test_view_;
+  NSView* __strong test_view_;
 };
 
 // Tests that the overlay view gets added and removed at the appropriate times.
 TEST_F(HistoryOverlayControllerTest, DismissClearsAnimationsAndRemovesView) {
   EXPECT_EQ(0u, [[test_view() subviews] count]);
 
-  base::scoped_nsobject<HistoryOverlayController> controller(
-      [[HistoryOverlayController alloc] initForMode:kHistoryOverlayModeBack]);
+  HistoryOverlayController* controller =
+      [[HistoryOverlayController alloc] initForMode:kHistoryOverlayModeBack];
   [controller showPanelForView:test_view()];
   EXPECT_EQ(1u, [[test_view() subviews] count]);
 
diff --git a/chrome/browser/ui/cocoa/main_menu_builder.h b/chrome/browser/ui/cocoa/main_menu_builder.h
index a1b8a3b..dc3e47f 100644
--- a/chrome/browser/ui/cocoa/main_menu_builder.h
+++ b/chrome/browser/ui/cocoa/main_menu_builder.h
@@ -12,9 +12,12 @@
 #include <vector>
 
 #include "base/check_op.h"
-#include "base/mac/scoped_nsobject.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
 
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
 namespace chrome {
 
 // Creates the main menu bar using the name specified in |product_name|, and
@@ -126,7 +129,7 @@
   }
 
   // Builds a NSMenuItem instance from the properties set on the Builder.
-  base::scoped_nsobject<NSMenuItem> Build() const;
+  NSMenuItem* Build() const;
 
  private:
   bool is_separator_ = false;
@@ -136,10 +139,10 @@
 
   int tag_ = 0;
 
-  id target_ = nil;
+  id __strong target_ = nil;
   SEL action_ = nil;
 
-  NSString* key_equivalent_ = @"";
+  NSString* __strong key_equivalent_ = @"";
   NSEventModifierFlags key_equivalent_flags_ = 0;
 
   bool is_alternate_ = false;
diff --git a/chrome/browser/ui/cocoa/main_menu_builder.mm b/chrome/browser/ui/cocoa/main_menu_builder.mm
index b3c5b07b0..710d4da0 100644
--- a/chrome/browser/ui/cocoa/main_menu_builder.mm
+++ b/chrome/browser/ui/cocoa/main_menu_builder.mm
@@ -22,17 +22,20 @@
 #import "ui/base/l10n/l10n_util_mac.h"
 #include "ui/strings/grit/ui_strings.h"
 
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
 namespace chrome {
 namespace {
 
 using Item = internal::MenuItemBuilder;
 
-base::scoped_nsobject<NSMenuItem> BuildAppMenu(
-    NSApplication* nsapp,
-    id app_delegate,
-    const std::u16string& product_name,
-    bool is_pwa) {
-  base::scoped_nsobject<NSMenuItem> item =
+NSMenuItem* BuildAppMenu(NSApplication* nsapp,
+                         id app_delegate,
+                         const std::u16string& product_name,
+                         bool is_pwa) {
+  NSMenuItem* item =
       // NB: The IDS_APP_MENU_PRODUCT_NAME string is not actually used to
       // determine what is displayed in bold in the menu bar as the app menu
       // title. The Info.plist's CFBundleName value is what is actually used.
@@ -89,20 +92,19 @@
           })
           .Build();
 
-  NSMenuItem* services_item = [[item submenu] itemWithTag:-1];
+  NSMenuItem* services_item = [item.submenu itemWithTag:-1];
   [services_item setTag:0];
 
-  [nsapp setServicesMenu:[services_item submenu]];
+  nsapp.servicesMenu = services_item.submenu;
 
   return item;
 }
 
-base::scoped_nsobject<NSMenuItem> BuildFileMenu(
-    NSApplication* nsapp,
-    id app_delegate,
-    const std::u16string& product_name,
-    bool is_pwa) {
-  base::scoped_nsobject<NSMenuItem> item =
+NSMenuItem* BuildFileMenu(NSApplication* nsapp,
+                          id app_delegate,
+                          const std::u16string& product_name,
+                          bool is_pwa) {
+  NSMenuItem* item =
       Item(IDS_FILE_MENU_MAC)
           .tag(IDC_FILE_MENU)
           .submenu({
@@ -143,12 +145,11 @@
   return item;
 }
 
-base::scoped_nsobject<NSMenuItem> BuildEditMenu(
-    NSApplication* nsapp,
-    id app_delegate,
-    const std::u16string& product_name,
-    bool is_pwa) {
-  base::scoped_nsobject<NSMenuItem> item =
+NSMenuItem* BuildEditMenu(NSApplication* nsapp,
+                          id app_delegate,
+                          const std::u16string& product_name,
+                          bool is_pwa) {
+  NSMenuItem* item =
       Item(IDS_EDIT_MENU_MAC)
           .tag(IDC_EDIT_MENU)
           .submenu({
@@ -244,12 +245,11 @@
   return item;
 }
 
-base::scoped_nsobject<NSMenuItem> BuildViewMenu(
-    NSApplication* nsapp,
-    id app_delegate,
-    const std::u16string& product_name,
-    bool is_pwa) {
-  base::scoped_nsobject<NSMenuItem> item =
+NSMenuItem* BuildViewMenu(NSApplication* nsapp,
+                          id app_delegate,
+                          const std::u16string& product_name,
+                          bool is_pwa) {
+  NSMenuItem* item =
       Item(IDS_VIEW_MENU_MAC)
           .tag(IDC_VIEW_MENU)
           .submenu({
@@ -311,12 +311,11 @@
   return item;
 }
 
-base::scoped_nsobject<NSMenuItem> BuildHistoryMenu(
-    NSApplication* nsapp,
-    id app_delegate,
-    const std::u16string& product_name,
-    bool is_pwa) {
-  base::scoped_nsobject<NSMenuItem> item =
+NSMenuItem* BuildHistoryMenu(NSApplication* nsapp,
+                             id app_delegate,
+                             const std::u16string& product_name,
+                             bool is_pwa) {
+  NSMenuItem* item =
       Item(IDS_HISTORY_MENU_MAC)
           .tag(IDC_HISTORY_MENU)
           .submenu({
@@ -349,15 +348,14 @@
   return item;
 }
 
-base::scoped_nsobject<NSMenuItem> BuildBookmarksMenu(
-    NSApplication* nsapp,
-    id app_delegate,
-    const std::u16string& product_name,
-    bool is_pwa) {
+NSMenuItem* BuildBookmarksMenu(NSApplication* nsapp,
+                               id app_delegate,
+                               const std::u16string& product_name,
+                               bool is_pwa) {
   if (is_pwa)
-    return base::scoped_nsobject<NSMenuItem>();
+    return nil;
 
-  base::scoped_nsobject<NSMenuItem> item =
+  NSMenuItem* item =
       Item(IDS_BOOKMARKS_MENU)
           .tag(IDC_BOOKMARKS_MENU)
           .submenu({
@@ -371,24 +369,22 @@
   return item;
 }
 
-base::scoped_nsobject<NSMenuItem> BuildPeopleMenu(
-    NSApplication* nsapp,
-    id app_delegate,
-    const std::u16string& product_name,
-    bool is_pwa) {
-  base::scoped_nsobject<NSMenuItem> item = Item(IDS_PROFILES_MENU_NAME)
-                                               .tag(IDC_PROFILE_MAIN_MENU)
-                                               .submenu({})
-                                               .Build();
+NSMenuItem* BuildPeopleMenu(NSApplication* nsapp,
+                            id app_delegate,
+                            const std::u16string& product_name,
+                            bool is_pwa) {
+  NSMenuItem* item = Item(IDS_PROFILES_MENU_NAME)
+                         .tag(IDC_PROFILE_MAIN_MENU)
+                         .submenu({})
+                         .Build();
   return item;
 }
 
-base::scoped_nsobject<NSMenuItem> BuildWindowMenu(
-    NSApplication* nsapp,
-    id app_delegate,
-    const std::u16string& product_name,
-    bool is_pwa) {
-  base::scoped_nsobject<NSMenuItem> item =
+NSMenuItem* BuildWindowMenu(NSApplication* nsapp,
+                            id app_delegate,
+                            const std::u16string& product_name,
+                            bool is_pwa) {
+  NSMenuItem* item =
       Item(IDS_WINDOW_MENU_MAC)
           .tag(IDC_WINDOW_MENU)
           .submenu({
@@ -426,15 +422,14 @@
   return item;
 }
 
-base::scoped_nsobject<NSMenuItem> BuildTabMenu(
-    NSApplication* nsapp,
-    id app_delegate,
-    const std::u16string& product_name,
-    bool is_pwa) {
+NSMenuItem* BuildTabMenu(NSApplication* nsapp,
+                         id app_delegate,
+                         const std::u16string& product_name,
+                         bool is_pwa) {
   if (is_pwa)
-    return base::scoped_nsobject<NSMenuItem>();
+    return nil;
 
-  base::scoped_nsobject<NSMenuItem> item =
+  NSMenuItem* item =
       Item(IDS_TAB_MENU_MAC)
           .tag(IDC_TAB_MENU)
           .submenu({
@@ -475,15 +470,14 @@
   return item;
 }
 
-base::scoped_nsobject<NSMenuItem> BuildHelpMenu(
-    NSApplication* nsapp,
-    id app_delegate,
-    const std::u16string& product_name,
-    bool is_pwa) {
+NSMenuItem* BuildHelpMenu(NSApplication* nsapp,
+                          id app_delegate,
+                          const std::u16string& product_name,
+                          bool is_pwa) {
   if (is_pwa)
-    return base::scoped_nsobject<NSMenuItem>();
+    return nil;
 
-  base::scoped_nsobject<NSMenuItem> item =
+  NSMenuItem* item =
       Item(IDS_HELP_MENU_MAC)
           .submenu({
 #if BUILDFLAG(GOOGLE_CHROME_BRANDING)
@@ -504,16 +498,19 @@
                    id<NSApplicationDelegate> app_delegate,
                    const std::u16string& product_name,
                    bool is_pwa) {
-  base::scoped_nsobject<NSMenu> main_menu([[NSMenu alloc] initWithTitle:@""]);
-
-  using Builder = base::scoped_nsobject<NSMenuItem> (*)(
-      NSApplication*, id, const std::u16string&, bool);
-  static const Builder kBuilderFuncs[] = {
-      &BuildAppMenu,     &BuildFileMenu,      &BuildEditMenu,   &BuildViewMenu,
-      &BuildHistoryMenu, &BuildBookmarksMenu, &BuildPeopleMenu, &BuildTabMenu,
-      &BuildWindowMenu,  &BuildHelpMenu,
-  };
-  for (auto* builder : kBuilderFuncs) {
+  NSMenu* main_menu = [[NSMenu alloc] initWithTitle:@""];
+  for (auto* builder : {
+           &BuildAppMenu,
+           &BuildFileMenu,
+           &BuildEditMenu,
+           &BuildViewMenu,
+           &BuildHistoryMenu,
+           &BuildBookmarksMenu,
+           &BuildPeopleMenu,
+           &BuildTabMenu,
+           &BuildWindowMenu,
+           &BuildHelpMenu,
+       }) {
     auto item = builder(nsapp, app_delegate, product_name, is_pwa);
     if (item)
       [main_menu addItem:item];
@@ -532,12 +529,12 @@
 
 MenuItemBuilder::~MenuItemBuilder() = default;
 
-base::scoped_nsobject<NSMenuItem> MenuItemBuilder::Build() const {
+NSMenuItem* MenuItemBuilder::Build() const {
   if (is_removed_)
-    return base::scoped_nsobject<NSMenuItem>();
+    return nil;
 
   if (is_separator_) {
-    base::scoped_nsobject<NSMenuItem> item([[NSMenuItem separatorItem] retain]);
+    NSMenuItem* item = [NSMenuItem separatorItem];
     if (tag_) {
       [item setTag:tag_];
     }
@@ -568,20 +565,19 @@
 
   SEL action = !submenu_.has_value() ? action_ : nil;
 
-  base::scoped_nsobject<NSMenuItem> item([[NSMenuItem alloc]
-      initWithTitle:title
-             action:action
-      keyEquivalent:key_equivalent]);
-  [item setTarget:target_];
-  [item setTag:tag_];
-  [item setKeyEquivalentModifierMask:key_equivalent_flags];
-  [item setAlternate:is_alternate_];
-  [item setHidden:is_hidden_];
+  NSMenuItem* item = [[NSMenuItem alloc] initWithTitle:title
+                                                action:action
+                                         keyEquivalent:key_equivalent];
+  item.target = target_;
+  item.tag = tag_;
+  item.keyEquivalentModifierMask = key_equivalent_flags;
+  item.alternate = is_alternate_;
+  item.hidden = is_hidden_;
 
   if (submenu_.has_value()) {
-    base::scoped_nsobject<NSMenu> menu([[NSMenu alloc] initWithTitle:title]);
+    NSMenu* menu = [[NSMenu alloc] initWithTitle:title];
     for (const auto& subitem : submenu_.value()) {
-      base::scoped_nsobject<NSMenuItem> ns_subitem = subitem.Build();
+      NSMenuItem* ns_subitem = subitem.Build();
       if (ns_subitem)
         [menu addItem:ns_subitem];
     }
diff --git a/chrome/browser/ui/cocoa/main_menu_builder_unittest.mm b/chrome/browser/ui/cocoa/main_menu_builder_unittest.mm
index 0f317c7..0cf7207 100644
--- a/chrome/browser/ui/cocoa/main_menu_builder_unittest.mm
+++ b/chrome/browser/ui/cocoa/main_menu_builder_unittest.mm
@@ -13,26 +13,28 @@
 #include "ui/base/l10n/l10n_util.h"
 #include "ui/base/l10n/l10n_util_mac.h"
 
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
 namespace {
 
 using chrome::internal::MenuItemBuilder;
 
 TEST(MainMenuBuilderTest, Separator) {
-  base::scoped_nsobject<NSMenuItem> item =
-      MenuItemBuilder().is_separator().Build();
+  NSMenuItem* item = MenuItemBuilder().is_separator().Build();
   EXPECT_TRUE([item isSeparatorItem]);
   EXPECT_EQ(0, [item tag]);
 }
 
 TEST(MainMenuBuilderTest, SeparatorWithTag) {
-  base::scoped_nsobject<NSMenuItem> item =
-      MenuItemBuilder().is_separator().tag(999).Build();
+  NSMenuItem* item = MenuItemBuilder().is_separator().tag(999).Build();
   EXPECT_TRUE([item isSeparatorItem]);
   EXPECT_EQ(999, [item tag]);
 }
 
 TEST(MainMenuBuilderTest, CommandId) {
-  base::scoped_nsobject<NSMenuItem> item =
+  NSMenuItem* item =
       MenuItemBuilder(IDS_NEW_TAB).command_id(IDC_NEW_TAB).Build();
   EXPECT_EQ(@selector(commandDispatch:), [item action]);
   EXPECT_FALSE([item target]);
@@ -43,21 +45,21 @@
 }
 
 TEST(MainMenuBuilderTest, CustomTargetAction) {
-  base::scoped_nsobject<NSObject> target([[NSObject alloc] init]);
+  NSObject* target = [[NSObject alloc] init];
 
-  base::scoped_nsobject<NSMenuItem> item = MenuItemBuilder(IDS_PREFERENCES)
-                                               .target(target)
-                                               .action(@selector(fooBar:))
-                                               .Build();
+  NSMenuItem* item = MenuItemBuilder(IDS_PREFERENCES)
+                         .target(target)
+                         .action(@selector(fooBar:))
+                         .Build();
   EXPECT_NSEQ(l10n_util::GetNSStringWithFixup(IDS_PREFERENCES), [item title]);
 
-  EXPECT_EQ(target.get(), [item target]);
+  EXPECT_EQ(target, [item target]);
   EXPECT_EQ(@selector(fooBar:), [item action]);
   EXPECT_EQ(0, [item tag]);
 }
 
 TEST(MainMenuBuilderTest, Submenu) {
-  base::scoped_nsobject<NSMenuItem> item =
+  NSMenuItem* item =
       MenuItemBuilder(IDS_EDIT)
           .tag(123)
           .submenu({
@@ -89,14 +91,13 @@
 }
 
 TEST(MainMenuBuilderTest, StringId) {
-  base::scoped_nsobject<NSMenuItem> item =
-      MenuItemBuilder(IDS_NEW_TAB_MAC).Build();
+  NSMenuItem* item = MenuItemBuilder(IDS_NEW_TAB_MAC).Build();
   EXPECT_NSEQ(l10n_util::GetNSStringWithFixup(IDS_NEW_TAB_MAC), [item title]);
 }
 
 TEST(MainMenuBuilderTest, StringIdWithArg) {
   std::u16string product_name(u"MyAppIsTotallyAwesome");
-  base::scoped_nsobject<NSMenuItem> item =
+  NSMenuItem* item =
       MenuItemBuilder(IDS_ABOUT_MAC).string_format_1(product_name).Build();
 
   EXPECT_NSEQ(l10n_util::GetNSStringF(IDS_ABOUT_MAC, product_name),
@@ -104,17 +105,15 @@
 }
 
 TEST(MainMenuBuilderTest, Disabled) {
-  base::scoped_nsobject<NSMenuItem> item =
-      MenuItemBuilder(IDS_NEW_TAB_MAC).remove_if(true).Build();
-  EXPECT_EQ(nil, item.get());
+  NSMenuItem* item = MenuItemBuilder(IDS_NEW_TAB_MAC).remove_if(true).Build();
+  EXPECT_EQ(nil, item);
 
   item = MenuItemBuilder(IDS_NEW_TAB_MAC).remove_if(false).Build();
   EXPECT_NSEQ(l10n_util::GetNSStringWithFixup(IDS_NEW_TAB_MAC), [item title]);
 }
 
 TEST(MainMenuBuilderTest, Hidden) {
-  base::scoped_nsobject<NSMenuItem> item =
-      MenuItemBuilder(IDS_NEW_TAB_MAC).set_hidden(true).Build();
+  NSMenuItem* item = MenuItemBuilder(IDS_NEW_TAB_MAC).set_hidden(true).Build();
   EXPECT_EQ(true, [item isHidden]);
 }
 
diff --git a/chrome/browser/ui/cocoa/profiles/profile_menu_controller_unittest.mm b/chrome/browser/ui/cocoa/profiles/profile_menu_controller_unittest.mm
index cbe16bf..f5d3d4d 100644
--- a/chrome/browser/ui/cocoa/profiles/profile_menu_controller_unittest.mm
+++ b/chrome/browser/ui/cocoa/profiles/profile_menu_controller_unittest.mm
@@ -4,7 +4,6 @@
 
 #import "chrome/browser/ui/cocoa/profiles/profile_menu_controller.h"
 
-#include "base/mac/scoped_nsobject.h"
 #include "base/strings/sys_string_conversions.h"
 #include "base/threading/thread_restrictions.h"
 #include "chrome/browser/profiles/profile_attributes_entry.h"
@@ -23,6 +22,10 @@
 #include "testing/gtest_mac.h"
 #include "ui/base/l10n/l10n_util_mac.h"
 
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
 class ProfileMenuControllerTest : public BrowserWithTestWindowTest {
  public:
   ProfileMenuControllerTest() { RebuildController(); }
@@ -36,11 +39,10 @@
   }
 
   void RebuildController() {
-    item_.reset([[NSMenuItem alloc] initWithTitle:@"Users"
-                                           action:nil
-                                    keyEquivalent:@""]);
-    controller_.reset(
-        [[ProfileMenuController alloc] initWithMainMenuItem:item_]);
+    item_ = [[NSMenuItem alloc] initWithTitle:@"Users"
+                                       action:nil
+                                keyEquivalent:@""];
+    controller_ = [[ProfileMenuController alloc] initWithMainMenuItem:item_];
   }
 
   void TestBottomItems() {
@@ -76,13 +78,13 @@
     }
   }
 
-  ProfileMenuController* controller() { return controller_.get(); }
+  ProfileMenuController* controller() { return controller_; }
 
-  NSMenuItem* menu_item() { return item_.get(); }
+  NSMenuItem* menu_item() { return item_; }
 
  private:
-  base::scoped_nsobject<NSMenuItem> item_;
-  base::scoped_nsobject<ProfileMenuController> controller_;
+  NSMenuItem* __strong item_;
+  ProfileMenuController* __strong controller_;
 };
 
 TEST_F(ProfileMenuControllerTest, InitializeMenu) {
@@ -137,7 +139,7 @@
 }
 
 TEST_F(ProfileMenuControllerTest, InsertItems) {
-  base::scoped_nsobject<NSMenu> menu([[NSMenu alloc] initWithTitle:@""]);
+  NSMenu* menu = [[NSMenu alloc] initWithTitle:@""];
   ASSERT_EQ(0, [menu numberOfItems]);
 
   // Even with one profile items can still be inserted.
@@ -194,7 +196,7 @@
 }
 
 TEST_F(ProfileMenuControllerTest, InitialActiveBrowser) {
-  [controller() activeBrowserChangedTo:NULL];
+  [controller() activeBrowserChangedTo:nullptr];
   VerifyProfileNamedIsActive(l10n_util::GetNSString(IDS_DEFAULT_PROFILE_NAME),
                              __LINE__);
 }
@@ -276,7 +278,7 @@
   // Simulate the active browser changing to NULL and ensure a profile doesn't
   // get created by disallowing IO operations temporarily.
   base::ScopedDisallowBlocking scoped_disallow_blocking;
-  [controller() activeBrowserChangedTo:NULL];
+  [controller() activeBrowserChangedTo:nullptr];
   // Check that validateMenuItem does not load a profile, and edit is disabled.
   // Adding a new profile is still possible since this happens through the
   // profile picker.
diff --git a/chrome/browser/ui/cocoa/renderer_context_menu/render_view_context_menu_mac_unittest.mm b/chrome/browser/ui/cocoa/renderer_context_menu/render_view_context_menu_mac_unittest.mm
index c782062..eb15537 100644
--- a/chrome/browser/ui/cocoa/renderer_context_menu/render_view_context_menu_mac_unittest.mm
+++ b/chrome/browser/ui/cocoa/renderer_context_menu/render_view_context_menu_mac_unittest.mm
@@ -14,6 +14,10 @@
 #include "testing/gtest/include/gtest/gtest.h"
 #include "ui/base/cocoa/text_services_context_menu.h"
 
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
 namespace {
 
 class RenderViewContextMenuMacTest : public testing::Test {
diff --git a/chrome/browser/ui/cocoa/tab_menu_bridge_unittest.mm b/chrome/browser/ui/cocoa/tab_menu_bridge_unittest.mm
index aced6102..828021bf 100644
--- a/chrome/browser/ui/cocoa/tab_menu_bridge_unittest.mm
+++ b/chrome/browser/ui/cocoa/tab_menu_bridge_unittest.mm
@@ -6,7 +6,6 @@
 
 #import <Cocoa/Cocoa.h>
 
-#include "base/mac/scoped_nsobject.h"
 #include "base/strings/sys_string_conversions.h"
 #include "base/strings/utf_string_conversions.h"
 #include "base/test/task_environment.h"
@@ -21,6 +20,10 @@
 #include "content/public/test/web_contents_tester.h"
 #include "testing/gtest_mac.h"
 
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
 constexpr int kStaticItemCount = 4;
 
 class TabStripModelUiHelperDelegate : public TestTabStripModelDelegate {
@@ -40,17 +43,17 @@
     rvh_test_enabler_ = std::make_unique<content::RenderViewHostTestEnabler>();
     delegate_ = std::make_unique<TabStripModelUiHelperDelegate>();
     model_ = std::make_unique<TabStripModel>(delegate_.get(), nullptr);
-    menu_root_.reset(ItemWithTitle(@"Tab"));
-    menu_.reset([[NSMenu alloc] initWithTitle:@"Tab"]);
-    menu_root_.get().submenu = menu_.get();
+    menu_root_ = ItemWithTitle(@"Tab");
+    menu_ = [[NSMenu alloc] initWithTitle:@"Tab"];
+    menu_root_.submenu = menu_;
 
-    AddStaticItems(menu_.get());
+    AddStaticItems(menu_);
   }
 
   void TearDown() override { model_->CloseAllTabs(); }
 
-  NSMenuItem* menu_root() { return menu_root_.get(); }
-  NSMenu* menu() { return menu_.get(); }
+  NSMenuItem* menu_root() { return menu_root_; }
+  NSMenu* menu() { return menu_; }
   TabStripModel* model() { return model_.get(); }
   TabStripModelDelegate* delegate() { return delegate_.get(); }
 
@@ -172,8 +175,8 @@
   std::unique_ptr<content::RenderViewHostTestEnabler> rvh_test_enabler_;
   std::unique_ptr<TabStripModelUiHelperDelegate> delegate_;
   std::unique_ptr<TabStripModel> model_;
-  base::scoped_nsobject<NSMenuItem> menu_root_;
-  base::scoped_nsobject<NSMenu> menu_;
+  NSMenuItem* __strong menu_root_;
+  NSMenu* __strong menu_;
 };
 
 TEST_F(TabMenuBridgeTest, CreatesBlankMenu) {
diff --git a/chrome/browser/ui/cocoa/test/run_loop_testing_unittest.mm b/chrome/browser/ui/cocoa/test/run_loop_testing_unittest.mm
index 79a21d7..eb762e43 100644
--- a/chrome/browser/ui/cocoa/test/run_loop_testing_unittest.mm
+++ b/chrome/browser/ui/cocoa/test/run_loop_testing_unittest.mm
@@ -6,16 +6,15 @@
 
 #import <Foundation/Foundation.h>
 
-#include "base/mac/scoped_nsobject.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
-@interface TestDelayed : NSObject {
- @private
-  BOOL _didWork;
-  TestDelayed* _next;
-}
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
+@interface TestDelayed : NSObject
 @property(readonly, nonatomic) BOOL didWork;
-@property(assign, nonatomic) TestDelayed* next;
+@property(strong, nonatomic) TestDelayed* next;
 @end
 
 @implementation TestDelayed
@@ -36,7 +35,7 @@
 @end
 
 TEST(RunLoopTesting, RunAllPending) {
-  base::scoped_nsobject<TestDelayed> tester([[TestDelayed alloc] init]);
+  TestDelayed* tester = [[TestDelayed alloc] init];
   EXPECT_FALSE([tester didWork]);
 
   chrome::testing::NSRunLoopRunAllPending();
@@ -45,8 +44,8 @@
 }
 
 TEST(RunLoopTesting, NestedWork) {
-  base::scoped_nsobject<TestDelayed> tester([[TestDelayed alloc] init]);
-  base::scoped_nsobject<TestDelayed> nested([[TestDelayed alloc] init]);
+  TestDelayed* tester = [[TestDelayed alloc] init];
+  TestDelayed* nested = [[TestDelayed alloc] init];
   [tester setNext:nested];
 
   EXPECT_FALSE([tester didWork]);
diff --git a/chrome/browser/ui/cocoa/window_size_autosaver.h b/chrome/browser/ui/cocoa/window_size_autosaver.h
index fb83834c9..03c8508b 100644
--- a/chrome/browser/ui/cocoa/window_size_autosaver.h
+++ b/chrome/browser/ui/cocoa/window_size_autosaver.h
@@ -12,8 +12,7 @@
 
 // WindowSizeAutosaver is a helper class that makes it easy to let windows
 // autoremember their position or position and size in a PrefService object.
-// To use this, add a |base::scoped_nsobject<WindowSizeAutosaver>| to your
-// window
+// To use this, add a |WindowSizeAutosaver* __strong| to your window
 // controller and initialize it in the window controller's init method, passing
 // a window and an autosave name. The autosaver will register for "window moved"
 // and "window resized" notifications and write the current window state to the
@@ -34,4 +33,3 @@
 @end
 
 #endif  // CHROME_BROWSER_UI_COCOA_WINDOW_SIZE_AUTOSAVER_H_
-
diff --git a/chrome/browser/ui/cocoa/window_size_autosaver_unittest.mm b/chrome/browser/ui/cocoa/window_size_autosaver_unittest.mm
index 2b12ee8..5ddcc70 100644
--- a/chrome/browser/ui/cocoa/window_size_autosaver_unittest.mm
+++ b/chrome/browser/ui/cocoa/window_size_autosaver_unittest.mm
@@ -6,7 +6,6 @@
 
 #import "chrome/browser/ui/cocoa/window_size_autosaver.h"
 
-#include "base/mac/scoped_nsobject.h"
 #include "chrome/browser/ui/cocoa/test/cocoa_test_helper.h"
 #include "chrome/test/base/browser_with_test_window_test.h"
 #include "chrome/test/base/testing_profile.h"
@@ -16,8 +15,14 @@
 #include "testing/gtest/include/gtest/gtest.h"
 #include "testing/platform_test.h"
 
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
 namespace {
 
+constexpr char kPath[] = "WindowSizeAutosaverTest";
+
 class WindowSizeAutosaverTest : public BrowserWithTestWindowTest {
   void SetUp() override {
     BrowserWithTestWindowTest::SetUp();
@@ -26,9 +31,10 @@
                   styleMask:NSWindowStyleMaskTitled | NSWindowStyleMaskResizable
                     backing:NSBackingStoreBuffered
                       defer:NO];
+    window_.releasedWhenClosed = NO;
     static_cast<user_prefs::PrefRegistrySyncable*>(
         profile()->GetPrefs()->DeprecatedGetPrefRegistry())
-        ->RegisterDictionaryPref(path_);
+        ->RegisterDictionaryPref(kPath);
   }
 
   void TearDown() override {
@@ -38,8 +44,7 @@
 
  public:
   CocoaTestHelper cocoa_test_helper_;
-  NSWindow* window_;
-  const char* path_ = "WindowSizeAutosaverTest";
+  NSWindow* __strong window_;
 };
 
 TEST_F(WindowSizeAutosaverTest, RestoresAndSavesPos) {
@@ -47,7 +52,7 @@
   ASSERT_TRUE(pref);
 
   // Check to make sure there is no existing pref for window placement.
-  const base::Value::Dict& placement = pref->GetDict(path_);
+  const base::Value::Dict& placement = pref->GetDict(kPath);
   EXPECT_TRUE(placement.empty());
 
   // Replace the window with one that doesn't have resize controls.
@@ -56,6 +61,7 @@
                                         styleMask:NSWindowStyleMaskTitled
                                           backing:NSBackingStoreBuffered
                                             defer:NO];
+  window_.releasedWhenClosed = NO;
 
   // Ask the window to save its position, then check that a preference
   // exists.  We're technically passing in a pointer to the user prefs
@@ -65,10 +71,10 @@
   {
     NSRect frame = [window_ frame];
     // Empty state, shouldn't restore:
-    base::scoped_nsobject<WindowSizeAutosaver> sizeSaver(
+    [[maybe_unused]] WindowSizeAutosaver* sizeSaver =
         [[WindowSizeAutosaver alloc] initWithWindow:window_
                                         prefService:pref
-                                               path:path_]);
+                                               path:kPath];
     EXPECT_EQ(NSMinX(frame), NSMinX([window_ frame]));
     EXPECT_EQ(NSMinY(frame), NSMinY([window_ frame]));
     EXPECT_EQ(NSWidth(frame), NSWidth([window_ frame]));
@@ -83,10 +89,10 @@
 
   {
     // Should restore last stored position, but not size.
-    base::scoped_nsobject<WindowSizeAutosaver> sizeSaver(
+    [[maybe_unused]] WindowSizeAutosaver* sizeSaver =
         [[WindowSizeAutosaver alloc] initWithWindow:window_
                                         prefService:pref
-                                               path:path_]);
+                                               path:kPath];
     EXPECT_EQ(300, NSMinX([window_ frame]));
     EXPECT_EQ(310, NSMinY([window_ frame]));
     EXPECT_EQ(160, NSWidth([window_ frame]));
@@ -94,7 +100,7 @@
   }
 
   // ...and it should be in the profile, too.
-  const base::Value::Dict& windowPref = pref->GetDict(path_);
+  const base::Value::Dict& windowPref = pref->GetDict(kPath);
   EXPECT_FALSE(windowPref.FindInt("left").has_value());
   EXPECT_FALSE(windowPref.FindInt("right").has_value());
   EXPECT_FALSE(windowPref.FindInt("top").has_value());
@@ -112,7 +118,7 @@
   ASSERT_TRUE(pref);
 
   // Check to make sure there is no existing pref for window placement.
-  const base::Value::Dict& placement = pref->GetDict(path_);
+  const base::Value::Dict& placement = pref->GetDict(kPath);
   EXPECT_TRUE(placement.empty());
 
   // Ask the window to save its position, then check that a preference
@@ -123,10 +129,10 @@
   {
     NSRect frame = [window_ frame];
     // Empty state, shouldn't restore:
-    base::scoped_nsobject<WindowSizeAutosaver> sizeSaver(
+    [[maybe_unused]] WindowSizeAutosaver* sizeSaver =
         [[WindowSizeAutosaver alloc] initWithWindow:window_
                                         prefService:pref
-                                               path:path_]);
+                                               path:kPath];
     EXPECT_EQ(NSMinX(frame), NSMinX([window_ frame]));
     EXPECT_EQ(NSMinY(frame), NSMinY([window_ frame]));
     EXPECT_EQ(NSWidth(frame), NSWidth([window_ frame]));
@@ -141,10 +147,10 @@
 
   {
     // Should restore last stored size
-    base::scoped_nsobject<WindowSizeAutosaver> sizeSaver(
+    [[maybe_unused]] WindowSizeAutosaver* sizeSaver =
         [[WindowSizeAutosaver alloc] initWithWindow:window_
                                         prefService:pref
-                                               path:path_]);
+                                               path:kPath];
     EXPECT_EQ(300, NSMinX([window_ frame]));
     EXPECT_EQ(310, NSMinY([window_ frame]));
     EXPECT_EQ(250, NSWidth([window_ frame]));
@@ -152,7 +158,7 @@
   }
 
   // ...and it should be in the profile, too.
-  const base::Value::Dict& windowPref = pref->GetDict(path_);
+  const base::Value::Dict& windowPref = pref->GetDict(kPath);
   EXPECT_FALSE(windowPref.FindInt("x").has_value());
   EXPECT_FALSE(windowPref.FindInt("y").has_value());
   absl::optional<int> x1 = windowPref.FindInt("left");
@@ -174,7 +180,7 @@
   PrefService* pref = profile()->GetPrefs();
   ASSERT_TRUE(pref);
 
-  ScopedDictPrefUpdate update(pref, path_);
+  ScopedDictPrefUpdate update(pref, kPath);
   base::Value::Dict& windowPref = update.Get();
   windowPref.Set("left", 50);
   windowPref.Set("right", 50);
@@ -184,10 +190,10 @@
   {
     // Window rect shouldn't change...
     NSRect frame = [window_ frame];
-    base::scoped_nsobject<WindowSizeAutosaver> sizeSaver(
+    [[maybe_unused]] WindowSizeAutosaver* sizeSaver =
         [[WindowSizeAutosaver alloc] initWithWindow:window_
                                         prefService:pref
-                                               path:path_]);
+                                               path:kPath];
     EXPECT_EQ(NSMinX(frame), NSMinX([window_ frame]));
     EXPECT_EQ(NSMinY(frame), NSMinY([window_ frame]));
     EXPECT_EQ(NSWidth(frame), NSWidth([window_ frame]));
diff --git a/chrome/browser/ui/color/chrome_color_id.h b/chrome/browser/ui/color/chrome_color_id.h
index 786a4e8..5522132ad2 100644
--- a/chrome/browser/ui/color/chrome_color_id.h
+++ b/chrome/browser/ui/color/chrome_color_id.h
@@ -358,6 +358,9 @@
   E_CPONLY(kColorSidePanelBadgeBackgroundUpdated) \
   E_CPONLY(kColorSidePanelBadgeForeground) \
   E_CPONLY(kColorSidePanelBadgeForegroundUpdated) \
+  E_CPONLY(kColorSidePanelBookmarksSelectedFolderBackground) \
+  E_CPONLY(kColorSidePanelBookmarksSelectedFolderForeground) \
+  E_CPONLY(kColorSidePanelBookmarksSelectedFolderIcon) \
   E_CPONLY(kColorSidePanelCardBackground) \
   E_CPONLY(kColorSidePanelCardPrimaryForeground) \
   E_CPONLY(kColorSidePanelCardSecondaryForeground) \
@@ -373,6 +376,10 @@
   E_CPONLY(kColorSidePanelCustomizeChromeThemeCheckmarkForeground) \
   E_CPONLY(kColorSidePanelCustomizeChromeThemeSnapshotBackground) \
   E_CPONLY(kColorSidePanelCustomizeChromeWebStoreOptionBorder) \
+  E_CPONLY(kColorSidePanelDialogBackground) \
+  E_CPONLY(kColorSidePanelDialogDivider) \
+  E_CPONLY(kColorSidePanelDialogPrimaryForeground) \
+  E_CPONLY(kColorSidePanelDialogSecondaryForeground) \
   E_CPONLY(kColorSidePanelDivider) \
   E_CPONLY(kColorSidePanelEditFooterBorder) \
   E_CPONLY(kColorSidePanelFilterChipBorder) \
diff --git a/chrome/browser/ui/color/material_side_panel_color_mixer.cc b/chrome/browser/ui/color/material_side_panel_color_mixer.cc
index ea951eb..fda7a67f 100644
--- a/chrome/browser/ui/color/material_side_panel_color_mixer.cc
+++ b/chrome/browser/ui/color/material_side_panel_color_mixer.cc
@@ -21,6 +21,13 @@
       ui::kColorSysOnSurfaceSubtle};
   mixer[kColorSidePanelDivider] = {ui::kColorSysDivider};
 
+  /* Dialogs within the side panel. */
+  mixer[kColorSidePanelDialogBackground] = {ui::kColorSysSurface};
+  mixer[kColorSidePanelDialogDivider] = {ui::kColorSysNeutralOutline};
+  mixer[kColorSidePanelDialogPrimaryForeground] = {ui::kColorSysOnSurface};
+  mixer[kColorSidePanelDialogSecondaryForeground] = {
+      ui::kColorSysOnSurfaceSubtle};
+
   mixer[kColorSidePanelBadgeBackground] = {ui::kColorSysNeutralContainer};
   mixer[kColorSidePanelBadgeBackgroundUpdated] = {
       ui::kColorSysTertiaryContainer};
@@ -44,6 +51,14 @@
 
   mixer[kColorSidePanelTextfieldBorder] = {ui::kColorSysNeutralOutline};
 
+  /* Bookmarks */
+  mixer[kColorSidePanelBookmarksSelectedFolderBackground] = {
+      ui::kColorSysStateRipplePrimary};
+  mixer[kColorSidePanelBookmarksSelectedFolderForeground] = {
+      ui::kColorSysOnSurface};
+  mixer[kColorSidePanelBookmarksSelectedFolderIcon] = {
+      ui::kColorSysOnSurfaceSubtle};
+
   /* Customize Chrome */
   mixer[kColorSidePanelCustomizeChromeColorPickerCheckmarkBackground] = {
       ui::kColorSysOnSurface};
diff --git a/chrome/browser/ui/commerce/price_tracking/mock_shopping_list_ui_tab_helper.cc b/chrome/browser/ui/commerce/price_tracking/mock_shopping_list_ui_tab_helper.cc
index bf6b067..c64f44ae 100644
--- a/chrome/browser/ui/commerce/price_tracking/mock_shopping_list_ui_tab_helper.cc
+++ b/chrome/browser/ui/commerce/price_tracking/mock_shopping_list_ui_tab_helper.cc
@@ -9,6 +9,7 @@
 #include "third_party/skia/include/core/SkBitmap.h"
 #include "ui/gfx/image/image.h"
 #include "ui/gfx/image/image_skia.h"
+#include "ui/views/view.h"
 
 // static
 void MockShoppingListUiTabHelper::CreateForWebContents(
@@ -33,6 +34,10 @@
         base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
             FROM_HERE, base::BindOnce(std::move(callback), true));
       });
+
+  ON_CALL(*this, CreateShoppingInsightsWebView).WillByDefault([]() {
+    return std::make_unique<views::View>();
+  });
 }
 
 MockShoppingListUiTabHelper::~MockShoppingListUiTabHelper() = default;
diff --git a/chrome/browser/ui/commerce/price_tracking/mock_shopping_list_ui_tab_helper.h b/chrome/browser/ui/commerce/price_tracking/mock_shopping_list_ui_tab_helper.h
index 2a6f62e..c447e14f 100644
--- a/chrome/browser/ui/commerce/price_tracking/mock_shopping_list_ui_tab_helper.h
+++ b/chrome/browser/ui/commerce/price_tracking/mock_shopping_list_ui_tab_helper.h
@@ -12,6 +12,10 @@
 class WebContents;
 }  // namespace content
 
+namespace views {
+class View;
+}  // namespace views
+
 class MockShoppingListUiTabHelper : public commerce::ShoppingListUiTabHelper {
  public:
   static void CreateForWebContents(content::WebContents* content);
@@ -32,6 +36,10 @@
                bool is_new_bookmark,
                base::OnceCallback<void(bool)> callback),
               (override));
+  MOCK_METHOD(std::unique_ptr<views::View>,
+              CreateShoppingInsightsWebView,
+              (),
+              (override));
 
  private:
   gfx::Image valid_product_image_;
diff --git a/chrome/browser/ui/commerce/price_tracking/shopping_list_ui_tab_helper.cc b/chrome/browser/ui/commerce/price_tracking/shopping_list_ui_tab_helper.cc
index 8cd014a7..aef3765d 100644
--- a/chrome/browser/ui/commerce/price_tracking/shopping_list_ui_tab_helper.cc
+++ b/chrome/browser/ui/commerce/price_tracking/shopping_list_ui_tab_helper.cc
@@ -306,6 +306,15 @@
   }
 }
 
+void ShoppingListUiTabHelper::ShowShoppingInsightsSidePanel() {
+  auto* side_panel_ui = GetSidePanelUI();
+  auto* registry = SidePanelRegistry::Get(web_contents());
+  DCHECK(side_panel_ui && registry->GetEntryForKey(SidePanelEntry::Key(
+                              SidePanelEntry::Id::kShoppingInsights)));
+
+  side_panel_ui->Show(SidePanelEntryId::kShoppingInsights);
+}
+
 void ShoppingListUiTabHelper::UpdatePriceTrackingStateFromSubscriptions() {
   if (!cluster_id_for_page_.has_value())
     return;
diff --git a/chrome/browser/ui/commerce/price_tracking/shopping_list_ui_tab_helper.h b/chrome/browser/ui/commerce/price_tracking/shopping_list_ui_tab_helper.h
index 18e30d06..861fab66 100644
--- a/chrome/browser/ui/commerce/price_tracking/shopping_list_ui_tab_helper.h
+++ b/chrome/browser/ui/commerce/price_tracking/shopping_list_ui_tab_helper.h
@@ -90,6 +90,7 @@
   virtual void SetPriceTrackingState(bool enable,
                                      bool is_new_bookmark,
                                      base::OnceCallback<void(bool)> callback);
+  void ShowShoppingInsightsSidePanel();
 
  protected:
   ShoppingListUiTabHelper(content::WebContents* contents,
@@ -99,6 +100,8 @@
 
   const absl::optional<bool>& GetPendingTrackingStateForTesting();
 
+  virtual std::unique_ptr<views::View> CreateShoppingInsightsWebView();
+
  private:
   friend class content::WebContentsUserData<ShoppingListUiTabHelper>;
   friend class ShoppingListUiTabHelperTest;
@@ -134,8 +137,6 @@
   // first.
   void MakeShoppingInsightsSidePanelUnavailable();
 
-  std::unique_ptr<views::View> CreateShoppingInsightsWebView();
-
   SidePanelUI* GetSidePanelUI() const;
 
   // The shopping service is tied to the lifetime of the browser context
diff --git a/chrome/browser/ui/content_settings/content_setting_media_image_model_unittest.mm b/chrome/browser/ui/content_settings/content_setting_media_image_model_unittest.mm
index b0cdf8bd..a941434 100644
--- a/chrome/browser/ui/content_settings/content_setting_media_image_model_unittest.mm
+++ b/chrome/browser/ui/content_settings/content_setting_media_image_model_unittest.mm
@@ -31,6 +31,10 @@
 #include "ui/gfx/paint_vector_icon.h"
 #include "ui/gfx/vector_icon_types.h"
 
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
 namespace gfx {
 struct VectorIcon;
 }
@@ -109,12 +113,12 @@
     auth_wrapper.SetMockMediaPermissionStatus(kAllowed);
     content_setting_image_model->Update(web_contents());
     ExpectImageModelState(
-        *content_setting_image_model, true /*is_visible*/, true /*has_icon*/,
+        *content_setting_image_model, /*is_visible=*/true, /*has_icon=*/true,
         l10n_util::GetStringUTF16(IDS_CAMERA_ACCESSED), 0, &gfx::kNoneIcon);
     auth_wrapper.SetMockMediaPermissionStatus(kDenied);
     content_setting_image_model->Update(web_contents());
     ExpectImageModelState(
-        *content_setting_image_model, true /*is_visible*/, true /*has_icon*/,
+        *content_setting_image_model, /*is_visible=*/true, /*has_icon=*/true,
         l10n_util::GetStringUTF16(IDS_CAMERA_BLOCKED), IDS_CAMERA_TURNED_OFF,
         &vector_icons::kBlockedBadgeIcon);
     auth_wrapper.SetMockMediaPermissionStatus(kNotDetermined);
@@ -130,12 +134,12 @@
     auth_wrapper.SetMockMediaPermissionStatus(kAllowed);
     content_setting_image_model->Update(web_contents());
     ExpectImageModelState(
-        *content_setting_image_model, true /*is_visible*/, true /*has_icon*/,
+        *content_setting_image_model, /*is_visible=*/true, /*has_icon=*/true,
         l10n_util::GetStringUTF16(IDS_MICROPHONE_ACCESSED), 0, &gfx::kNoneIcon);
     auth_wrapper.SetMockMediaPermissionStatus(kDenied);
     content_setting_image_model->Update(web_contents());
-    ExpectImageModelState(*content_setting_image_model, true /*is_visible*/,
-                          true /*has_icon*/,
+    ExpectImageModelState(*content_setting_image_model, /*is_visible=*/true,
+                          /*has_icon=*/true,
                           l10n_util::GetStringUTF16(IDS_MICROPHONE_BLOCKED),
                           IDS_MIC_TURNED_OFF, &vector_icons::kBlockedBadgeIcon);
     auth_wrapper.SetMockMediaPermissionStatus(kNotDetermined);
@@ -154,14 +158,14 @@
     auth_wrapper.SetMockMediaPermissionStatus(kAllowed);
     content_setting_image_model->Update(web_contents());
     ExpectImageModelState(
-        *content_setting_image_model, true /*is_visible*/, true /*has_icon*/,
+        *content_setting_image_model, /*is_visible=*/true, /*has_icon=*/true,
         l10n_util::GetStringUTF16(IDS_MICROPHONE_CAMERA_ALLOWED), 0,
         &gfx::kNoneIcon);
     auth_wrapper.SetMockMediaPermissionStatus(kDenied);
     auth_wrapper.SetMockMediaPermissionStatus(kDenied);
     content_setting_image_model->Update(web_contents());
     ExpectImageModelState(
-        *content_setting_image_model, true /*is_visible*/, true /*has_icon*/,
+        *content_setting_image_model, /*is_visible=*/true, /*has_icon=*/true,
         l10n_util::GetStringUTF16(IDS_MICROPHONE_CAMERA_BLOCKED),
         IDS_CAMERA_TURNED_OFF, &vector_icons::kBlockedBadgeIcon);
     auth_wrapper.SetMockMediaPermissionStatus(kNotDetermined);
@@ -186,8 +190,8 @@
           GetDefaultAudioDevice(), GetDefaultVideoDevice(), std::string(),
           std::string());
       content_setting_image_model->Update(web_contents());
-      ExpectImageModelState(*content_setting_image_model, true /*is_visible*/,
-                            true /*has_icon*/,
+      ExpectImageModelState(*content_setting_image_model, /*is_visible=*/true,
+                            /*has_icon=*/true,
                             l10n_util::GetStringUTF16(IDS_CAMERA_BLOCKED), 0,
                             &vector_icons::kBlockedBadgeIcon);
     }
@@ -201,8 +205,8 @@
           GetDefaultAudioDevice(), GetDefaultVideoDevice(), std::string(),
           std::string());
       content_setting_image_model->Update(web_contents());
-      ExpectImageModelState(*content_setting_image_model, true /*is_visible*/,
-                            true /*has_icon*/,
+      ExpectImageModelState(*content_setting_image_model, /*is_visible=*/true,
+                            /*has_icon=*/true,
                             l10n_util::GetStringUTF16(IDS_MICROPHONE_BLOCKED),
                             0, &vector_icons::kBlockedBadgeIcon);
     }
@@ -219,7 +223,7 @@
           std::string());
       content_setting_image_model->Update(web_contents());
       ExpectImageModelState(
-          *content_setting_image_model, true /*is_visible*/, true /*has_icon*/,
+          *content_setting_image_model, /*is_visible=*/true, /*has_icon=*/true,
           l10n_util::GetStringUTF16(IDS_MICROPHONE_CAMERA_BLOCKED), 0,
           &vector_icons::kBlockedBadgeIcon);
     }
diff --git a/chrome/browser/ui/layout_constants.cc b/chrome/browser/ui/layout_constants.cc
index d063554..7a2b9df 100644
--- a/chrome/browser/ui/layout_constants.cc
+++ b/chrome/browser/ui/layout_constants.cc
@@ -87,14 +87,16 @@
     case TAB_HEIGHT:
       return (touch_ui ? 41 : 34) + GetLayoutConstant(TABSTRIP_TOOLBAR_OVERLAP);
     case TAB_SEPARATOR_HEIGHT:
-      // TODO (1451400): ChromeRefresh2023 needs different values for this
-      // constant.
+      // TODO (crbug.com/1451400): ChromeRefresh2023 needs different values for
+      // this constant.
       return touch_ui ? 24 : 20;
     case TAB_PRE_TITLE_PADDING:
       return 8;
     case TAB_STACK_DISTANCE:
       return touch_ui ? 4 : 6;
     case TABSTRIP_REGION_VIEW_CONTROL_PADDING:
+      // TODO (crbug.com/1451400): ChromeRefresh2023 needs different values for
+      // this constant.
       return 8;
     case TABSTRIP_TOOLBAR_OVERLAP:
       // Because tab scrolling puts the tabstrip on a separate layer,
diff --git a/chrome/browser/ui/media_router/cast_notification_controller_lacros.cc b/chrome/browser/ui/media_router/cast_notification_controller_lacros.cc
new file mode 100644
index 0000000..9f4aeaf
--- /dev/null
+++ b/chrome/browser/ui/media_router/cast_notification_controller_lacros.cc
@@ -0,0 +1,197 @@
+// 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/media_router/cast_notification_controller_lacros.h"
+
+#include <algorithm>
+
+#include "base/strings/utf_string_conversions.h"
+#include "chrome/browser/media/router/discovery/access_code/access_code_cast_feature.h"
+#include "chrome/browser/notifications/notification_display_service.h"
+#include "chrome/browser/notifications/notification_handler.h"
+#include "chrome/browser/profiles/profile.h"
+#include "chrome/grit/generated_resources.h"
+#include "components/media_router/browser/media_router.h"
+#include "components/media_router/browser/media_router_factory.h"
+#include "components/media_router/browser/mirroring_media_controller_host.h"
+#include "components/vector_icons/vector_icons.h"
+#include "content/public/browser/browser_context.h"
+#include "ui/base/l10n/l10n_util.h"
+#include "ui/base/models/image_model.h"
+#include "ui/message_center/public/cpp/message_center_constants.h"
+#include "ui/message_center/public/cpp/notification.h"
+#include "ui/message_center/public/cpp/notification_types.h"
+
+namespace media_router {
+
+namespace {
+
+constexpr char kNotificationId[] = "browser.cast.session";
+constexpr char kNotifierId[] = "browser.cast";
+
+std::u16string GetNotificationTitle(const std::string& sink_name) {
+  if (sink_name.empty()) {
+    return l10n_util::GetStringUTF16(
+        IDS_MEDIA_ROUTER_NOTIFICATION_TITLE_UNKNOWN);
+  }
+  return l10n_util::GetStringFUTF16(IDS_MEDIA_ROUTER_NOTIFICATION_TITLE,
+                                    base::UTF8ToUTF16(sink_name));
+}
+
+std::u16string GetNotificationMessage(const MediaRoute& route,
+                                      MirroringMediaControllerHost* freeze_host,
+                                      bool freeze_enabled) {
+  if (!freeze_enabled || !freeze_host || !freeze_host->can_freeze()) {
+    if (route.media_source().IsDesktopMirroringSource()) {
+      return l10n_util::GetStringUTF16(
+          IDS_MEDIA_ROUTER_NOTIFICATION_MESSAGE_CAST_SCREEN);
+    }
+    return base::UTF8ToUTF16(route.description());
+  }
+  if (freeze_host->is_frozen()) {
+    if (route.media_source().IsDesktopMirroringSource()) {
+      return l10n_util::GetStringUTF16(
+          IDS_MEDIA_ROUTER_NOTIFICATION_MESSAGE_SCREEN_PAUSED);
+    }
+    return l10n_util::GetStringUTF16(
+        IDS_MEDIA_ROUTER_NOTIFICATION_MESSAGE_PAUSED);
+  }
+  if (route.media_source().IsDesktopMirroringSource()) {
+    return l10n_util::GetStringUTF16(
+        IDS_MEDIA_ROUTER_NOTIFICATION_MESSAGE_SCREEN_CAN_PAUSE);
+  }
+  if (route.media_source().IsTabMirroringSource()) {
+    return l10n_util::GetStringUTF16(
+        IDS_MEDIA_ROUTER_NOTIFICATION_MESSAGE_TAB_CAN_PAUSE);
+  }
+  return base::UTF8ToUTF16(route.description());
+}
+
+}  // namespace
+
+CastNotificationControllerLacros::CastNotificationControllerLacros(
+    Profile* profile)
+    : CastNotificationControllerLacros(
+          profile,
+          NotificationDisplayService::GetForProfile(profile),
+          MediaRouterFactory::GetApiForBrowserContext(profile)) {}
+
+CastNotificationControllerLacros::CastNotificationControllerLacros(
+    Profile* profile,
+    NotificationDisplayService* notification_service,
+    MediaRouter* router)
+    : MediaRoutesObserver(router),
+      profile_(profile),
+      notification_service_(notification_service),
+      media_router_(router) {}
+
+CastNotificationControllerLacros::~CastNotificationControllerLacros() = default;
+
+void CastNotificationControllerLacros::OnRoutesUpdated(
+    const std::vector<MediaRoute>& routes) {
+  freeze_button_index_.reset();
+  stop_button_index_.reset();
+  displayed_route_is_frozen_ = false;
+
+  auto route_it =
+      std::find_if(routes.begin(), routes.end(),
+                   [](const MediaRoute& route) { return route.is_local(); });
+  if (route_it == routes.end()) {
+    // There was no active local route, so we hide the current outstanding
+    // notification, if it exists.
+    HideNotification();
+    return;
+  }
+  // This will overwrite the existing notification if there is one.
+  ShowNotification(*route_it);
+}
+
+void CastNotificationControllerLacros::ShowNotification(
+    const MediaRoute& route) {
+  displayed_route_id_ = route.media_route_id();
+  notification_service_->Display(NotificationHandler::Type::TRANSIENT,
+                                 CreateNotification(route), nullptr);
+}
+
+void CastNotificationControllerLacros::HideNotification() {
+  displayed_route_id_ = "";
+  notification_service_->Close(NotificationHandler::Type::TRANSIENT,
+                               kNotificationId);
+}
+
+message_center::Notification
+CastNotificationControllerLacros::CreateNotification(const MediaRoute& route) {
+  MirroringMediaControllerHost* freeze_host =
+      media_router_->GetMirroringMediaControllerHost(route.media_route_id());
+  message_center::RichNotificationData data;
+  data.buttons = GetButtons(route, freeze_host);
+
+  return message_center::Notification{
+      message_center::NotificationType::NOTIFICATION_TYPE_SIMPLE,
+      kNotificationId,
+      GetNotificationTitle(route.media_sink_name()),
+      GetNotificationMessage(route, freeze_host,
+                             IsAccessCodeCastFreezeUiEnabled(profile_)),
+      /*icon=*/ui::ImageModel{},
+      /*display_source=*/u"",
+      /*origin_url=*/GURL{},
+      message_center::NotifierId{message_center::NotifierType::SYSTEM_COMPONENT,
+                                 kNotifierId},
+      std::move(data),
+      base::MakeRefCounted<message_center::HandleNotificationClickDelegate>(
+          base::BindRepeating(
+              &CastNotificationControllerLacros::OnNotificationClicked,
+              weak_ptr_factory_.GetWeakPtr())),
+  };
+}
+
+std::vector<message_center::ButtonInfo>
+CastNotificationControllerLacros::GetButtons(
+    const MediaRoute& route,
+    MirroringMediaControllerHost* freeze_host) {
+  std::vector<message_center::ButtonInfo> buttons;
+  if (IsAccessCodeCastFreezeUiEnabled(profile_) && freeze_host &&
+      freeze_host->can_freeze()) {
+    displayed_route_is_frozen_ = freeze_host->is_frozen();
+    buttons.emplace_back(
+        displayed_route_is_frozen_
+            ? l10n_util::GetStringUTF16(IDS_MEDIA_ROUTER_SINK_VIEW_RESUME)
+            : l10n_util::GetStringUTF16(IDS_MEDIA_ROUTER_SINK_VIEW_PAUSE));
+    freeze_button_index_ = buttons.size() - 1;
+  }
+  buttons.emplace_back(
+      l10n_util::GetStringUTF16(IDS_MEDIA_ROUTER_SINK_VIEW_STOP));
+  stop_button_index_ = buttons.size() - 1;
+  return buttons;
+}
+
+void CastNotificationControllerLacros::OnNotificationClicked(
+    absl::optional<int> button_index) {
+  if (freeze_button_index_ && button_index == freeze_button_index_) {
+    FreezeOrUnfreezeCastStream();
+  } else if (button_index == stop_button_index_) {
+    // Handles the case that the stop button is pressed, or the notification is
+    // pressed not on a button.
+    StopCasting();
+  }
+}
+
+void CastNotificationControllerLacros::StopCasting() {
+  media_router_->TerminateRoute(displayed_route_id_);
+}
+
+void CastNotificationControllerLacros::FreezeOrUnfreezeCastStream() {
+  MirroringMediaControllerHost* freeze_host =
+      media_router_->GetMirroringMediaControllerHost(displayed_route_id_);
+  if (!freeze_host) {
+    return;
+  }
+  if (displayed_route_is_frozen_) {
+    freeze_host->Unfreeze();
+  } else {
+    freeze_host->Freeze();
+  }
+}
+
+}  // namespace media_router
diff --git a/chrome/browser/ui/media_router/cast_notification_controller_lacros.h b/chrome/browser/ui/media_router/cast_notification_controller_lacros.h
new file mode 100644
index 0000000..94235b26
--- /dev/null
+++ b/chrome/browser/ui/media_router/cast_notification_controller_lacros.h
@@ -0,0 +1,71 @@
+// 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_MEDIA_ROUTER_CAST_NOTIFICATION_CONTROLLER_LACROS_H_
+#define CHROME_BROWSER_UI_MEDIA_ROUTER_CAST_NOTIFICATION_CONTROLLER_LACROS_H_
+
+#include "components/keyed_service/core/keyed_service.h"
+#include "components/media_router/browser/media_routes_observer.h"
+#include "ui/message_center/public/cpp/notification.h"
+
+class NotificationDisplayService;
+class Profile;
+
+namespace media_router {
+
+class MediaRouter;
+class MirroringMediaControllerHost;
+
+// Manages showing Chrome OS notifications when casting from Lacros and handling
+// user input from the notifications, e.g. to stop casting.
+//
+// Notifications for Cast sessions started from Ash are managed by
+// ash::CastNotificationController instead.
+class CastNotificationControllerLacros : public KeyedService,
+                                         public MediaRoutesObserver {
+ public:
+  explicit CastNotificationControllerLacros(Profile* profile);
+  CastNotificationControllerLacros(
+      Profile* profile,
+      NotificationDisplayService* notification_service,
+      MediaRouter* router);
+  CastNotificationControllerLacros(const CastNotificationControllerLacros&) =
+      delete;
+  CastNotificationControllerLacros& operator=(
+      const CastNotificationControllerLacros&) = delete;
+
+  ~CastNotificationControllerLacros() override;
+
+  // MediaRoutesObserver:
+  void OnRoutesUpdated(const std::vector<MediaRoute>& routes) override;
+
+ private:
+  void ShowNotification(const MediaRoute& route);
+  void HideNotification();
+
+  message_center::Notification CreateNotification(const MediaRoute& route);
+  std::vector<message_center::ButtonInfo> GetButtons(
+      const MediaRoute& route,
+      MirroringMediaControllerHost* freeze_host);
+
+  void OnNotificationClicked(absl::optional<int> button_index);
+  void StopCasting();
+  void FreezeOrUnfreezeCastStream();
+
+  const raw_ptr<Profile> profile_;
+  const raw_ptr<NotificationDisplayService> notification_service_;
+  const raw_ptr<MediaRouter> media_router_;
+
+  std::string displayed_route_id_;
+  bool displayed_route_is_frozen_ = false;
+  absl::optional<int> freeze_button_index_;
+  absl::optional<int> stop_button_index_;
+
+  base::WeakPtrFactory<CastNotificationControllerLacros> weak_ptr_factory_{
+      this};
+};
+
+}  // namespace media_router
+
+#endif  // CHROME_BROWSER_UI_MEDIA_ROUTER_CAST_NOTIFICATION_CONTROLLER_LACROS_H_
diff --git a/chrome/browser/ui/media_router/cast_notification_controller_lacros_factory.cc b/chrome/browser/ui/media_router/cast_notification_controller_lacros_factory.cc
new file mode 100644
index 0000000..2ef48da
--- /dev/null
+++ b/chrome/browser/ui/media_router/cast_notification_controller_lacros_factory.cc
@@ -0,0 +1,58 @@
+// 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/media_router/cast_notification_controller_lacros_factory.h"
+
+#include "base/no_destructor.h"
+#include "chrome/browser/media/router/chrome_media_router_factory.h"
+#include "chrome/browser/media/router/media_router_feature.h"
+#include "chrome/browser/notifications/notification_display_service_factory.h"
+#include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/ui/media_router/cast_notification_controller_lacros.h"
+
+namespace media_router {
+
+CastNotificationControllerLacrosFactory::
+    CastNotificationControllerLacrosFactory()
+    : ProfileKeyedServiceFactory(
+          "CastNotificationControllerLacros",
+          ProfileSelections::Builder()
+              .WithRegular(ProfileSelection::kOriginalOnly)
+              .WithGuest(ProfileSelection::kOriginalOnly)
+              .Build()) {
+  DependsOn(media_router::ChromeMediaRouterFactory::GetInstance());
+  DependsOn(NotificationDisplayServiceFactory::GetInstance());
+}
+CastNotificationControllerLacrosFactory::
+    ~CastNotificationControllerLacrosFactory() = default;
+
+// static
+CastNotificationControllerLacrosFactory*
+CastNotificationControllerLacrosFactory::GetInstance() {
+  static base::NoDestructor<CastNotificationControllerLacrosFactory> factory;
+  return factory.get();
+}
+
+KeyedService* CastNotificationControllerLacrosFactory::BuildServiceInstanceFor(
+    content::BrowserContext* context) const {
+  if (!media_router::MediaRouterEnabled(context)) {
+    return nullptr;
+  }
+  return new CastNotificationControllerLacros(
+      Profile::FromBrowserContext(context));
+}
+
+bool CastNotificationControllerLacrosFactory::
+    ServiceIsCreatedWithBrowserContext() const {
+  return true;
+}
+
+bool CastNotificationControllerLacrosFactory::ServiceIsNULLWhileTesting()
+    const {
+  // CastNotificationControllerLacros depends on MediaRouter that needs an IO
+  // thread which is missing in many tests. So we disable it in tests.
+  return true;
+}
+
+}  // namespace media_router
diff --git a/chrome/browser/ui/media_router/cast_notification_controller_lacros_factory.h b/chrome/browser/ui/media_router/cast_notification_controller_lacros_factory.h
new file mode 100644
index 0000000..e39cf63
--- /dev/null
+++ b/chrome/browser/ui/media_router/cast_notification_controller_lacros_factory.h
@@ -0,0 +1,38 @@
+// 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_MEDIA_ROUTER_CAST_NOTIFICATION_CONTROLLER_LACROS_FACTORY_H_
+#define CHROME_BROWSER_UI_MEDIA_ROUTER_CAST_NOTIFICATION_CONTROLLER_LACROS_FACTORY_H_
+
+#include "chrome/browser/profiles/profile_keyed_service_factory.h"
+
+namespace content {
+class BrowserContext;
+}
+
+namespace media_router {
+
+class CastNotificationControllerLacrosFactory
+    : public ProfileKeyedServiceFactory {
+ public:
+  CastNotificationControllerLacrosFactory();
+  CastNotificationControllerLacrosFactory(
+      const CastNotificationControllerLacrosFactory&) = delete;
+  CastNotificationControllerLacrosFactory& operator=(
+      const CastNotificationControllerLacrosFactory&) = delete;
+  ~CastNotificationControllerLacrosFactory() override;
+
+  static CastNotificationControllerLacrosFactory* GetInstance();
+
+ private:
+  // ProfileKeyedServiceFactory:
+  KeyedService* BuildServiceInstanceFor(
+      content::BrowserContext* context) const override;
+  bool ServiceIsCreatedWithBrowserContext() const override;
+  bool ServiceIsNULLWhileTesting() const override;
+};
+
+}  // namespace media_router
+
+#endif  // CHROME_BROWSER_UI_MEDIA_ROUTER_CAST_NOTIFICATION_CONTROLLER_LACROS_FACTORY_H_
diff --git a/chrome/browser/ui/media_router/cast_notification_controller_lacros_unittest.cc b/chrome/browser/ui/media_router/cast_notification_controller_lacros_unittest.cc
new file mode 100644
index 0000000..342fb45
--- /dev/null
+++ b/chrome/browser/ui/media_router/cast_notification_controller_lacros_unittest.cc
@@ -0,0 +1,111 @@
+// 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/media_router/cast_notification_controller_lacros.h"
+
+#include <memory>
+
+#include "base/functional/bind.h"
+#include "chrome/browser/media/router/chrome_media_router_factory.h"
+#include "chrome/browser/media/router/discovery/access_code/access_code_cast_feature.h"
+#include "chrome/browser/notifications/notification_display_service.h"
+#include "chrome/test/base/testing_profile.h"
+#include "components/media_router/browser/test/mock_media_router.h"
+#include "components/media_router/common/media_source.h"
+#include "components/prefs/pref_service.h"
+#include "content/public/test/browser_task_environment.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using testing::_;
+using testing::Return;
+using testing::WithArg;
+
+namespace media_router {
+
+namespace {
+
+constexpr char kRouteId[] = "route_id";
+
+class MockNotificationDisplayService : public NotificationDisplayService {
+ public:
+  MOCK_METHOD(void,
+              Display,
+              (NotificationHandler::Type notification_type,
+               const message_center::Notification& notification,
+               std::unique_ptr<NotificationCommon::Metadata> metadata));
+  MOCK_METHOD(void,
+              Close,
+              (NotificationHandler::Type notification_type,
+               const std::string& notification_id));
+  MOCK_METHOD(void, GetDisplayed, (DisplayedNotificationsCallback callback));
+  MOCK_METHOD(void, AddObserver, (Observer * observer));
+  MOCK_METHOD(void, RemoveObserver, (Observer * observer));
+};
+
+MediaRoute CreateMediaRoute() {
+  return MediaRoute{kRouteId, MediaSource{"source_id"}, "sink_id",
+                    "Route description.",
+                    /*is_local=*/true};
+}
+
+}  // namespace
+
+class CastNotificationControllerLacrosTest : public testing::Test {
+ public:
+  void SetUp() override {
+    TestingProfile::Builder builder;
+    builder.AddTestingFactory(ChromeMediaRouterFactory::GetInstance(),
+                              base::BindRepeating(&MockMediaRouter::Create));
+    profile_ = builder.Build();
+
+    notification_controller_ =
+        std::make_unique<CastNotificationControllerLacros>(
+            profile_.get(), &notification_service_, &media_router_);
+  }
+
+ protected:
+  content::BrowserTaskEnvironment task_environment_;
+  MockNotificationDisplayService notification_service_;
+  MockMediaRouter media_router_;
+  std::unique_ptr<TestingProfile> profile_;
+  std::unique_ptr<CastNotificationControllerLacros> notification_controller_;
+};
+
+TEST_F(CastNotificationControllerLacrosTest, DisplayNotification) {
+  EXPECT_CALL(notification_service_,
+              Display(NotificationHandler::Type::TRANSIENT, _, _))
+      .WillOnce(
+          WithArg<1>([](const message_center::Notification& notification) {
+            EXPECT_EQ(message_center::NOTIFICATION_TYPE_SIMPLE,
+                      notification.type());
+            EXPECT_EQ("browser.cast.session", notification.id());
+            EXPECT_EQ("browser.cast", notification.notifier_id().id);
+          }));
+  notification_controller_->OnRoutesUpdated({CreateMediaRoute()});
+}
+
+TEST_F(CastNotificationControllerLacrosTest, CloseNotification) {
+  notification_controller_->OnRoutesUpdated({CreateMediaRoute()});
+
+  // Removing a route should cause the corresponding notification to be closed.
+  EXPECT_CALL(notification_service_, Close(NotificationHandler::Type::TRANSIENT,
+                                           "browser.cast.session"));
+  notification_controller_->OnRoutesUpdated({});
+}
+
+TEST_F(CastNotificationControllerLacrosTest, ClickOnStopButton) {
+  EXPECT_CALL(media_router_, TerminateRoute(kRouteId));
+  EXPECT_CALL(notification_service_,
+              Display(NotificationHandler::Type::TRANSIENT, _, _))
+      .WillOnce(
+          WithArg<1>([](const message_center::Notification& notification) {
+            // Clicking on the stop button should call TerminateRoute().
+            notification.delegate()->Click(/*button_index=*/0,
+                                           /*reply=*/absl::nullopt);
+          }));
+  notification_controller_->OnRoutesUpdated({CreateMediaRoute()});
+}
+
+}  // namespace media_router
diff --git a/chrome/browser/ui/profile_view_utils.cc b/chrome/browser/ui/profile_view_utils.cc
index 594e3850..21c6164 100644
--- a/chrome/browser/ui/profile_view_utils.cc
+++ b/chrome/browser/ui/profile_view_utils.cc
@@ -4,7 +4,10 @@
 
 #include "chrome/browser/ui/profile_view_utils.h"
 
+#include "chrome/browser/browser_process.h"
 #include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/profiles/profile_attributes_storage.h"
+#include "chrome/browser/profiles/profile_manager.h"
 #include "chrome/browser/signin/identity_manager_factory.h"
 #include "chrome/browser/sync/sync_ui_util.h"
 #include "chrome/browser/ui/browser_finder.h"
@@ -12,6 +15,7 @@
 #include "chrome/browser/ui/browser_navigator_params.h"
 #include "chrome/common/url_constants.h"
 #include "components/signin/public/base/consent_level.h"
+#include "components/signin/public/identity_manager/account_info.h"
 #include "components/signin/public/identity_manager/identity_manager.h"
 #include "net/base/url_util.h"
 #include "ui/base/window_open_disposition.h"
@@ -54,3 +58,24 @@
   }
   return browser_count;
 }
+
+AccountInfo GetAccountInfoFromProfile(const Profile* profile) {
+  signin::IdentityManager* identity_manager =
+      IdentityManagerFactory::GetForProfileIfExists(profile);
+  // IdentityManager may be null if one is not mapped to the profile through the
+  // KeyedServiceFactory. We do not create one if it doesn't already exist and
+  // simply return an empty AccountInfo object.
+  if (!identity_manager) {
+    return AccountInfo();
+  }
+  CoreAccountInfo account =
+      identity_manager->GetPrimaryAccountInfo(signin::ConsentLevel::kSignin);
+  return identity_manager->FindExtendedAccountInfo(account);
+}
+
+ProfileAttributesEntry* GetProfileAttributesFromProfile(
+    const Profile* profile) {
+  return g_browser_process->profile_manager()
+      ->GetProfileAttributesStorage()
+      .GetProfileAttributesWithPath(profile->GetPath());
+}
diff --git a/chrome/browser/ui/profile_view_utils.h b/chrome/browser/ui/profile_view_utils.h
index 38f75b7b..39c530e 100644
--- a/chrome/browser/ui/profile_view_utils.h
+++ b/chrome/browser/ui/profile_view_utils.h
@@ -9,6 +9,8 @@
 #include <utility>
 
 class Profile;
+class ProfileAttributesEntry;
+struct AccountInfo;
 
 // Navigates to the Google Account page.
 void NavigateToGoogleAccountPage(Profile* profile, const std::string& email);
@@ -23,4 +25,10 @@
 // Note: For regular profiles this includes incognito sessions.
 int CountBrowsersFor(Profile* profile);
 
+// Returns the AccountInfo from the profile.
+AccountInfo GetAccountInfoFromProfile(const Profile* profile);
+
+// Returns the ProfileAttributesEntry from the profile.
+ProfileAttributesEntry* GetProfileAttributesFromProfile(const Profile* profile);
+
 #endif  // CHROME_BROWSER_UI_PASSWORDS_UI_UTILS_H_
diff --git a/chrome/browser/ui/toolbar/app_menu_model.cc b/chrome/browser/ui/toolbar/app_menu_model.cc
index 07ed0a28..e938007 100644
--- a/chrome/browser/ui/toolbar/app_menu_model.cc
+++ b/chrome/browser/ui/toolbar/app_menu_model.cc
@@ -29,12 +29,12 @@
 #include "chrome/browser/prefs/incognito_mode_prefs.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/profiles/profile_attributes_entry.h"
-#include "chrome/browser/profiles/profile_attributes_storage.h"
 #include "chrome/browser/profiles/profile_avatar_icon_util.h"
-#include "chrome/browser/profiles/profile_manager.h"
 #include "chrome/browser/search/search.h"
 #include "chrome/browser/sharing_hub/sharing_hub_features.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 "chrome/browser/ui/bookmarks/bookmark_utils.h"
 #include "chrome/browser/ui/browser.h"
 #include "chrome/browser/ui/browser_commands.h"
@@ -82,6 +82,7 @@
 #include "components/prefs/pref_service.h"
 #include "components/profile_metrics/browser_profile_type.h"
 #include "components/signin/public/base/signin_metrics.h"
+#include "components/signin/public/base/signin_pref_names.h"
 #include "components/signin/public/identity_manager/identity_manager.h"
 #include "components/strings/grit/components_strings.h"
 #include "components/vector_icons/vector_icons.h"
@@ -214,27 +215,6 @@
          url.DomainIs(password_manager::kChromeUIPasswordManagerHost);
 }
 
-ProfileAttributesEntry* GetProfileAttributesFromProfile(
-    const Profile* profile) {
-  return g_browser_process->profile_manager()
-      ->GetProfileAttributesStorage()
-      .GetProfileAttributesWithPath(profile->GetPath());
-}
-
-AccountInfo GetAccountInfoFromProfile(const Profile* profile) {
-  signin::IdentityManager* identity_manager =
-      IdentityManagerFactory::GetForProfileIfExists(profile);
-  // IdentityManager may be null if one is not mapped to the profile through the
-  // KeyedServiceFactory. We do not create one if it doesn't already exist and
-  // simply return an empty AccountInfo object.
-  if (!identity_manager) {
-    return AccountInfo();
-  }
-  CoreAccountInfo account =
-      identity_manager->GetPrimaryAccountInfo(signin::ConsentLevel::kSignin);
-  return identity_manager->FindExtendedAccountInfo(account);
-}
-
 class ProfileSubMenuModel : public ui::SimpleMenuModel {
  public:
   ProfileSubMenuModel(ui::SimpleMenuModel::Delegate* delegate,
@@ -250,14 +230,17 @@
   const std::u16string& profile_name() const { return profile_name_; }
 
  private:
+  bool BuildSyncSection();
+
   ui::ImageModel avatar_image_model_;
   std::u16string profile_name_;
+  raw_ptr<Profile> profile_;
 };
 
 ProfileSubMenuModel::ProfileSubMenuModel(
     ui::SimpleMenuModel::Delegate* delegate,
     Profile* profile)
-    : SimpleMenuModel(delegate) {
+    : SimpleMenuModel(delegate), profile_(profile) {
   const int avatar_icon_size =
       GetLayoutConstant(APP_MENU_PROFILE_ROW_AVATAR_ICON_SIZE);
   avatar_image_model_ = ui::ImageModel::FromVectorIcon(
@@ -269,6 +252,9 @@
   } else if (profile->IsGuestSession()) {
     profile_name_ = l10n_util::GetStringUTF16(IDS_GUEST_PROFILE_NAME);
   } else {
+    if (BuildSyncSection()) {
+      AddSeparator(ui::NORMAL_SEPARATOR);
+    }
     ProfileAttributesEntry* profile_attributes =
         GetProfileAttributesFromProfile(profile);
     // If the profile is being deleted, profile_attributes may be null.
@@ -328,6 +314,64 @@
   }
 }
 
+bool ProfileSubMenuModel::BuildSyncSection() {
+  if (!profile_->GetPrefs()->GetBoolean(prefs::kSigninAllowed)) {
+    return false;
+  }
+
+  if (!SyncServiceFactory::IsSyncAllowed(profile_)) {
+    return false;
+  }
+
+  const AccountInfo account_info = GetAccountInfoFromProfile(profile_);
+
+  const std::u16string signed_in_status =
+      (IsSyncPaused(profile_) || account_info.IsEmpty())
+          ? l10n_util::GetStringUTF16(IDS_PROFILES_LOCAL_PROFILE_STATE)
+          : l10n_util::GetStringFUTF16(IDS_PROFILE_ROW_SIGNED_IN_MESSAGE,
+                                       {base::UTF8ToUTF16(account_info.email)});
+
+  AddTitle(signed_in_status);
+  signin::IdentityManager* const identity_manager =
+      IdentityManagerFactory::GetForProfile(profile_);
+  const bool is_sync_feature_enabled =
+      identity_manager->HasPrimaryAccount(signin::ConsentLevel::kSync);
+  // First, check for sync errors. They may exist even if sync-the-feature is
+  // disabled and only sync-the-transport is running.
+  const absl::optional<AvatarSyncErrorType> error =
+      GetAvatarSyncErrorType(profile_);
+  if (error.has_value()) {
+    if (error == AvatarSyncErrorType::kSyncPaused) {
+      // If sync is paused the menu item will be specific to the paused error.
+      AddItemWithStringIdAndIcon(IDC_SHOW_SIGNIN_WHEN_PAUSED,
+                                 IDS_PROFILE_ROW_SIGN_IN_AGAIN,
+                                 ui::ImageModel::FromVectorIcon(
+                                     vector_icons::kSyncOffChromeRefreshIcon,
+                                     ui::kColorMenuIcon, kDefaultIconSize));
+    } else {
+      // All remaining errors will have the same menu item.
+      AddItemWithStringIdAndIcon(
+          IDC_SHOW_SYNC_SETTINGS, IDS_PROFILE_ROW_SYNC_ERROR_MESSAGE,
+          ui::ImageModel::FromVectorIcon(
+              vector_icons::kSyncProblemChromeRefreshIcon, ui::kColorMenuIcon,
+              kDefaultIconSize));
+    }
+    return true;
+  }
+  if (is_sync_feature_enabled) {
+    AddItemWithStringIdAndIcon(
+        IDC_SHOW_SYNC_SETTINGS, IDS_PROFILE_ROW_SYNC_IS_ON,
+        ui::ImageModel::FromVectorIcon(vector_icons::kSyncChromeRefreshIcon,
+                                       ui::kColorMenuIcon, kDefaultIconSize));
+  } else {
+    AddItemWithStringIdAndIcon(
+        IDC_TURN_ON_SYNC, IDS_PROFILE_ROW_TURN_ON_SYNC,
+        ui::ImageModel::FromVectorIcon(vector_icons::kSyncOffChromeRefreshIcon,
+                                       ui::kColorMenuIcon, kDefaultIconSize));
+  }
+  return true;
+}
+
 class PasswordsAndAutofillSubMenuModel : public ui::SimpleMenuModel {
  public:
   explicit PasswordsAndAutofillSubMenuModel(
@@ -1102,6 +1146,29 @@
       }
       LogMenuAction(MENU_ACTION_MANAGE_GOOGLE_ACCOUNT);
       break;
+#if !BUILDFLAG(IS_CHROMEOS_ASH)
+    case IDC_SHOW_SYNC_SETTINGS:
+      if (!uma_action_recorded_) {
+        UMA_HISTOGRAM_MEDIUM_TIMES("WrenchMenu.TimeToAction.ShowSyncSettings",
+                                   delta);
+      }
+      LogMenuAction(MENU_SHOW_SYNC_SETTINGS);
+      break;
+    case IDC_TURN_ON_SYNC:
+      if (!uma_action_recorded_) {
+        UMA_HISTOGRAM_MEDIUM_TIMES("WrenchMenu.TimeToAction.ShowTurnOnSync",
+                                   delta);
+      }
+      LogMenuAction(MENU_TURN_ON_SYNC);
+      break;
+    case IDC_SHOW_SIGNIN_WHEN_PAUSED:
+      if (!uma_action_recorded_) {
+        UMA_HISTOGRAM_MEDIUM_TIMES(
+            "WrenchMenu.TimeToAction.ShowSigninWhenPaused", delta);
+      }
+      LogMenuAction(MENU_SHOW_SIGNIN_WHEN_PAUSED);
+      break;
+#endif
   }
 
   if (!uma_action_recorded_) {
diff --git a/chrome/browser/ui/toolbar/app_menu_model.h b/chrome/browser/ui/toolbar/app_menu_model.h
index c898bc46..2f701977 100644
--- a/chrome/browser/ui/toolbar/app_menu_model.h
+++ b/chrome/browser/ui/toolbar/app_menu_model.h
@@ -87,6 +87,9 @@
   MENU_ACTION_CUSTOMIZE_CHROME = 65,
   MENU_ACTION_CLOSE_PROFILE = 66,
   MENU_ACTION_MANAGE_GOOGLE_ACCOUNT = 67,
+  MENU_SHOW_SIGNIN_WHEN_PAUSED = 68,
+  MENU_SHOW_SYNC_SETTINGS = 69,
+  MENU_TURN_ON_SYNC = 70,
   LIMIT_MENU_ACTION
 };
 
diff --git a/chrome/browser/ui/toolbar/app_menu_model_unittest.cc b/chrome/browser/ui/toolbar/app_menu_model_unittest.cc
index 5637fd4..56ec45ba 100644
--- a/chrome/browser/ui/toolbar/app_menu_model_unittest.cc
+++ b/chrome/browser/ui/toolbar/app_menu_model_unittest.cc
@@ -16,6 +16,7 @@
 #include "chrome/app/chrome_command_ids.h"
 #include "chrome/browser/defaults.h"
 #include "chrome/browser/prefs/browser_prefs.h"
+#include "chrome/browser/signin/identity_manager_factory.h"
 #include "chrome/browser/ui/browser.h"
 #include "chrome/browser/ui/global_error/global_error.h"
 #include "chrome/browser/ui/global_error/global_error_service.h"
@@ -31,6 +32,9 @@
 #include "chrome/test/base/testing_profile.h"
 #include "components/password_manager/core/common/password_manager_features.h"
 #include "components/performance_manager/public/features.h"
+#include "components/signin/public/base/consent_level.h"
+#include "components/signin/public/identity_manager/account_info.h"
+#include "components/signin/public/identity_manager/identity_test_utils.h"
 #include "components/sync_preferences/testing_pref_service_syncable.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "ui/base/ui_base_features.h"
@@ -398,15 +402,51 @@
   check_for_icons(u"<Root Menu>", &model);
 }
 
-TEST_F(TestAppMenuModelCR2023, LogProfileMenuMetrics) {
+// Profile row does not show on ChromeOS.
+#if !BUILDFLAG(IS_CHROMEOS)
+class TestAppMenuModelCR2023MetricsTest
+    : public TestAppMenuModelCR2023,
+      public testing::WithParamInterface<int> {
+ public:
+  TestAppMenuModelCR2023MetricsTest() = default;
+};
+
+TEST_P(TestAppMenuModelCR2023MetricsTest, LogProfileMenuMetrics) {
+  int command_id = GetParam();
   TestLogMetricsAppMenuModel model(this, browser());
   model.Init();
-  model.ExecuteCommand(IDC_MANAGE_GOOGLE_ACCOUNT, 0);
-  model.ExecuteCommand(IDC_CLOSE_PROFILE, 0);
-  model.ExecuteCommand(IDC_CUSTOMIZE_CHROME, 0);
-  EXPECT_EQ(3, model.log_metrics_count_);
+  model.ExecuteCommand(command_id, 0);
+  EXPECT_EQ(1, model.log_metrics_count_);
 }
 
+INSTANTIATE_TEST_SUITE_P(All,
+                         TestAppMenuModelCR2023MetricsTest,
+                         testing::Values(IDC_MANAGE_GOOGLE_ACCOUNT,
+                                         IDC_CLOSE_PROFILE,
+                                         IDC_CUSTOMIZE_CHROME,
+                                         IDC_SHOW_SIGNIN_WHEN_PAUSED,
+                                         IDC_SHOW_SYNC_SETTINGS,
+                                         IDC_TURN_ON_SYNC));
+
+TEST_F(TestAppMenuModelCR2023, ProfileSyncOnTest) {
+  signin::IdentityManager* identity_manager =
+      IdentityManagerFactory::GetForProfile(browser()->profile());
+  signin::MakePrimaryAccountAvailable(identity_manager, "user@example.com",
+                                      signin::ConsentLevel::kSync);
+  signin::SetRefreshTokenForPrimaryAccount(identity_manager);
+  AppMenuModel model(this, browser());
+  model.Init();
+  const size_t profile_menu_index =
+      model.GetIndexOfCommandId(IDC_PROFILE_MENU_IN_APP_MENU).value();
+  ui::SimpleMenuModel* profile_menu = static_cast<ui::SimpleMenuModel*>(
+      model.GetSubmenuModelAt(profile_menu_index));
+  const size_t sync_settings_index =
+      profile_menu->GetIndexOfCommandId(IDC_SHOW_SYNC_SETTINGS).value();
+  EXPECT_TRUE(profile_menu->IsEnabledAt(sync_settings_index));
+}
+
+#endif
+
 #if BUILDFLAG(IS_CHROMEOS)
 // Tests settings menu items is disabled in the app menu when
 // kSystemFeaturesDisableList is set.
diff --git a/chrome/browser/ui/views/accelerator_table_unittest_mac.mm b/chrome/browser/ui/views/accelerator_table_unittest_mac.mm
index d2af2ba..91a94b9 100644
--- a/chrome/browser/ui/views/accelerator_table_unittest_mac.mm
+++ b/chrome/browser/ui/views/accelerator_table_unittest_mac.mm
@@ -14,6 +14,10 @@
 #include "ui/events/event_constants.h"
 #import "ui/events/keycodes/keyboard_code_conversion_mac.h"
 
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
 namespace {
 
 void VerifyTableDoesntHaveDuplicates(
diff --git a/chrome/browser/ui/views/autofill/payments/autofill_progress_dialog_views_browsertest.cc b/chrome/browser/ui/views/autofill/payments/autofill_progress_dialog_views_browsertest.cc
index dd4383b..8dc198b 100644
--- a/chrome/browser/ui/views/autofill/payments/autofill_progress_dialog_views_browsertest.cc
+++ b/chrome/browser/ui/views/autofill/payments/autofill_progress_dialog_views_browsertest.cc
@@ -94,14 +94,17 @@
 }
 
 // Ensures clicking on the cancel button is correctly handled.
-// TODO(crbug.com/1257990): Flaky.
 IN_PROC_BROWSER_TEST_F(AutofillProgressDialogViewsBrowserTest,
-                       DISABLED_ClickCancelButton) {
+                       ClickCancelButton) {
   base::HistogramTester histogram_tester;
   ShowUi("VirtualCardUnmask");
   VerifyUi();
+  auto* dialog_views = GetDialogViews();
+  ASSERT_TRUE(dialog_views);
+  views::test::WidgetDestroyedWaiter destroyed_waiter(
+      dialog_views->GetWidget());
   GetDialogViews()->CancelDialog();
-  base::RunLoop().RunUntilIdle();
+  destroyed_waiter.Wait();
   EXPECT_FALSE(GetDialogViews());
   histogram_tester.ExpectUniqueSample(
       "Autofill.ProgressDialog.CardUnmask.Shown", true, 1);
@@ -116,7 +119,7 @@
   ShowUi("VirtualCardUnmask");
   VerifyUi();
   auto* dialog_views = GetDialogViews();
-  EXPECT_TRUE(dialog_views);
+  ASSERT_TRUE(dialog_views);
   views::test::WidgetDestroyedWaiter destroyed_waiter(
       dialog_views->GetWidget());
   base::MockOnceClosure no_interactive_authentication_callback;
diff --git a/chrome/browser/ui/views/autofill/payments/card_unmask_otp_input_dialog_views.cc b/chrome/browser/ui/views/autofill/payments/card_unmask_otp_input_dialog_views.cc
index 689a1b2..43fb72a 100644
--- a/chrome/browser/ui/views/autofill/payments/card_unmask_otp_input_dialog_views.cc
+++ b/chrome/browser/ui/views/autofill/payments/card_unmask_otp_input_dialog_views.cc
@@ -113,7 +113,7 @@
 
 void CardUnmaskOtpInputDialogViews::AddedToWidget() {
   GetBubbleFrameView()->SetTitleView(
-      std::make_unique<TitleWithIconAndSeparatorView>(
+      std::make_unique<TitleWithIconAfterLabelView>(
           GetWindowTitle(), TitleWithIconAndSeparatorView::Icon::GOOGLE_PAY));
 }
 
diff --git a/chrome/browser/ui/views/commerce/price_insights_icon_view.cc b/chrome/browser/ui/views/commerce/price_insights_icon_view.cc
index 5376ce9..3fc701f8 100644
--- a/chrome/browser/ui/views/commerce/price_insights_icon_view.cc
+++ b/chrome/browser/ui/views/commerce/price_insights_icon_view.cc
@@ -6,6 +6,8 @@
 
 #include "chrome/browser/ui/browser_element_identifiers.h"
 #include "chrome/browser/ui/commerce/price_tracking/shopping_list_ui_tab_helper.h"
+#include "chrome/browser/ui/side_panel/side_panel_entry_id.h"
+#include "chrome/browser/ui/side_panel/side_panel_ui.h"
 #include "components/commerce/core/commerce_feature_list.h"
 #include "components/strings/grit/components_strings.h"
 #include "components/vector_icons/vector_icons.h"
@@ -43,8 +45,15 @@
 
 void PriceInsightsIconView::OnExecuting(
     PageActionIconView::ExecuteSource execute_source) {
-  // TODO(meiliang): Open price insights side panel.
-  NOTIMPLEMENTED();
+  auto* web_contents = GetWebContents();
+  if (!web_contents) {
+    return;
+  }
+  auto* tab_helper =
+      commerce::ShoppingListUiTabHelper::FromWebContents(web_contents);
+  CHECK(tab_helper);
+
+  tab_helper->ShowShoppingInsightsSidePanel();
 }
 
 bool PriceInsightsIconView::ShouldShow() const {
diff --git a/chrome/browser/ui/views/commerce/price_insights_icon_view_interactive_uitest.cc b/chrome/browser/ui/views/commerce/price_insights_icon_view_interactive_uitest.cc
new file mode 100644
index 0000000..375a462
--- /dev/null
+++ b/chrome/browser/ui/views/commerce/price_insights_icon_view_interactive_uitest.cc
@@ -0,0 +1,107 @@
+// 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/views/commerce/price_insights_icon_view.h"
+
+#include "chrome/browser/commerce/shopping_service_factory.h"
+#include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/ui/browser_element_identifiers.h"
+#include "chrome/browser/ui/commerce/price_tracking/mock_shopping_list_ui_tab_helper.h"
+#include "chrome/test/base/in_process_browser_test.h"
+#include "chrome/test/base/interactive_test_utils.h"
+#include "chrome/test/base/ui_test_utils.h"
+#include "chrome/test/interaction/interactive_browser_test.h"
+#include "components/commerce/core/commerce_feature_list.h"
+#include "components/commerce/core/mock_shopping_service.h"
+#include "content/public/test/browser_test.h"
+#include "net/dns/mock_host_resolver.h"
+#include "net/test/embedded_test_server/embedded_test_server.h"
+#include "ui/base/interaction/interactive_test.h"
+
+namespace {
+DEFINE_LOCAL_ELEMENT_IDENTIFIER_VALUE(kShoppingTab);
+const char kShoppingURL[] = "/shopping.html";
+
+std::unique_ptr<net::test_server::HttpResponse> BasicResponse(
+    const net::test_server::HttpRequest& request) {
+  auto response = std::make_unique<net::test_server::BasicHttpResponse>();
+  response->set_content("shopping page");
+  response->set_content_type("text/html");
+  return response;
+}
+}  // namespace
+
+class PriceInsightsIconViewInteractiveTest : public InteractiveBrowserTest {
+ public:
+  void SetUp() override {
+    set_open_about_blank_on_browser_launch(true);
+    ASSERT_TRUE(embedded_test_server()->InitializeAndListen());
+    InteractiveBrowserTest::SetUp();
+  }
+
+  void SetUpOnMainThread() override {
+    host_resolver()->AddRule("*", "127.0.0.1");
+    embedded_test_server()->RegisterDefaultHandler(
+        base::BindRepeating(&BasicResponse));
+    embedded_test_server()->StartAcceptingConnections();
+
+    InteractiveBrowserTest::SetUpOnMainThread();
+
+    SetUpTabHelperAndShoppingService();
+  }
+
+ private:
+  base::test::ScopedFeatureList test_features_{commerce::kPriceInsights};
+
+  void SetUpTabHelperAndShoppingService() {
+    // Remove the original tab helper so we don't get into a bad situation when
+    // we go to replace the shopping service with the mock one. The old tab
+    // helper is still holding a reference to the original shopping service and
+    // other dependencies which we switch out below (leaving some dangling
+    // pointers on destruction).
+    browser()->tab_strip_model()->GetActiveWebContents()->RemoveUserData(
+        commerce::ShoppingListUiTabHelper::UserDataKey());
+
+    auto* mock_shopping_service = static_cast<commerce::MockShoppingService*>(
+        commerce::ShoppingServiceFactory::GetInstance()
+            ->SetTestingFactoryAndUse(
+                browser()->profile(),
+                base::BindRepeating([](content::BrowserContext* context) {
+                  return commerce::MockShoppingService::Build();
+                })));
+
+    MockShoppingListUiTabHelper::CreateForWebContents(
+        browser()->tab_strip_model()->GetActiveWebContents());
+    MockShoppingListUiTabHelper* mock_tab_helper =
+        static_cast<MockShoppingListUiTabHelper*>(
+            MockShoppingListUiTabHelper::FromWebContents(
+                browser()->tab_strip_model()->GetActiveWebContents()));
+    EXPECT_CALL(*mock_tab_helper, ShouldShowPriceInsightsIconView)
+        .Times(testing::AnyNumber());
+    ON_CALL(*mock_tab_helper, ShouldShowPriceInsightsIconView)
+        .WillByDefault(testing::Return(true));
+
+    mock_tab_helper->SetShoppingServiceForTesting(mock_shopping_service);
+
+    EXPECT_CALL(*mock_shopping_service, GetProductInfoForUrl)
+        .Times(testing::AnyNumber());
+
+    commerce::ProductInfo info;
+    info.product_cluster_id.emplace(12345L);
+    mock_shopping_service->SetResponseForGetProductInfoForUrl(info);
+  }
+};
+
+IN_PROC_BROWSER_TEST_F(PriceInsightsIconViewInteractiveTest,
+                       SidePanelShownOnPress) {
+  RunTestSequence(
+      InstrumentTab(kShoppingTab),
+      NavigateWebContents(kShoppingTab,
+                          embedded_test_server()->GetURL(kShoppingURL)),
+      // Ensure the side panel isn't open
+      EnsureNotPresent(kSidePanelElementId),
+      // Click on the action chip to open the side panel
+      PressButton(kPriceInsightsChipElementId),
+      WaitForShow(kSidePanelElementId), FlushEvents());
+}
diff --git a/chrome/browser/ui/views/frame/browser_non_client_frame_view_mac_unittest.mm b/chrome/browser/ui/views/frame/browser_non_client_frame_view_mac_unittest.mm
index b89bd95..5e7b20d 100644
--- a/chrome/browser/ui/views/frame/browser_non_client_frame_view_mac_unittest.mm
+++ b/chrome/browser/ui/views/frame/browser_non_client_frame_view_mac_unittest.mm
@@ -7,6 +7,10 @@
 #include "base/strings/stringprintf.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
 TEST(BrowserNonClientFrameViewMacTest, GetCenteredTitleBounds) {
   struct {
     int frame_width;
diff --git a/chrome/browser/ui/views/frame/browser_view.cc b/chrome/browser/ui/views/frame/browser_view.cc
index 807d0809..06f11d4 100644
--- a/chrome/browser/ui/views/frame/browser_view.cc
+++ b/chrome/browser/ui/views/frame/browser_view.cc
@@ -224,6 +224,7 @@
 #include "ui/base/metadata/metadata_impl_macros.h"
 #include "ui/base/models/image_model.h"
 #include "ui/base/resource/resource_bundle.h"
+#include "ui/base/text/bytes_formatting.h"
 #include "ui/base/theme_provider.h"
 #include "ui/base/window_open_disposition.h"
 #include "ui/base/window_open_disposition_utils.h"
@@ -3162,48 +3163,84 @@
 
   // Alert tab states.
   absl::optional<TabAlertState> alert = tabstrip_->GetTabAlertState(index);
-  if (!alert.has_value())
-    return title;
-
-  switch (alert.value()) {
-    case TabAlertState::AUDIO_PLAYING:
-      return l10n_util::GetStringFUTF16(IDS_TAB_AX_LABEL_AUDIO_PLAYING_FORMAT,
-                                        title);
-    case TabAlertState::USB_CONNECTED:
-      return l10n_util::GetStringFUTF16(IDS_TAB_AX_LABEL_USB_CONNECTED_FORMAT,
-                                        title);
-    case TabAlertState::BLUETOOTH_CONNECTED:
-      return l10n_util::GetStringFUTF16(
-          IDS_TAB_AX_LABEL_BLUETOOTH_CONNECTED_FORMAT, title);
-    case TabAlertState::BLUETOOTH_SCAN_ACTIVE:
-      return l10n_util::GetStringFUTF16(
-          IDS_TAB_AX_LABEL_BLUETOOTH_SCAN_ACTIVE_FORMAT, title);
-    case TabAlertState::HID_CONNECTED:
-      return l10n_util::GetStringFUTF16(IDS_TAB_AX_LABEL_HID_CONNECTED_FORMAT,
-                                        title);
-    case TabAlertState::SERIAL_CONNECTED:
-      return l10n_util::GetStringFUTF16(
-          IDS_TAB_AX_LABEL_SERIAL_CONNECTED_FORMAT, title);
-    case TabAlertState::MEDIA_RECORDING:
-      return l10n_util::GetStringFUTF16(IDS_TAB_AX_LABEL_MEDIA_RECORDING_FORMAT,
-                                        title);
-    case TabAlertState::AUDIO_MUTING:
-      return l10n_util::GetStringFUTF16(IDS_TAB_AX_LABEL_AUDIO_MUTING_FORMAT,
-                                        title);
-    case TabAlertState::TAB_CAPTURING:
-      return l10n_util::GetStringFUTF16(IDS_TAB_AX_LABEL_TAB_CAPTURING_FORMAT,
-                                        title);
-    case TabAlertState::PIP_PLAYING:
-      return l10n_util::GetStringFUTF16(IDS_TAB_AX_LABEL_PIP_PLAYING_FORMAT,
-                                        title);
-    case TabAlertState::DESKTOP_CAPTURING:
-      return l10n_util::GetStringFUTF16(
-          IDS_TAB_AX_LABEL_DESKTOP_CAPTURING_FORMAT, title);
-    case TabAlertState::VR_PRESENTING_IN_HEADSET:
-      return l10n_util::GetStringFUTF16(IDS_TAB_AX_LABEL_VR_PRESENTING, title);
+  if (alert.has_value()) {
+    switch (alert.value()) {
+      case TabAlertState::AUDIO_PLAYING:
+        title = l10n_util::GetStringFUTF16(
+            IDS_TAB_AX_LABEL_AUDIO_PLAYING_FORMAT, title);
+        break;
+      case TabAlertState::USB_CONNECTED:
+        title = l10n_util::GetStringFUTF16(
+            IDS_TAB_AX_LABEL_USB_CONNECTED_FORMAT, title);
+        break;
+      case TabAlertState::BLUETOOTH_CONNECTED:
+        title = l10n_util::GetStringFUTF16(
+            IDS_TAB_AX_LABEL_BLUETOOTH_CONNECTED_FORMAT, title);
+        break;
+      case TabAlertState::BLUETOOTH_SCAN_ACTIVE:
+        title = l10n_util::GetStringFUTF16(
+            IDS_TAB_AX_LABEL_BLUETOOTH_SCAN_ACTIVE_FORMAT, title);
+        break;
+      case TabAlertState::HID_CONNECTED:
+        title = l10n_util::GetStringFUTF16(
+            IDS_TAB_AX_LABEL_HID_CONNECTED_FORMAT, title);
+        break;
+      case TabAlertState::SERIAL_CONNECTED:
+        title = l10n_util::GetStringFUTF16(
+            IDS_TAB_AX_LABEL_SERIAL_CONNECTED_FORMAT, title);
+        break;
+      case TabAlertState::MEDIA_RECORDING:
+        title = l10n_util::GetStringFUTF16(
+            IDS_TAB_AX_LABEL_MEDIA_RECORDING_FORMAT, title);
+        break;
+      case TabAlertState::AUDIO_MUTING:
+        title = l10n_util::GetStringFUTF16(IDS_TAB_AX_LABEL_AUDIO_MUTING_FORMAT,
+                                           title);
+        break;
+      case TabAlertState::TAB_CAPTURING:
+        title = l10n_util::GetStringFUTF16(
+            IDS_TAB_AX_LABEL_TAB_CAPTURING_FORMAT, title);
+        break;
+      case TabAlertState::PIP_PLAYING:
+        title = l10n_util::GetStringFUTF16(IDS_TAB_AX_LABEL_PIP_PLAYING_FORMAT,
+                                           title);
+        break;
+      case TabAlertState::DESKTOP_CAPTURING:
+        title = l10n_util::GetStringFUTF16(
+            IDS_TAB_AX_LABEL_DESKTOP_CAPTURING_FORMAT, title);
+        break;
+      case TabAlertState::VR_PRESENTING_IN_HEADSET:
+        title =
+            l10n_util::GetStringFUTF16(IDS_TAB_AX_LABEL_VR_PRESENTING, title);
+        break;
+    }
   }
 
-  NOTREACHED_NORETURN();
+  const TabRendererData& tab_data = tabstrip_->tab_at(index)->data();
+  if (tab_data.should_show_discard_status &&
+      base::FeatureList::IsEnabled(
+          performance_manager::features::kDiscardedTabTreatment)) {
+    title = l10n_util::GetStringFUTF16(IDS_TAB_AX_INACTIVE_TAB, title);
+    if (tab_data.discarded_memory_savings_in_bytes > 0) {
+      title = l10n_util::GetStringFUTF16(
+          IDS_TAB_AX_MEMORY_SAVINGS, title,
+          ui::FormatBytes(tab_data.discarded_memory_savings_in_bytes));
+    }
+  } else if (tab_data.tab_resource_usage &&
+             tab_data.tab_resource_usage->memory_usage_in_bytes() > 0) {
+    const uint64_t memory_used =
+        tab_data.tab_resource_usage->memory_usage_in_bytes();
+    const int message_id =
+        memory_used > static_cast<uint64_t>(
+                          performance_manager::features::
+                              kMemoryUsageInHovercardsHighThresholdBytes.Get())
+            ? IDS_TAB_AX_HIGH_MEMORY_USAGE
+            : IDS_TAB_AX_MEMORY_USAGE;
+    title = l10n_util::GetStringFUTF16(message_id, title,
+                                       ui::FormatBytes(memory_used));
+  }
+
+  return title;
 }
 
 std::vector<views::NativeViewHost*>
@@ -4765,6 +4802,11 @@
   overlay_view_->SetVisible(true);
   InvalidateLayout();
   GetWidget()->GetRootView()->Layout();
+
+#if BUILDFLAG(IS_CHROMEOS)
+  top_container()->SetBackground(
+      views::CreateThemedSolidBackground(ui::kColorFrameActive));
+#endif  // BUILDFLAG(IS_CHROMEOS)
 }
 
 void BrowserView::OnImmersiveRevealEnded() {
@@ -4780,6 +4822,7 @@
   if (AppUsesWindowControlsOverlay()) {
     UpdateWindowControlsOverlayEnabled();
   }
+  top_container()->SetBackground(nullptr);
 #endif  // BUILDFLAG(IS_CHROMEOS)
 }
 
diff --git a/chrome/browser/ui/views/frame/browser_view_browsertest.cc b/chrome/browser/ui/views/frame/browser_view_browsertest.cc
index 6ce4b52..d421b5b 100644
--- a/chrome/browser/ui/views/frame/browser_view_browsertest.cc
+++ b/chrome/browser/ui/views/frame/browser_view_browsertest.cc
@@ -334,27 +334,6 @@
 // accessible title for tab-modal dialogs is not necessary.
 #if !BUILDFLAG(IS_MAC)
 
-// Open a tab-modal dialog and check that the accessible window title is the
-// title of the dialog.
-IN_PROC_BROWSER_TEST_F(BrowserViewTest, GetAccessibleTabModalDialogTitle) {
-  std::u16string window_title =
-      u"about:blank - " + l10n_util::GetStringUTF16(IDS_PRODUCT_NAME);
-  EXPECT_TRUE(base::StartsWith(browser_view()->GetAccessibleWindowTitle(),
-                               window_title, base::CompareCase::SENSITIVE));
-
-  content::WebContents* contents = browser_view()->GetActiveWebContents();
-  auto delegate = std::make_unique<TestTabModalConfirmDialogDelegate>(contents);
-  TestTabModalConfirmDialogDelegate* delegate_observer = delegate.get();
-  TabModalConfirmDialog::Create(std::move(delegate), contents);
-  EXPECT_EQ(browser_view()->GetAccessibleWindowTitle(),
-            delegate_observer->GetTitle());
-
-  delegate_observer->Close();
-
-  EXPECT_TRUE(base::StartsWith(browser_view()->GetAccessibleWindowTitle(),
-                               window_title, base::CompareCase::SENSITIVE));
-}
-
 // Open a tab-modal dialog and check that the accessibility tree only contains
 // the dialog.
 IN_PROC_BROWSER_TEST_F(BrowserViewTest, GetAccessibleTabModalDialogTree) {
diff --git a/chrome/browser/ui/views/frame/browser_view_interactive_uitest.cc b/chrome/browser/ui/views/frame/browser_view_interactive_uitest.cc
index b6e62ab..ee05636 100644
--- a/chrome/browser/ui/views/frame/browser_view_interactive_uitest.cc
+++ b/chrome/browser/ui/views/frame/browser_view_interactive_uitest.cc
@@ -10,10 +10,12 @@
 #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/tab_modal_confirm_dialog.h"
 #include "chrome/browser/ui/ui_features.h"
 #include "chrome/browser/ui/views/location_bar/location_bar_view.h"
 #include "chrome/browser/ui/views/tabs/tab_strip.h"
 #include "chrome/common/pref_names.h"
+#include "chrome/grit/chromium_strings.h"
 #include "chrome/test/base/in_process_browser_test.h"
 #include "chrome/test/base/ui_test_utils.h"
 #include "content/public/browser/web_contents.h"
@@ -39,6 +41,10 @@
   BrowserViewTest(const BrowserViewTest&) = delete;
   BrowserViewTest& operator=(const BrowserViewTest&) = delete;
 
+  BrowserView* browser_view() {
+    return BrowserView::GetBrowserViewForBrowser(browser());
+  }
+
   void SetUpOnMainThread() override {
 #if BUILDFLAG(IS_MAC)
     // Set the preference to true so we expect to see the top view in
@@ -366,3 +372,49 @@
   navigation_watcher.Wait();
   EXPECT_FALSE(browser()->tab_strip_model()->TabsAreLoading());
 }
+
+// On Mac, voiceover treats tab modal dialogs as native windows, so setting an
+// accessible title for tab-modal dialogs is not necessary.
+#if !BUILDFLAG(IS_MAC)
+
+namespace {
+
+class TestTabModalConfirmDialogDelegate : public TabModalConfirmDialogDelegate {
+ public:
+  explicit TestTabModalConfirmDialogDelegate(content::WebContents* contents)
+      : TabModalConfirmDialogDelegate(contents) {}
+
+  TestTabModalConfirmDialogDelegate(const TestTabModalConfirmDialogDelegate&) =
+      delete;
+  TestTabModalConfirmDialogDelegate& operator=(
+      const TestTabModalConfirmDialogDelegate&) = delete;
+
+  std::u16string GetTitle() override { return std::u16string(u"Dialog Title"); }
+  std::u16string GetDialogMessage() override { return std::u16string(); }
+};
+
+}  // namespace
+
+// Open a tab-modal dialog and check that the accessible window title is the
+// title of the dialog. The accessible window title is based on the focused
+// dialog and this dependency on focus is why this is an interactive ui test.
+IN_PROC_BROWSER_TEST_F(BrowserViewTest, GetAccessibleTabModalDialogTitle) {
+  std::u16string window_title =
+      u"about:blank - " + l10n_util::GetStringUTF16(IDS_PRODUCT_NAME);
+  EXPECT_TRUE(base::StartsWith(browser_view()->GetAccessibleWindowTitle(),
+                               window_title, base::CompareCase::SENSITIVE));
+
+  content::WebContents* contents = browser_view()->GetActiveWebContents();
+  auto delegate = std::make_unique<TestTabModalConfirmDialogDelegate>(contents);
+  TestTabModalConfirmDialogDelegate* delegate_observer = delegate.get();
+  TabModalConfirmDialog::Create(std::move(delegate), contents);
+  EXPECT_EQ(browser_view()->GetAccessibleWindowTitle(),
+            delegate_observer->GetTitle());
+
+  delegate_observer->Close();
+
+  EXPECT_TRUE(base::StartsWith(browser_view()->GetAccessibleWindowTitle(),
+                               window_title, base::CompareCase::SENSITIVE));
+}
+
+#endif
diff --git a/chrome/browser/ui/views/frame/tab_strip_region_view.cc b/chrome/browser/ui/views/frame/tab_strip_region_view.cc
index 546c06ed..456c3b8 100644
--- a/chrome/browser/ui/views/frame/tab_strip_region_view.cc
+++ b/chrome/browser/ui/views/frame/tab_strip_region_view.cc
@@ -28,6 +28,12 @@
 #include "ui/base/l10n/l10n_util.h"
 #include "ui/base/metadata/metadata_header_macros.h"
 #include "ui/base/metadata/metadata_impl_macros.h"
+#include "ui/base/ui_base_features.h"
+#include "ui/compositor/layer.h"
+#include "ui/compositor/layer_type.h"
+#include "ui/gfx/geometry/insets.h"
+#include "ui/gfx/geometry/point.h"
+#include "ui/views/accessible_pane_view.h"
 #include "ui/views/border.h"
 #include "ui/views/cascading_property.h"
 #include "ui/views/controls/button/image_button.h"
@@ -40,6 +46,10 @@
 
 namespace {
 
+// TODO (1451400): This const should replace
+// TABSTRIP_REGION_VIEW_CONTROL_PADDING once ChromeRefresh launched.
+constexpr int kCRtabstripRegionViewControlPadding = 6;
+
 class FrameGrabHandle : public views::View {
  public:
   METADATA_HEADER(FrameGrabHandle);
@@ -54,9 +64,22 @@
 BEGIN_METADATA(FrameGrabHandle, views::View)
 END_METADATA
 
+bool ShouldTabSearchRenderBeforeTabStrip() {
+// Mac should have tabsearch on the right side. Windows >= Win10 has the
+// Tab Search button as a FrameCaptionButton, but it still needs to be on the
+// left if it exists.
+#if BUILDFLAG(IS_CHROMEOS) || BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_WIN)
+  return features::IsChromeRefresh2023();
+#else
+  return false;
+#endif
+}
+
 }  // namespace
 
-TabStripRegionView::TabStripRegionView(std::unique_ptr<TabStrip> tab_strip) {
+TabStripRegionView::TabStripRegionView(std::unique_ptr<TabStrip> tab_strip)
+    : render_tab_search_before_tab_strip_(
+          ShouldTabSearchRenderBeforeTabStrip()) {
   views::SetCascadingColorProviderColor(
       this, views::kCascadingBackgroundColor,
       kColorTabBackgroundInactiveFrameInactive);
@@ -65,6 +88,30 @@
   layout_manager_->SetOrientation(views::LayoutOrientation::kHorizontal);
 
   tab_strip_ = tab_strip.get();
+  const Browser* browser = tab_strip_->GetBrowser();
+
+  // Add and configure the TabSearchButton.
+  std::unique_ptr<TabSearchButton> tab_search_button;
+  if (browser && browser->is_type_normal()) {
+    tab_search_button = std::make_unique<TabSearchButton>(tab_strip_);
+    tab_search_button->SetTooltipText(
+        l10n_util::GetStringUTF16(IDS_TOOLTIP_TAB_SEARCH));
+    tab_search_button->SetAccessibleName(
+        l10n_util::GetStringUTF16(IDS_ACCNAME_TAB_SEARCH));
+    tab_search_button->SetProperty(views::kCrossAxisAlignmentKey,
+                                   views::LayoutAlignment::kCenter);
+  }
+
+  if (tab_search_button && render_tab_search_before_tab_strip_) {
+    tab_search_button->SetPaintToLayer();
+    tab_search_button->layer()->SetFillsBoundsOpaquely(false);
+
+    tab_search_button_ = AddChildView(std::move(tab_search_button));
+    // Inset between the tabsearch and tabstrip should be reduced to account for
+    // extra spacing.
+    layout_manager_->SetChildViewIgnoredByLayout(tab_search_button_, true);
+  }
+
   if (base::FeatureList::IsEnabled(features::kScrollableTabStrip)) {
     std::unique_ptr<TabStripScrollContainer> scroll_container =
         std::make_unique<TabStripScrollContainer>(std::move(tab_strip));
@@ -113,11 +160,6 @@
                                views::MaximumFlexSizeRule::kUnbounded)
           .WithOrder(3));
 
-  // This is the margin necessary to ensure correct spacing between right-
-  // aligned control and the end of the TabStripRegionView.
-  const auto control_padding = gfx::Insets::TLBR(
-      0, 0, 0, GetLayoutConstant(TABSTRIP_REGION_VIEW_CONTROL_PADDING));
-
   SetProperty(views::kElementIdentifierKey, kTabStripRegionElementId);
 
 #if BUILDFLAG(IS_CHROMEOS)
@@ -125,22 +167,36 @@
     return;
 #endif
 
-  const Browser* browser = tab_strip_->GetBrowser();
-  if (!browser ||
-      WindowFrameUtil::IsWin10TabSearchCaptionButtonEnabled(browser)) {
-    return;
+  if (browser && tab_search_button &&
+      !WindowFrameUtil::IsWin10TabSearchCaptionButtonEnabled(browser) &&
+      !render_tab_search_before_tab_strip_) {
+    tab_search_button_ = AddChildView(std::move(tab_search_button));
+    if (features::IsChromeRefresh2023()) {
+      tab_search_button_->SetProperty(
+          views::kMarginsKey,
+          gfx::Insets::TLBR(0, 0, kCRtabstripRegionViewControlPadding,
+                            kCRtabstripRegionViewControlPadding));
+    } else {
+      const auto control_padding = gfx::Insets::TLBR(
+          0, 0, 0, GetLayoutConstant(TABSTRIP_REGION_VIEW_CONTROL_PADDING));
+
+      tab_search_button_->SetProperty(views::kMarginsKey, control_padding);
+    }
   }
 
-  if (browser->is_type_normal()) {
-    auto tab_search_button = std::make_unique<TabSearchButton>(tab_strip_);
-    tab_search_button->SetTooltipText(
-        l10n_util::GetStringUTF16(IDS_TOOLTIP_TAB_SEARCH));
-    tab_search_button->SetAccessibleName(
-        l10n_util::GetStringUTF16(IDS_ACCNAME_TAB_SEARCH));
-    tab_search_button->SetProperty(views::kCrossAxisAlignmentKey,
-                                   views::LayoutAlignment::kCenter);
-    tab_search_button_ = AddChildView(std::move(tab_search_button));
-    tab_search_button_->SetProperty(views::kMarginsKey, control_padding);
+  if (tab_search_button_ && render_tab_search_before_tab_strip_) {
+    // The `tab_search_button_` is being laid out manually.
+    CHECK(layout_manager_->IsChildViewIgnoredByLayout(tab_search_button_));
+
+    // Add a margin to the tab_strip_container_ to leave the correct amount of
+    // space for the `tab_search_button_`.
+    gfx::Size tab_search_button_size = tab_search_button_->GetPreferredSize();
+    tab_strip_container_->SetProperty(
+        views::kMarginsKey,
+        gfx::Insets::TLBR(0,
+                          tab_search_button_size.width() +
+                              kCRtabstripRegionViewControlPadding,
+                          0, 0));
   }
 }
 
@@ -192,6 +248,54 @@
   return IsRectInWindowCaption(gfx::Rect(point, gfx::Size(1, 1)));
 }
 
+views::View::Views TabStripRegionView::GetChildrenInZOrder() {
+  views::View::Views children;
+
+  if (tab_strip_container_) {
+    children.emplace_back(tab_strip_container_);
+  }
+
+  if (new_tab_button_) {
+    children.emplace_back(new_tab_button_);
+  }
+
+  if (tab_search_button_) {
+    children.emplace_back(tab_search_button_);
+  }
+
+  if (reserved_grab_handle_space_) {
+    children.emplace_back(reserved_grab_handle_space_);
+  }
+
+  return children;
+}
+
+// The TabSearchButton need bounds that overlap the TabStripContainer, which
+// FlexLayout doesn't currently support. Because of this the TSB bounds are
+// manually calculated.
+void TabStripRegionView::Layout() {
+  views::AccessiblePaneView::Layout();
+
+  if (tab_search_button_ && render_tab_search_before_tab_strip_) {
+    const gfx::Size tab_search_button_size =
+        tab_search_button_->GetPreferredSize();
+
+    const int x = tab_strip_container_->x() - tab_search_button_size.width() +
+                  kCRtabstripRegionViewControlPadding;
+
+    // The y position is measured from the bottom of the tabstrip, and then
+    // pading and button height are removed.
+    const int y = tab_strip_container_->y() + tab_strip_container_->height() -
+                  kCRtabstripRegionViewControlPadding -
+                  tab_search_button_size.height();
+
+    const gfx::Rect tab_search_new_bounds =
+        gfx::Rect(gfx::Point(x, y), tab_search_button_size);
+
+    tab_search_button_->SetBoundsRect(tab_search_new_bounds);
+  }
+}
+
 bool TabStripRegionView::CanDrop(const OSExchangeData& data) {
   return TabDragController::IsSystemDragAndDropSessionRunning() &&
          data.HasCustomFormat(
diff --git a/chrome/browser/ui/views/frame/tab_strip_region_view.h b/chrome/browser/ui/views/frame/tab_strip_region_view.h
index 2dad820..0727267 100644
--- a/chrome/browser/ui/views/frame/tab_strip_region_view.h
+++ b/chrome/browser/ui/views/frame/tab_strip_region_view.h
@@ -49,6 +49,14 @@
   }
 
   // views::View:
+  // The TabSearchButton and NewTabButton may need to be rendered above the
+  // TabStrip, but FlexLayout needs the children to be stored in the correct
+  // order in the view.
+  views::View::Views GetChildrenInZOrder() override;
+
+  // Calls the parent Layout, but in some cases may also need to manually
+  // position the TabSearchButton to layer over the TabStrip.
+  void Layout() override;
 
   // These system drag & drop methods forward the events to TabDragController to
   // support its fallback tab dragging mode in the case where the platform
@@ -85,6 +93,11 @@
   raw_ptr<NewTabButton, DanglingUntriaged> new_tab_button_ = nullptr;
   raw_ptr<TabSearchButton, DanglingUntriaged> tab_search_button_ = nullptr;
 
+  // On some platforms for Chrome Refresh, the TabSearchButton should be
+  // laid out before the TabStrip. Storing this configuration prevents
+  // rechecking the child order on every layout.
+  const bool render_tab_search_before_tab_strip_;
+
   const base::CallbackListSubscription subscription_ =
       ui::TouchUiController::Get()->RegisterCallback(
           base::BindRepeating(&TabStripRegionView::UpdateNewTabButtonBorder,
diff --git a/chrome/browser/ui/views/frame/top_container_view.cc b/chrome/browser/ui/views/frame/top_container_view.cc
index a45e4ab..0332b7d 100644
--- a/chrome/browser/ui/views/frame/top_container_view.cc
+++ b/chrome/browser/ui/views/frame/top_container_view.cc
@@ -18,10 +18,15 @@
   SetProperty(views::kElementIdentifierKey, kTopContainerElementId);
 }
 
-TopContainerView::~TopContainerView() {
-}
+TopContainerView::~TopContainerView() = default;
 
 void TopContainerView::PaintChildren(const views::PaintInfo& paint_info) {
+// For ChromeOS, we don't need to manually call
+// `BrowserNonClientFrameViewChromeOS::Paint` here since it will be triggered by
+// BrowserRootView::PaintChildren() on immersive revealed.
+// TODO (b/287068468): Verify if it's needed on MacOS, once it's verified, we
+// can decide whether keep or remove this function.
+#if !BUILDFLAG(IS_CHROMEOS)
   if (browser_view_->immersive_mode_controller()->IsRevealed()) {
     // Top-views depend on parts of the frame (themes, window title, window
     // controls) being painted underneath them. Clip rect has already been set
@@ -40,6 +45,7 @@
         views::PaintInfo::CreateRootPaintInfo(
             context, browser_view_->frame()->GetFrameView()->size()));
   }
+#endif
   View::PaintChildren(paint_info);
 }
 
diff --git a/chrome/browser/ui/views/omnibox/omnibox_result_view.cc b/chrome/browser/ui/views/omnibox/omnibox_result_view.cc
index 3bfa3ec..829a8e7 100644
--- a/chrome/browser/ui/views/omnibox/omnibox_result_view.cc
+++ b/chrome/browser/ui/views/omnibox/omnibox_result_view.cc
@@ -51,6 +51,7 @@
 #include "ui/gfx/image/image_skia_operations.h"
 #include "ui/gfx/paint_vector_icon.h"
 #include "ui/views/animation/ink_drop.h"
+#include "ui/views/background.h"
 #include "ui/views/controls/button/image_button.h"
 #include "ui/views/controls/button/image_button_factory.h"
 #include "ui/views/controls/focus_ring.h"
@@ -240,6 +241,15 @@
   if (part_state == OmniboxPartState::NORMAL && !prefers_contrast)
     return nullptr;
 
+  if (OmniboxFieldTrial::IsChromeRefreshSuggestHoverFillShapeEnabled()) {
+    views::Radii radii = {
+        .top_right = static_cast<float>(view->height()),
+        .bottom_right = static_cast<float>(view->height()),
+    };
+    return views::CreateThemedRoundedRectBackground(
+        GetOmniboxBackgroundColorId(part_state), radii, 0);
+  }
+
   return views::CreateThemedSolidBackground(
       GetOmniboxBackgroundColorId(part_state));
 }
diff --git a/chrome/browser/ui/views/omnibox/omnibox_row_view.cc b/chrome/browser/ui/views/omnibox/omnibox_row_view.cc
index bbad2d2..fd1c0d4 100644
--- a/chrome/browser/ui/views/omnibox/omnibox_row_view.cc
+++ b/chrome/browser/ui/views/omnibox/omnibox_row_view.cc
@@ -384,14 +384,16 @@
 }
 
 gfx::Insets OmniboxRowView::GetInsets() const {
+  const int right_inset =
+      OmniboxFieldTrial::IsChromeRefreshSuggestHoverFillShapeEnabled() ? 16 : 0;
   // A visible header means this is the start of a new section. Give the section
   // that just ended an extra 4dp of padding. https://crbug.com/1076646
   if (line_ != 0 && header_view_ && header_view_->GetVisible() &&
       !OmniboxFieldTrial::IsChromeRefreshSuggestIconsEnabled()) {
-    return gfx::Insets::TLBR(4, 0, 0, 0);
+    return gfx::Insets::TLBR(4, 0, 0, right_inset);
   }
 
-  return gfx::Insets();
+  return gfx::Insets::TLBR(0, 0, 0, right_inset);
 }
 
 BEGIN_METADATA(OmniboxRowView, views::View)
diff --git a/chrome/browser/ui/views/page_action/pwa_install_view_browsertest.cc b/chrome/browser/ui/views/page_action/pwa_install_view_browsertest.cc
index f7549bce..1788a41 100644
--- a/chrome/browser/ui/views/page_action/pwa_install_view_browsertest.cc
+++ b/chrome/browser/ui/views/page_action/pwa_install_view_browsertest.cc
@@ -781,8 +781,8 @@
   // shown once in an user session.
   web_app::RecordInstallIphIgnored(
       profile()->GetPrefs(),
-      web_app::GenerateAppId(/*manifest_id=*/absl::nullopt,
-                             app_banner_manager_->GetManifestStartUrl()),
+      web_app::GenerateAppId(/*manifest_id_path=*/absl::nullopt,
+                             GetInstallableAppURL()),
       base::Time::Now());
   bool installable = OpenTab(app_url).installable;
   ASSERT_TRUE(installable);
diff --git a/chrome/browser/ui/views/permissions/chip_controller.cc b/chrome/browser/ui/views/permissions/chip_controller.cc
index ec06d99..d5f23d11 100644
--- a/chrome/browser/ui/views/permissions/chip_controller.cc
+++ b/chrome/browser/ui/views/permissions/chip_controller.cc
@@ -114,6 +114,12 @@
   }
 }
 
+void ChipController::OnRequestsFinalized() {
+  if (!is_confirmation_showing_) {
+    ResetPermissionPromptChip();
+  }
+}
+
 void ChipController::OnRequestDecided(
     permissions::PermissionAction permission_action) {
   DCHECK(permission_prompt_model_);
diff --git a/chrome/browser/ui/views/permissions/chip_controller.h b/chrome/browser/ui/views/permissions/chip_controller.h
index a2858a55..b917291 100644
--- a/chrome/browser/ui/views/permissions/chip_controller.h
+++ b/chrome/browser/ui/views/permissions/chip_controller.h
@@ -49,6 +49,7 @@
   // PermissionRequestManager::Observer:
   void OnPermissionRequestManagerDestructed() override;
   void OnTabVisibilityChanged(content::Visibility visibility) override;
+  void OnRequestsFinalized() override;
 
   // OnBubbleRemoved only triggers when a request chip (bubble) is removed, when
   // the user navigates while a confirmation chip is showing, the request is
diff --git a/chrome/browser/ui/views/side_panel/search_companion/companion_live_test.cc b/chrome/browser/ui/views/side_panel/search_companion/companion_live_test.cc
index 573b759..eef804d 100644
--- a/chrome/browser/ui/views/side_panel/search_companion/companion_live_test.cc
+++ b/chrome/browser/ui/views/side_panel/search_companion/companion_live_test.cc
@@ -31,9 +31,12 @@
 #include "components/signin/core/browser/account_reconcilor.h"
 #include "components/signin/public/identity_manager/identity_manager.h"
 #include "components/signin/public/identity_manager/identity_test_utils.h"
+#include "components/sync/service/sync_service.h"
 #include "content/public/browser/web_contents.h"
 #include "content/public/test/browser_test.h"
 #include "content/public/test/test_navigation_observer.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
 #include "ui/compositor/scoped_animation_duration_scale_mode.h"
 
 namespace signin::test {
@@ -59,6 +62,10 @@
     return SidePanelUtil::GetSidePanelCoordinatorForBrowser(browser());
   }
 
+  syncer::SyncService* sync_service() {
+    return signin::test::sync_service(browser());
+  }
+
   SignInFunctions sign_in_functions = SignInFunctions(
       base::BindLambdaForTesting(
           [this]() -> Browser* { return this->browser(); }),
@@ -87,6 +94,21 @@
     return content::EvalJs(iframe, code);
   }
 
+  ::testing::AssertionResult ExecJs(const std::string& code) {
+    // Execute test in iframe.
+    content::RenderFrameHost* iframe =
+        content::ChildFrameAt(GetCompanionWebContents(browser()), 0);
+
+    return content::ExecJs(iframe, code);
+  }
+
+  void ClickButtonByAriaLabel(const std::string& aria_label) {
+    // Clicks a button in the side panel by its aria-label attribute.
+    std::string js_string = "document.querySelectorAll('button[aria-label=\"" +
+                            aria_label + "\"]')[0].click();";
+    EXPECT_TRUE(ExecJs(js_string));
+  }
+
   void WaitForCompanionToBeLoaded() {
     content::WebContents* companion_web_contents =
         GetCompanionWebContents(browser());
@@ -97,6 +119,58 @@
     nav_observer.Wait();
   }
 
+  void WaitForCompanionIframeReload() {
+    content::WebContents* companion_web_contents =
+        GetCompanionWebContents(browser());
+    EXPECT_TRUE(companion_web_contents);
+
+    // Wait for the navigations in the inner iframe to complete.
+    content::TestNavigationObserver nav_observer(companion_web_contents, 1);
+    nav_observer.Wait();
+  }
+
+  void WaitForHistogram(const std::string& histogram_name) {
+    // Continue if histogram was already recorded.
+    if (base::StatisticsRecorder::FindHistogram(histogram_name)) {
+      return;
+    }
+
+    // Else, wait until the histogram is recorded.
+    base::RunLoop run_loop;
+    auto histogram_observer = std::make_unique<
+        base::StatisticsRecorder::ScopedHistogramSampleObserver>(
+        histogram_name,
+        base::BindLambdaForTesting(
+            [&](const char* histogram_name, uint64_t name_hash,
+                base::HistogramBase::Sample sample) { run_loop.Quit(); }));
+    run_loop.Run();
+  }
+
+  void WaitForHistogramSample(const std::string& histogram_name,
+                              base::HistogramBase::Sample expected_sample) {
+    // Continue if histogram sample was already recorded.
+    if (base::StatisticsRecorder::FindHistogram(histogram_name) &&
+        histogram_tester_->GetBucketCount(histogram_name, expected_sample)) {
+      return;
+    }
+
+    // Else, wait until a new sample is recorded for the histogram.
+    base::RunLoop run_loop;
+    auto histogram_observer = std::make_unique<
+        base::StatisticsRecorder::ScopedHistogramSampleObserver>(
+        histogram_name,
+        base::BindLambdaForTesting(
+            [&](const char* histogram_name, uint64_t name_hash,
+                base::HistogramBase::Sample sample) { run_loop.Quit(); }));
+    run_loop.Run();
+  }
+
+  void WaitForTabCount(int expected) {
+    while (browser()->tab_strip_model()->count() != expected) {
+      base::RunLoop().RunUntilIdle();
+    }
+  }
+
   void EnableMsbb(bool enable_msbb) {
     if (enable_msbb) {
       base::CommandLine::ForCurrentProcess()->AppendSwitch(
@@ -117,23 +191,6 @@
     EnableMsbb(true);
   }
 
-  void WaitForHistogram(const std::string& histogram_name) {
-    // Continue if histogram was already recorded.
-    if (base::StatisticsRecorder::FindHistogram(histogram_name)) {
-      return;
-    }
-
-    // Else, wait until the histogram is recorded.
-    base::RunLoop run_loop;
-    auto histogram_observer = std::make_unique<
-        base::StatisticsRecorder::ScopedHistogramSampleObserver>(
-        histogram_name,
-        base::BindLambdaForTesting(
-            [&](const char* histogram_name, uint64_t name_hash,
-                base::HistogramBase::Sample sample) { run_loop.Quit(); }));
-    run_loop.Run();
-  }
-
  protected:
   base::test::ScopedFeatureList feature_list_;
   std::unique_ptr<base::HistogramTester> histogram_tester_;
@@ -142,12 +199,14 @@
 // Test will only run when passed the --run-live-tests flag. To run, use
 // browser_tests --gtest_filter=CompanionLiveTest.* --run-live-tests
 IN_PROC_BROWSER_TEST_F(CompanionLiveTest, InitialNavigation) {
-  // Navigate to a website, open side panel, and ensure that companion loads.
+  // Navigate to a website, open the side panel, and verify that companion
+  // experiments appear in the side panel for an opted in account.
   TestAccount ta;
-  // Intelligence test account has lens server access.
+  // Test account is opted in to labs.
   CHECK(GetTestAccountsUtil()->GetAccount("INTELLIGENCE_ACCOUNT", ta));
   sign_in_functions.SignInFromWeb(ta, 0);
 
+  // Navigate to google.com and open side panel.
   const GURL google_url("https://google.com/");
   ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), google_url));
   ASSERT_EQ(side_panel_coordinator()->GetCurrentEntryId(), absl::nullopt);
@@ -158,6 +217,134 @@
   WaitForCompanionToBeLoaded();
   EXPECT_EQ(side_panel_coordinator()->GetCurrentEntryId(),
             SidePanelEntry::Id::kSearchCompanion);
+
+  // Verify that CQ loads.
+  WaitForHistogram("Companion.CQ.Shown");
+  histogram_tester_->ExpectBucketCount("Companion.CQ.Shown",
+                                       /*sample=*/true, /*expected_count=*/1);
+  // Close the side panel.
+  side_panel_coordinator()->Close();
+  WaitForHistogram("SidePanel.OpenDuration");
+}
+
+IN_PROC_BROWSER_TEST_F(CompanionLiveTest, InitialNavigationNotOptedIn) {
+  // Navigate to a website, open the side panel, and verify that companion
+  // experiments do not appear in the side panel for a non-opted in account.
+  TestAccount ta;
+  // Test account has not opted in to labs.
+  CHECK(GetTestAccountsUtil()->GetAccount("INTELLIGENCE_ACCOUNT_2", ta));
+  sign_in_functions.SignInFromWeb(ta, 0);
+
+  // Ensure sync is on.
+  sign_in_functions.TurnOnSync(ta, 0);
+  EXPECT_TRUE(sync_service()->IsSyncFeatureEnabled());
+
+  // Navigate to google.com and open side panel.
+  const GURL google_url("https://google.com/");
+  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), google_url));
+  ASSERT_EQ(side_panel_coordinator()->GetCurrentEntryId(), absl::nullopt);
+
+  side_panel_coordinator()->Show(SidePanelEntry::Id::kSearchCompanion);
+  EXPECT_TRUE(side_panel_coordinator()->IsSidePanelShowing());
+  WaitForCompanionToBeLoaded();
+
+  // Verify the kExpsShown promo event is shown and that CQ does not load.
+  WaitForHistogramSample("Companion.PromoEvent",
+                         (int)companion::PromoEvent::kExpsShown);
+  histogram_tester_->ExpectBucketCount(
+      "Companion.PromoEvent",
+      /*sample=*/companion::PromoEvent::kExpsShown, /*expected_count=*/1);
+  histogram_tester_->ExpectTotalCount("Companion.CQ.Shown", 0);
+
+  // Close the side panel.
+  side_panel_coordinator()->Close();
+  WaitForHistogram("SidePanel.OpenDuration");
+}
+
+IN_PROC_BROWSER_TEST_F(CompanionLiveTest, InitialNavigationLoggedOut) {
+  // Navigate to a website, open the side panel, and ensure the sign in promo is
+  // shown for a logged out account. Verify the sign-in promo functionality.
+  EnableMsbb(false);
+  const GURL google_url("https://google.com/");
+  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), google_url));
+  ASSERT_EQ(side_panel_coordinator()->GetCurrentEntryId(), absl::nullopt);
+
+  side_panel_coordinator()->Show(SidePanelEntry::Id::kSearchCompanion);
+  EXPECT_TRUE(side_panel_coordinator()->IsSidePanelShowing());
+
+  WaitForCompanionToBeLoaded();
+  EXPECT_EQ(side_panel_coordinator()->GetCurrentEntryId(),
+            SidePanelEntry::Id::kSearchCompanion);
+
+  // Expect the sign-in promo and no CQ shown.
+  WaitForHistogramSample("Companion.PromoEvent",
+                         (int)companion::PromoEvent::kSignInShown);
+  histogram_tester_->ExpectBucketCount(
+      "Companion.PromoEvent",
+      /*sample=*/companion::PromoEvent::kSignInShown, /*expected_count=*/1);
+  histogram_tester_->ExpectTotalCount("Companion.CQ.Shown", 0);
+
+  // Click on sign-in promo and expect sign in site in new tab.
+  int tab_count = browser()->tab_strip_model()->count();
+  ClickButtonByAriaLabel("Sign in button");
+  WaitForTabCount(tab_count + 1);
+
+  // Wait for page to load.
+  content::TestNavigationObserver nav_observer(web_contents(), 1);
+  nav_observer.Wait();
+
+  // Verify that the sign-in page appears and PromoEvent histogram is updated.
+  EXPECT_THAT(web_contents()->GetLastCommittedURL().spec(),
+              ::testing::HasSubstr("accounts.google.com/signin"));
+  WaitForHistogramSample("Companion.PromoEvent",
+                         (int)companion::PromoEvent::kSignInAccepted);
+  histogram_tester_->ExpectBucketCount(
+      "Companion.PromoEvent",
+      /*sample=*/companion::PromoEvent::kSignInAccepted,
+      /*expected_count=*/1);
+
+  // Close the side panel.
+  side_panel_coordinator()->Close();
+  WaitForHistogram("SidePanel.OpenDuration");
+}
+
+IN_PROC_BROWSER_TEST_F(CompanionLiveTest, SearchBox) {
+  // Navigate to a website, open the side panel, and ensure that the multi-modal
+  // search box functions as intended.
+  TestAccount ta;
+  // Test account has opted in to labs.
+  CHECK(GetTestAccountsUtil()->GetAccount("INTELLIGENCE_ACCOUNT", ta));
+  sign_in_functions.SignInFromWeb(ta, 0);
+  const GURL google_url("https://google.com/");
+  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), google_url));
+  ASSERT_EQ(side_panel_coordinator()->GetCurrentEntryId(), absl::nullopt);
+
+  side_panel_coordinator()->Show(SidePanelEntry::Id::kSearchCompanion);
+  EXPECT_TRUE(side_panel_coordinator()->IsSidePanelShowing());
+  WaitForCompanionToBeLoaded();
+
+  // Ensure multimodal search box is present.
+  std::string search("Search");
+  ASSERT_EQ(EvalJs("document.querySelectorAll('input')[0].placeholder"),
+            search);
+
+  // Conduct a side search.
+  ExecJs("document.querySelectorAll('input')[0].value = 'test search';");
+  ClickButtonByAriaLabel("Search");
+  WaitForHistogram("Companion.SearchBox.Clicked");
+  histogram_tester_->ExpectBucketCount("Companion.SearchBox.Clicked",
+                                       /*sample=*/true, /*expected_count=*/1);
+
+  // Return to zero state.
+  ClickButtonByAriaLabel("Back");
+  EXPECT_EQ(side_panel_coordinator()->GetCurrentEntryId(),
+            SidePanelEntry::Id::kSearchCompanion);
+
+  // Click the region search button.
+  ClickButtonByAriaLabel("Search by image");
+  WaitForHistogram("Companion.RegionSearch.Clicked");
+  histogram_tester_->ExpectBucketCount("Companion.RegionSearch.Clicked",
+                                       /*sample=*/true, /*expected_count=*/1);
 }
 
 }  // namespace signin::test
diff --git a/chrome/browser/ui/views/toolbar/app_menu.cc b/chrome/browser/ui/views/toolbar/app_menu.cc
index e854c8c..718a60f 100644
--- a/chrome/browser/ui/views/toolbar/app_menu.cc
+++ b/chrome/browser/ui/views/toolbar/app_menu.cc
@@ -28,8 +28,6 @@
 #include "chrome/browser/browser_process.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/profiles/profile_attributes_entry.h"
-#include "chrome/browser/profiles/profile_attributes_storage.h"
-#include "chrome/browser/profiles/profile_manager.h"
 #include "chrome/browser/search/search.h"
 #include "chrome/browser/sharing_hub/sharing_hub_features.h"
 #include "chrome/browser/ui/bookmarks/bookmark_stats.h"
@@ -38,6 +36,7 @@
 #include "chrome/browser/ui/browser_window.h"
 #include "chrome/browser/ui/global_error/global_error_service_factory.h"
 #include "chrome/browser/ui/layout_constants.h"
+#include "chrome/browser/ui/profile_view_utils.h"
 #include "chrome/browser/ui/tabs/tab_strip_model.h"
 #include "chrome/browser/ui/toolbar/app_menu_model.h"
 #include "chrome/browser/ui/ui_features.h"
@@ -129,8 +128,9 @@
 
 // Returns true if |command_id| identifies a bookmark menu item.
 bool IsBookmarkCommand(int command_id) {
-  return command_id >= IDC_FIRST_UNBOUNDED_MENU &&
-         (command_id % AppMenuModel::kNumUnboundedMenuTypes == 0);
+  return command_id == IDC_SHOW_BOOKMARK_SIDE_PANEL ||
+         (command_id >= IDC_FIRST_UNBOUNDED_MENU &&
+          (command_id % AppMenuModel::kNumUnboundedMenuTypes == 0));
 }
 
 // Returns true if |command_id| identifies a recent tabs menu item.
@@ -1257,9 +1257,7 @@
           item->SetSelectedColorId(
               ui::kColorAppMenuProfileRowBackgroundHovered);
           ProfileAttributesEntry* profile_attributes =
-              g_browser_process->profile_manager()
-                  ->GetProfileAttributesStorage()
-                  .GetProfileAttributesWithPath(browser_->profile()->GetPath());
+              GetProfileAttributesFromProfile(browser_->profile());
           views::Label* profile_chip_label;
           views::LayoutProvider* layout_provider = views::LayoutProvider::Get();
           const MenuConfig& config = MenuConfig::instance();
diff --git a/chrome/browser/ui/views/toolbar/app_menu_browsertest.cc b/chrome/browser/ui/views/toolbar/app_menu_browsertest.cc
index a2ef79c..d19e4608 100644
--- a/chrome/browser/ui/views/toolbar/app_menu_browsertest.cc
+++ b/chrome/browser/ui/views/toolbar/app_menu_browsertest.cc
@@ -18,6 +18,7 @@
 #include "build/branding_buildflags.h"
 #include "build/buildflag.h"
 #include "build/chromeos_buildflags.h"
+#include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/sessions/tab_restore_service_factory.h"
 #include "chrome/browser/sessions/tab_restore_service_load_waiter.h"
 #include "chrome/browser/signin/identity_manager_factory.h"
@@ -33,6 +34,7 @@
 #include "chrome/browser/ui/views/toolbar/toolbar_view.h"
 #include "chrome/test/base/ui_test_utils.h"
 #include "components/password_manager/core/common/password_manager_features.h"
+#include "components/signin/public/base/signin_pref_names.h"
 #include "components/signin/public/identity_manager/identity_manager.h"
 #include "components/signin/public/identity_manager/identity_test_utils.h"
 #include "content/public/test/browser_test.h"
@@ -96,6 +98,7 @@
         {"find_and_edit", IDC_FIND_AND_EDIT_MENU},
         {"save_and_share", IDC_SAVE_AND_SHARE_MENU},
         {"profile_menu_in_app", IDC_PROFILE_MENU_IN_APP_MENU},
+        {"signin_not_allowed", IDC_PROFILE_MENU_IN_APP_MENU},
   });
   const auto* const id_entry = kSubmenus.find(name);
   if (id_entry == kSubmenus.end()) {
@@ -209,6 +212,10 @@
   ShowAndVerifyUi();
 }
 
+IN_PROC_BROWSER_TEST_F(AppMenuBrowserTestRefreshOnly, InvokeUi_main) {
+  ShowAndVerifyUi();
+}
+
 IN_PROC_BROWSER_TEST_F(AppMenuBrowserTestRefreshOnly, InvokeUi_main_guest) {
 // TODO(crbug.com/1427667): ChromeOS specific profile logic still needs to be
 // updated, setup this test for a Guest user session with appropriate command
@@ -270,5 +277,12 @@
                             signin::ConsentLevel::kSignin);
   ShowAndVerifyUi();
 }
+
+IN_PROC_BROWSER_TEST_F(AppMenuBrowserTestRefreshOnly,
+                       InvokeUi_signin_not_allowed) {
+  browser()->profile()->GetPrefs()->SetBoolean(prefs::kSigninAllowed, false);
+  ShowAndVerifyUi();
+}
+
 #endif
 }  // namespace
diff --git a/chrome/browser/ui/views/toolbar/toolbar_button.cc b/chrome/browser/ui/views/toolbar/toolbar_button.cc
index d69270a..045d48d 100644
--- a/chrome/browser/ui/views/toolbar/toolbar_button.cc
+++ b/chrome/browser/ui/views/toolbar/toolbar_button.cc
@@ -60,84 +60,6 @@
 constexpr int kBorderThicknessDpWithLabel = 1;
 constexpr int kBorderThicknessDpWithoutLabel = 2;
 
-// Cycle duration of ink drop pulsing animation used for in-product help.
-constexpr base::TimeDelta kFeaturePromoPulseDuration = base::Milliseconds(800);
-
-// Max inset for pulsing animation.
-constexpr float kFeaturePromoPulseInsetDip = 3.0f;
-
-// An InkDropMask used to animate the size of the BrowserAppMenuButton's ink
-// drop. This is used when showing in-product help.
-class PulsingInkDropMask : public views::AnimationDelegateViews,
-                           public views::InkDropMask {
- public:
-  PulsingInkDropMask(views::View* layer_container,
-                     const gfx::Size& layer_size,
-                     const gfx::Insets& margins,
-                     float normal_corner_radius,
-                     float max_inset)
-      : AnimationDelegateViews(layer_container),
-        views::InkDropMask(layer_size),
-        layer_container_(layer_container),
-        margins_(margins),
-        normal_corner_radius_(normal_corner_radius),
-        max_inset_(max_inset),
-        throb_animation_(this) {
-    throb_animation_.SetThrobDuration(kFeaturePromoPulseDuration);
-    throb_animation_.StartThrobbing(-1);
-  }
-
- private:
-  // views::InkDropMask:
-  void OnPaintLayer(const ui::PaintContext& context) override {
-    cc::PaintFlags flags;
-    flags.setStyle(cc::PaintFlags::kFill_Style);
-    flags.setAntiAlias(true);
-
-    ui::PaintRecorder recorder(context, layer()->size());
-
-    gfx::RectF bounds(layer()->bounds());
-    bounds.Inset(gfx::InsetsF(margins_));
-
-    const float current_inset =
-        throb_animation_.CurrentValueBetween(0.0f, max_inset_);
-    bounds.Inset(gfx::InsetsF(current_inset));
-    const float corner_radius = normal_corner_radius_ - current_inset;
-
-    recorder.canvas()->DrawRoundRect(bounds, corner_radius, flags);
-  }
-
-  // views::AnimationDelegateViews:
-  void AnimationProgressed(const gfx::Animation* animation) override {
-    DCHECK_EQ(animation, &throb_animation_);
-    layer()->SchedulePaint(gfx::Rect(layer()->size()));
-
-    // This is a workaround for crbug.com/935808: for scale factors >1,
-    // invalidating the mask layer doesn't cause the whole layer to be repainted
-    // on screen. TODO(crbug.com/935808): remove this workaround once the bug is
-    // fixed.
-    layer_container_->SchedulePaint();
-  }
-
-  // The View that contains the InkDrop layer we're masking. This must outlive
-  // our instance.
-  const raw_ptr<views::View> layer_container_;
-
-  // Margins between the layer bounds and the visible ink drop. We use this
-  // because sometimes the View we're masking is larger than the ink drop we
-  // want to show.
-  const gfx::Insets margins_;
-
-  // Normal corner radius of the ink drop without animation. This is also the
-  // corner radius at the largest instant of the animation.
-  const float normal_corner_radius_;
-
-  // Max inset, used at the smallest instant of the animation.
-  const float max_inset_;
-
-  gfx::ThrobAnimation throb_animation_;
-};
-
 }  // namespace
 
 ToolbarButton::ToolbarButton(PressedCallback callback)
@@ -158,39 +80,6 @@
 
   set_context_menu_controller(this);
 
-  views::InkDrop::Get(this)->SetCreateMaskCallback(base::BindRepeating(
-      [](ToolbarButton* host) -> std::unique_ptr<views::InkDropMask> {
-        if (host->has_in_product_help_promo_) {
-          // This gets the latest ink drop insets. |SetTrailingMargin()| is
-          // called whenever our margins change (i.e. due to the window
-          // maximizing or minimizing) and updates our internal padding property
-          // accordingly.
-          const gfx::Insets ink_drop_insets = GetToolbarInkDropInsets(host);
-          const float corner_radius = (host->height() - ink_drop_insets.top() -
-                                       ink_drop_insets.bottom()) /
-                                      2.0f;
-          return std::make_unique<PulsingInkDropMask>(
-              host->ink_drop_container(), host->size(), ink_drop_insets,
-              corner_radius, kFeaturePromoPulseInsetDip);
-        }
-        return std::make_unique<views::PathInkDropMask>(host->size(),
-                                                        GetHighlightPath(host));
-      },
-      this));
-  views::InkDrop::Get(this)->SetBaseColorCallback(base::BindRepeating(
-      [](ToolbarButton* host) {
-        if (host->has_in_product_help_promo_) {
-          return host->GetColorProvider()->GetColor(
-              kColorToolbarFeaturePromoHighlight);
-        }
-        absl::optional<SkColor> drop_base_color =
-            host->highlight_color_animation_.GetInkDropBaseColor();
-        if (drop_base_color)
-          return *drop_base_color;
-        return GetToolbarInkDropBaseColor(host);
-      },
-      this));
-
   // Make sure icons are flipped by default so that back, forward, etc. follows
   // UI direction.
   SetFlipCanvasOnPaintForRTLUI(true);
@@ -555,6 +444,8 @@
 
 std::u16string ToolbarButton::GetTooltipText(const gfx::Point& p) const {
   // Suppress tooltip when IPH is showing.
+  // TODO(crbug.com/1419653): Investigate if we should suppress tooltip for all
+  // Buttons rather than just ToolbarButtons when IPH is on.
   return has_in_product_help_promo_ ? std::u16string()
                                     : views::LabelButton::GetTooltipText(p);
 }
@@ -571,44 +462,11 @@
 
 void ToolbarButton::AfterPropertyChange(const void* key, int64_t old_value) {
   View::AfterPropertyChange(key, old_value);
-  if (key == user_education::kHasInProductHelpPromoKey)
-    SetHasInProductHelpPromo(
-        GetProperty(user_education::kHasInProductHelpPromoKey));
-}
-
-void ToolbarButton::SetHasInProductHelpPromo(bool has_in_product_help_promo) {
-  if (has_in_product_help_promo_ == has_in_product_help_promo)
-    return;
-
-  has_in_product_help_promo_ = has_in_product_help_promo;
-
-  // We call SetBaseColorCallback() and SetCreateMaskCallback(),
-  // returning the promo values if we are showing an in-product help promo.
-  // Calling HostSizeChanged() will force the new mask and color to be fetched.
-  //
-  // TODO(collinbaker): Consider adding explicit way to recreate mask instead
-  // of relying on HostSizeChanged() to do so.
-  views::InkDrop::Get(this)->GetInkDrop()->HostSizeChanged(size());
-
-  views::InkDropState next_state;
-  if (has_in_product_help_promo_ ||
-      (ShouldShowInkdropAfterIphInteraction() && GetVisible())) {
-    // If we are showing a promo, we must use the ACTIVATED state to show the
-    // highlight. Otherwise, if the menu is currently showing, we need to keep
-    // the ink drop in the ACTIVATED state.
-    next_state = views::InkDropState::ACTIVATED;
-  } else {
-    // If we are not showing a promo and the menu is hidden, we use the
-    // DEACTIVATED state.
-    next_state = views::InkDropState::DEACTIVATED;
-    // TODO(collinbaker): this is brittle since we don't know if something
-    // else should keep this ACTIVATED or in some other state. Consider adding
-    // code to track the correct state and restore to that.
+  if (key == user_education::kHasInProductHelpPromoKey) {
+    has_in_product_help_promo_ =
+        GetProperty(user_education::kHasInProductHelpPromoKey);
+    UpdateIcon();
   }
-  views::InkDrop::Get(this)->GetInkDrop()->AnimateToState(next_state);
-
-  UpdateIcon();
-  SchedulePaint();
 }
 
 bool ToolbarButton::ShouldShowMenu() {
diff --git a/chrome/browser/ui/views/toolbar/toolbar_button.h b/chrome/browser/ui/views/toolbar/toolbar_button.h
index 98611b8..35ef7d8 100644
--- a/chrome/browser/ui/views/toolbar/toolbar_button.h
+++ b/chrome/browser/ui/views/toolbar/toolbar_button.h
@@ -134,6 +134,8 @@
 
   // Returns if the button inkdrop should persist after the user interacts with
   // IPH for the button. Override this to change default behavior.
+  // TODO(crbug.com/1419653): Investigate if this is still needed and if so how
+  // it can be applied to all Buttons rather than just ToolbarButtons.
   virtual bool ShouldShowInkdropAfterIphInteraction();
 
   // Function to show the dropdown menu.
@@ -274,6 +276,7 @@
   const bool trigger_menu_on_long_press_;
 
   // Determines whether to highlight the button for in-product help.
+  // TODO(crbug.com/1419653): Remove this member after issue is addressed.
   bool has_in_product_help_promo_ = false;
 
   // Y position of mouse when left mouse button is pressed.
diff --git a/chrome/browser/ui/views/webid/fedcm_account_selection_view_desktop.cc b/chrome/browser/ui/views/webid/fedcm_account_selection_view_desktop.cc
index 723535cc..0575b1fa 100644
--- a/chrome/browser/ui/views/webid/fedcm_account_selection_view_desktop.cc
+++ b/chrome/browser/ui/views/webid/fedcm_account_selection_view_desktop.cc
@@ -176,7 +176,7 @@
       base::UTF8ToUTF16(top_frame_etld_plus_one), iframe_etld_plus_one_u16,
       base::UTF8ToUTF16(idp_etld_plus_one), idp_metadata);
 
-  if (create_bubble) {
+  if (create_bubble || should_show_bubble_widget_) {
     bubble_widget_->Show();
     input_protector_->VisibilityChanged(true);
   }
@@ -374,6 +374,9 @@
 
 void FedCmAccountSelectionView::OnSigninToIdP() {
   delegate_->OnSigninToIdP();
+  is_mismatch_continue_clicked_ = true;
+  UMA_HISTOGRAM_ENUMERATION("Blink.FedCm.IdpSigninStatus.MismatchDialogResult",
+                            MismatchDialogResult::kContinued);
 }
 
 content::WebContents* FedCmAccountSelectionView::ShowModalDialog(
@@ -473,4 +476,15 @@
 
   if (notify_delegate_of_dismiss_)
     delegate_->OnDismiss(dismiss_reason);
+
+  // Check is_mismatch_continue_clicked_ to ensure we don't record this metric
+  // after MismatchDialogResult::kContinued has been recorded.
+  if (state_ == State::IDP_SIGNIN_STATUS_MISMATCH &&
+      !is_mismatch_continue_clicked_) {
+    UMA_HISTOGRAM_ENUMERATION(
+        "Blink.FedCm.IdpSigninStatus.MismatchDialogResult",
+        dismiss_reason == DismissReason::kCloseButton
+            ? MismatchDialogResult::kDismissedByCloseIcon
+            : MismatchDialogResult::kDismissedForOtherReasons);
+  }
 }
diff --git a/chrome/browser/ui/views/webid/fedcm_account_selection_view_desktop.h b/chrome/browser/ui/views/webid/fedcm_account_selection_view_desktop.h
index fdee3c0..0c2d2ae 100644
--- a/chrome/browser/ui/views/webid/fedcm_account_selection_view_desktop.h
+++ b/chrome/browser/ui/views/webid/fedcm_account_selection_view_desktop.h
@@ -101,6 +101,17 @@
   virtual const AccountSelectionBubbleViewInterface* GetBubbleView() const;
 
  private:
+  FRIEND_TEST_ALL_PREFIXES(FedCmAccountSelectionViewDesktopTest,
+                           MismatchDialogDismissedByCloseIconMetric);
+  FRIEND_TEST_ALL_PREFIXES(FedCmAccountSelectionViewDesktopTest,
+                           MismatchDialogDismissedForOtherReasonsMetric);
+  FRIEND_TEST_ALL_PREFIXES(FedCmAccountSelectionViewDesktopTest,
+                           MismatchDialogContinueClickedMetric);
+  FRIEND_TEST_ALL_PREFIXES(FedCmAccountSelectionViewDesktopTest,
+                           MismatchDialogDestroyedMetric);
+  FRIEND_TEST_ALL_PREFIXES(FedCmAccountSelectionViewDesktopTest,
+                           MismatchDialogContinueClickedThenDestroyedMetric);
+
   enum class State {
     // User is shown message that they are not currently signed-in to IdP.
     // Dialog has button to sign-in to IdP.
@@ -123,6 +134,18 @@
     AUTO_REAUTHN
   };
 
+  // This enum describes the outcome of the mismatch dialog and is used for
+  // histograms. Do not remove or modify existing values, but you may add new
+  // values at the end. This enum should be kept in sync with
+  // FedCmMismatchDialogResult in tools/metrics/histograms/enums.xml.
+  enum class MismatchDialogResult {
+    kContinued,
+    kDismissedByCloseIcon,
+    kDismissedForOtherReasons,
+
+    kMaxValue = kDismissedForOtherReasons
+  };
+
   // views::WidgetObserver:
   void OnWidgetDestroying(views::Widget* widget) override;
 
@@ -190,6 +213,15 @@
   // IdentityProvider.close() was called.
   bool should_destroy_bubble_widget_{true};
 
+  // Whether the "Continue" button on the mismatch dialog is clicked. Once the
+  // "Continue" button is clicked, a pop-up window is shown for the user to sign
+  // in to an IDP. The mismatch dialog is hidden until it has been updated into
+  // an accounts dialog, which occurs after the user completes the sign in flow.
+  // If the user closes the page with the hidden mismatch dialog before
+  // completing the flow, this boolean prevents us from double counting the
+  // Blink.FedCm.IdpSigninStatus.MismatchDialogResult metric.
+  bool is_mismatch_continue_clicked_{false};
+
   base::WeakPtrFactory<FedCmAccountSelectionView> weak_ptr_factory_{this};
 };
 
diff --git a/chrome/browser/ui/views/webid/fedcm_account_selection_view_desktop_unittest.cc b/chrome/browser/ui/views/webid/fedcm_account_selection_view_desktop_unittest.cc
index e21803f..4a03d7d8 100644
--- a/chrome/browser/ui/views/webid/fedcm_account_selection_view_desktop_unittest.cc
+++ b/chrome/browser/ui/views/webid/fedcm_account_selection_view_desktop_unittest.cc
@@ -538,6 +538,110 @@
       static_cast<int>(FedCmAccountSelectionView::SheetType::AUTO_REAUTHN), 1);
 }
 
+// Tests that when the mismatch dialog is closed through the close icon, the
+// relevant metric is recorded.
+TEST_F(FedCmAccountSelectionViewDesktopTest,
+       MismatchDialogDismissedByCloseIconMetric) {
+  std::unique_ptr<TestFedCmAccountSelectionView> controller =
+      CreateAndShowFailureDialog();
+  histogram_tester_.ExpectTotalCount(
+      "Blink.FedCm.IdpSigninStatus.MismatchDialogResult", 0);
+
+  // Emulate user clicking the close icon.
+  widget_->CloseWithReason(views::Widget::ClosedReason::kCloseButtonClicked);
+  controller->OnWidgetDestroying(widget_.get());
+
+  histogram_tester_.ExpectUniqueSample(
+      "Blink.FedCm.IdpSigninStatus.MismatchDialogResult",
+      static_cast<int>(FedCmAccountSelectionView::MismatchDialogResult::
+                           kDismissedByCloseIcon),
+      1);
+}
+
+// Tests that when the mismatch dialog is closed through means other than the
+// close icon, the relevant metric is recorded.
+TEST_F(FedCmAccountSelectionViewDesktopTest,
+       MismatchDialogDismissedForOtherReasonsMetric) {
+  std::unique_ptr<TestFedCmAccountSelectionView> controller =
+      CreateAndShowFailureDialog();
+  histogram_tester_.ExpectTotalCount(
+      "Blink.FedCm.IdpSigninStatus.MismatchDialogResult", 0);
+
+  // Emulate user closing the mismatch dialog for an unspecified reason.
+  widget_->CloseWithReason(views::Widget::ClosedReason::kUnspecified);
+  controller->OnWidgetDestroying(widget_.get());
+
+  histogram_tester_.ExpectUniqueSample(
+      "Blink.FedCm.IdpSigninStatus.MismatchDialogResult",
+      static_cast<int>(FedCmAccountSelectionView::MismatchDialogResult::
+                           kDismissedForOtherReasons),
+      1);
+}
+
+// Tests that when FedCmAccountSelectionView is destroyed while the mismatch
+// dialog is open, the relevant metric is recorded.
+TEST_F(FedCmAccountSelectionViewDesktopTest, MismatchDialogDestroyedMetric) {
+  {
+    std::unique_ptr<TestFedCmAccountSelectionView> controller =
+        CreateAndShowFailureDialog();
+    histogram_tester_.ExpectTotalCount(
+        "Blink.FedCm.IdpSigninStatus.MismatchDialogResult", 0);
+  }
+
+  histogram_tester_.ExpectUniqueSample(
+      "Blink.FedCm.IdpSigninStatus.MismatchDialogResult",
+      static_cast<int>(FedCmAccountSelectionView::MismatchDialogResult::
+                           kDismissedForOtherReasons),
+      1);
+}
+
+// Tests that when the continue button on the mismatch dialog is clicked, the
+// relevant metric is recorded.
+TEST_F(FedCmAccountSelectionViewDesktopTest,
+       MismatchDialogContinueClickedMetric) {
+  std::unique_ptr<TestFedCmAccountSelectionView> controller =
+      CreateAndShowFailureDialog();
+  histogram_tester_.ExpectTotalCount(
+      "Blink.FedCm.IdpSigninStatus.MismatchDialogResult", 0);
+
+  AccountSelectionBubbleView::Observer* observer =
+      static_cast<AccountSelectionBubbleView::Observer*>(controller.get());
+
+  // Called when user clicks on "Continue" button in the mismatch dialog.
+  observer->OnSigninToIdP();
+
+  histogram_tester_.ExpectUniqueSample(
+      "Blink.FedCm.IdpSigninStatus.MismatchDialogResult",
+      static_cast<int>(
+          FedCmAccountSelectionView::MismatchDialogResult::kContinued),
+      1);
+}
+
+// Tests that when the continue button on the mismatch dialog is clicked and
+// then FedCmAccountSelectionView is destroyed, we record only the metric for
+// the continue button being clicked.
+TEST_F(FedCmAccountSelectionViewDesktopTest,
+       MismatchDialogContinueClickedThenDestroyedMetric) {
+  {
+    std::unique_ptr<TestFedCmAccountSelectionView> controller =
+        CreateAndShowFailureDialog();
+    histogram_tester_.ExpectTotalCount(
+        "Blink.FedCm.IdpSigninStatus.MismatchDialogResult", 0);
+
+    AccountSelectionBubbleView::Observer* observer =
+        static_cast<AccountSelectionBubbleView::Observer*>(controller.get());
+
+    // Called when user clicks on "Continue" button in the mismatch dialog.
+    observer->OnSigninToIdP();
+  }
+
+  histogram_tester_.ExpectUniqueSample(
+      "Blink.FedCm.IdpSigninStatus.MismatchDialogResult",
+      static_cast<int>(
+          FedCmAccountSelectionView::MismatchDialogResult::kContinued),
+      1);
+}
+
 // Test transitioning from IdP sign-in status mismatch failure dialog to regular
 // sign-in dialog. This emulates a user signing into the IdP in a pop-up window
 // and the pop-up window closes PRIOR to the failure dialog being updated to a
@@ -644,3 +748,33 @@
   // Widget should be closed.
   EXPECT_TRUE(widget_->IsClosed());
 }
+
+// Test that the mismatch dialog can be shown again after the pop-up window is
+// closed.
+TEST_F(FedCmAccountSelectionViewDesktopTest,
+       IdpSigninStatusMismatchDialogReshown) {
+  // Trigger IdP sign-in status mismatch failure dialog.
+  std::unique_ptr<TestFedCmAccountSelectionView> controller =
+      CreateAndShowFailureDialog();
+
+  // Emulate user clicking on "Continue" button in the failure dialog.
+  CreateAndShowPopupWindow(*controller);
+
+  // Mismatch dialog should be hidden because pop-up window is open.
+  EXPECT_FALSE(widget_->IsVisible());
+
+  // Emulate IdentityProvider.close() being called in the pop-up window.
+  controller->CloseModalDialog();
+
+  // Failure dialog should remain hidden because it has not been updated to an
+  // accounts dialog yet.
+  EXPECT_FALSE(widget_->IsVisible());
+
+  // Emulate another mismatch so we need to show the mismatch dialog again.
+  controller->ShowFailureDialog(kTopFrameEtldPlusOne, kIframeEtldPlusOne,
+                                kIdpEtldPlusOne,
+                                content::IdentityProviderMetadata());
+
+  // Mismatch dialog is visible again.
+  EXPECT_TRUE(widget_->IsVisible());
+}
diff --git a/chrome/browser/ui/views/webid/fedcm_modal_dialog_view.cc b/chrome/browser/ui/views/webid/fedcm_modal_dialog_view.cc
index aeea1b5..c70adee5 100644
--- a/chrome/browser/ui/views/webid/fedcm_modal_dialog_view.cc
+++ b/chrome/browser/ui/views/webid/fedcm_modal_dialog_view.cc
@@ -6,6 +6,7 @@
 
 #include <algorithm>
 
+#include "base/metrics/histogram_macros.h"
 #include "components/constrained_window/constrained_window_views.h"
 #include "components/url_formatter/elide_url.h"
 #include "components/web_modal/web_contents_modal_dialog_manager.h"
@@ -23,12 +24,30 @@
 FedCmModalDialogView::~FedCmModalDialogView() = default;
 
 content::WebContents* FedCmModalDialogView::ShowPopupWindow(const GURL& url) {
+  CHECK(!popup_window_);
+
+  if (!url.is_valid() || !url.SchemeIsHTTPOrHTTPS()) {
+    UMA_HISTOGRAM_ENUMERATION(
+        "Blink.FedCm.IdpSigninStatus.ShowPopupWindowResult",
+        ShowPopupWindowResult::kFailedByInvalidUrl);
+
+    return nullptr;
+  }
+
   content::OpenURLParams params(
       url, content::Referrer(), WindowOpenDisposition::NEW_POPUP,
       ui::PAGE_TRANSITION_AUTO_TOPLEVEL, /*is_renderer_initiated=*/false);
   popup_window_ =
       source_window_->GetDelegate()->OpenURLFromTab(source_window_, params);
 
+  if (!popup_window_) {
+    UMA_HISTOGRAM_ENUMERATION(
+        "Blink.FedCm.IdpSigninStatus.ShowPopupWindowResult",
+        ShowPopupWindowResult::kFailedForOtherReasons);
+
+    return nullptr;
+  }
+
   constexpr int kPopupWindowWidth = 500;
   constexpr int kPopupWindowPreferredHeight = 600;
   gfx::Rect source_window_rect = source_window_->GetContainerBounds();
@@ -45,6 +64,9 @@
 
   Observe(popup_window_);
 
+  UMA_HISTOGRAM_ENUMERATION("Blink.FedCm.IdpSigninStatus.ShowPopupWindowResult",
+                            ShowPopupWindowResult::kSuccess);
+
   return popup_window_;
 }
 
diff --git a/chrome/browser/ui/views/webid/fedcm_modal_dialog_view.h b/chrome/browser/ui/views/webid/fedcm_modal_dialog_view.h
index d9ddb1b..b731b059 100644
--- a/chrome/browser/ui/views/webid/fedcm_modal_dialog_view.h
+++ b/chrome/browser/ui/views/webid/fedcm_modal_dialog_view.h
@@ -21,6 +21,18 @@
     virtual void OnPopupWindowDestroyed() = 0;
   };
 
+  // This enum describes the outcome of attempting to open the pop-up window and
+  // is used for histograms. Do not remove or modify existing values, but you
+  // may add new values at the end. This enum should be kept in sync with
+  // FedCmShowPopupWindowResult in tools/metrics/histograms/enums.xml.
+  enum class ShowPopupWindowResult {
+    kSuccess,
+    kFailedByInvalidUrl,
+    kFailedForOtherReasons,
+
+    kMaxValue = kFailedForOtherReasons
+  };
+
   explicit FedCmModalDialogView(content::WebContents* web_contents,
                                 FedCmModalDialogView::Observer* observer);
   FedCmModalDialogView(const FedCmModalDialogView&) = delete;
diff --git a/chrome/browser/ui/views/webid/fedcm_modal_dialog_view_unittest.cc b/chrome/browser/ui/views/webid/fedcm_modal_dialog_view_unittest.cc
index ea16ada..a32dac0 100644
--- a/chrome/browser/ui/views/webid/fedcm_modal_dialog_view_unittest.cc
+++ b/chrome/browser/ui/views/webid/fedcm_modal_dialog_view_unittest.cc
@@ -4,6 +4,7 @@
 
 #include "chrome/browser/ui/views/webid/fedcm_modal_dialog_view.h"
 
+#include "base/test/metrics/histogram_tester.h"
 #include "chrome/test/base/testing_profile.h"
 #include "chrome/test/views/chrome_views_test_base.h"
 #include "content/public/test/test_web_contents_factory.h"
@@ -21,6 +22,8 @@
  protected:
   content::WebContents* web_contents() { return web_contents_; }
 
+  base::HistogramTester histogram_tester_;
+
  private:
   TestingProfile testing_profile_;
   content::TestWebContentsFactory web_contents_factory_;
@@ -39,14 +42,23 @@
   content::WebContents* OpenURLFromTab(
       content::WebContents* source,
       const content::OpenURLParams& params) override {
+    if (should_return_null_popup_window_) {
+      return nullptr;
+    }
+
     opened_++;
     return source;
   }
 
+  void SetShouldReturnNullPopupWindow(bool should_return_null_popup_window) {
+    should_return_null_popup_window_ = should_return_null_popup_window;
+  }
+
   int opened() const { return opened_; }
 
  private:
   int opened_ = 0;
+  bool should_return_null_popup_window_{false};
 };
 
 }  // namespace
@@ -55,13 +67,68 @@
   // Override the delegate to test that OpenURLFromTab gets called.
   TestDelegate delegate(web_contents());
 
-  std::unique_ptr<FedCmModalDialogView> popup_window =
+  std::unique_ptr<FedCmModalDialogView> popup_window_view =
       std::make_unique<FedCmModalDialogView>(web_contents(),
                                              /*observer=*/nullptr);
+  histogram_tester_.ExpectTotalCount(
+      "Blink.FedCm.IdpSigninStatus.ShowPopupWindowResult", 0);
   content::WebContents* web_contents =
-      popup_window->ShowPopupWindow(GURL(u"https://example.com"));
+      popup_window_view->ShowPopupWindow(GURL(u"https://example.com"));
 
   base::RunLoop().RunUntilIdle();
   EXPECT_EQ(1, delegate.opened());
   ASSERT_TRUE(web_contents);
+  histogram_tester_.ExpectUniqueSample(
+      "Blink.FedCm.IdpSigninStatus.ShowPopupWindowResult",
+      static_cast<int>(FedCmModalDialogView::ShowPopupWindowResult::kSuccess),
+      1);
+}
+
+TEST_F(FedCmModalDialogViewTest, ShowPopupWindowFailedByInvalidUrl) {
+  // Override the delegate to test that OpenURLFromTab gets called.
+  TestDelegate delegate(web_contents());
+
+  std::unique_ptr<FedCmModalDialogView> popup_window_view =
+      std::make_unique<FedCmModalDialogView>(web_contents(),
+                                             /*observer=*/nullptr);
+  histogram_tester_.ExpectTotalCount(
+      "Blink.FedCm.IdpSigninStatus.ShowPopupWindowResult", 0);
+  content::WebContents* web_contents =
+      popup_window_view->ShowPopupWindow(GURL(u"invalid"));
+
+  base::RunLoop().RunUntilIdle();
+  EXPECT_EQ(0, delegate.opened());
+  ASSERT_FALSE(web_contents);
+  histogram_tester_.ExpectUniqueSample(
+      "Blink.FedCm.IdpSigninStatus.ShowPopupWindowResult",
+      static_cast<int>(
+          FedCmModalDialogView::ShowPopupWindowResult::kFailedByInvalidUrl),
+      1);
+}
+
+TEST_F(FedCmModalDialogViewTest, ShowPopupWindowFailedForOtherReasons) {
+  // Override the delegate to test that OpenURLFromTab gets called.
+  TestDelegate delegate(web_contents());
+
+  // Set OpenURLFromTab to return nullptr to emulate showing pop-up window
+  // failing for other reasons.
+  delegate.SetShouldReturnNullPopupWindow(
+      /*should_return_null_popup_window=*/true);
+
+  std::unique_ptr<FedCmModalDialogView> popup_window_view =
+      std::make_unique<FedCmModalDialogView>(web_contents(),
+                                             /*observer=*/nullptr);
+  histogram_tester_.ExpectTotalCount(
+      "Blink.FedCm.IdpSigninStatus.ShowPopupWindowResult", 0);
+  content::WebContents* web_contents =
+      popup_window_view->ShowPopupWindow(GURL(u"https://example.com"));
+
+  base::RunLoop().RunUntilIdle();
+  EXPECT_EQ(0, delegate.opened());
+  ASSERT_FALSE(web_contents);
+  histogram_tester_.ExpectUniqueSample(
+      "Blink.FedCm.IdpSigninStatus.ShowPopupWindowResult",
+      static_cast<int>(
+          FedCmModalDialogView::ShowPopupWindowResult::kFailedForOtherReasons),
+      1);
 }
diff --git a/chrome/browser/ui/webui/browsing_topics/browsing_topics_internals_page_handler.cc b/chrome/browser/ui/webui/browsing_topics/browsing_topics_internals_page_handler.cc
index 6c29d65..3bdf121 100644
--- a/chrome/browser/ui/webui/browsing_topics/browsing_topics_internals_page_handler.cc
+++ b/chrome/browser/ui/webui/browsing_topics/browsing_topics_internals_page_handler.cc
@@ -33,10 +33,15 @@
           privacy_sandbox::kOverridePrivacySandboxSettingsLocalTesting),
       base::FeatureList::IsEnabled(
           blink::features::kBrowsingTopicsBypassIPIsPubliclyRoutableCheck),
+      base::FeatureList::IsEnabled(blink::features::kBrowsingTopicsXHR),
+      base::FeatureList::IsEnabled(blink::features::kBrowsingTopicsDocumentAPI),
+      browsing_topics::CurrentConfigVersion(),
+      base::FeatureList::IsEnabled(blink::features::kBrowsingTopicsParameters),
       blink::features::kBrowsingTopicsNumberOfEpochsToExpose.Get(),
       blink::features::kBrowsingTopicsTimePeriodPerEpoch.Get(),
       blink::features::kBrowsingTopicsNumberOfTopTopicsPerEpoch.Get(),
       blink::features::kBrowsingTopicsUseRandomTopicProbabilityPercent.Get(),
+      blink::features::kBrowsingTopicsMaxEpochIntroductionDelay.Get(),
       blink::features::
           kBrowsingTopicsNumberOfEpochsOfObservationDataToUseForFiltering.Get(),
       blink::features::
@@ -46,8 +51,8 @@
       blink::features::
           kBrowsingTopicsMaxNumberOfApiUsageContextDomainsToStorePerPageLoad
               .Get(),
-      blink::features::kBrowsingTopicsConfigVersion.Get(),
-      blink::features::kBrowsingTopicsTaxonomyVersion.Get());
+      blink::features::kBrowsingTopicsTaxonomyVersion.Get(),
+      blink::features::kBrowsingTopicsDisabledTopicsList.Get());
 
   std::move(callback).Run(std::move(config));
 }
diff --git a/chrome/browser/ui/webui/print_preview/print_preview_metrics.cc b/chrome/browser/ui/webui/print_preview/print_preview_metrics.cc
index bf777b7..a2fdb48a 100644
--- a/chrome/browser/ui/webui/print_preview/print_preview_metrics.cc
+++ b/chrome/browser/ui/webui/print_preview/print_preview_metrics.cc
@@ -150,8 +150,10 @@
       ReportPrintSettingHistogram(PrintSettingsBuckets::kFitToPaper);
   }
 
-  if (print_settings.FindInt(kSettingDpiHorizontal).value_or(0) > 0 &&
-      print_settings.FindInt(kSettingDpiVertical).value_or(0) > 0) {
+  int dpi_horizontal =
+      print_settings.FindInt(kSettingDpiHorizontal).value_or(0);
+  int dpi_vertical = print_settings.FindInt(kSettingDpiVertical).value_or(0);
+  if (dpi_horizontal > 0 && dpi_vertical > 0) {
     absl::optional<bool> is_default_opt =
         print_settings.FindBool(kSettingDpiDefault);
     if (is_default_opt.has_value()) {
@@ -159,6 +161,9 @@
                                       ? PrintSettingsBuckets::kDefaultDpi
                                       : PrintSettingsBuckets::kNonDefaultDpi);
     }
+    if (dpi_horizontal != dpi_vertical) {
+      ReportPrintSettingHistogram(PrintSettingsBuckets::kNonSquarePixels);
+    }
   }
 
 #if BUILDFLAG(IS_CHROMEOS_ASH)
diff --git a/chrome/browser/ui/webui/print_preview/print_preview_metrics.h b/chrome/browser/ui/webui/print_preview/print_preview_metrics.h
index c0a7058..7baf331 100644
--- a/chrome/browser/ui/webui/print_preview/print_preview_metrics.h
+++ b/chrome/browser/ui/webui/print_preview/print_preview_metrics.h
@@ -48,7 +48,8 @@
   kNonDefaultDpi = 23,
   kPin = 24,
   kFitToPaper = 25,
-  kMaxValue = kFitToPaper
+  kNonSquarePixels = 26,
+  kMaxValue = kNonSquarePixels
 };
 
 // This enum is used to back an UMA histogram, and should therefore be treated
diff --git a/chrome/browser/web_applications/commands/manifest_update_finalize_command.cc b/chrome/browser/web_applications/commands/manifest_update_finalize_command.cc
index 2bc5702c..1a7b2de 100644
--- a/chrome/browser/web_applications/commands/manifest_update_finalize_command.cc
+++ b/chrome/browser/web_applications/commands/manifest_update_finalize_command.cc
@@ -40,7 +40,10 @@
       install_info_(std::move(install_info)),
       write_callback_(std::move(write_callback)),
       keep_alive_(std::move(keep_alive)),
-      profile_keep_alive_(std::move(profile_keep_alive)) {}
+      profile_keep_alive_(std::move(profile_keep_alive)) {
+  CHECK(install_info_.manifest_id.is_valid());
+  CHECK(install_info_.start_url.is_valid());
+}
 
 ManifestUpdateFinalizeCommand::~ManifestUpdateFinalizeCommand() = default;
 
diff --git a/chrome/browser/web_applications/commands/manifest_update_finalize_command_unittest.cc b/chrome/browser/web_applications/commands/manifest_update_finalize_command_unittest.cc
index e691b969..c86e0a08 100644
--- a/chrome/browser/web_applications/commands/manifest_update_finalize_command_unittest.cc
+++ b/chrome/browser/web_applications/commands/manifest_update_finalize_command_unittest.cc
@@ -111,10 +111,9 @@
 }
 
 TEST_F(ManifestUpdateFinalizeCommandTest, UpdateFailsOnUnsuccessfulCode) {
-  WebAppInstallInfo info;
-  info.validated_scope_extensions.emplace();
-  ManifestUpdateResult expected_result =
-      RunCommandAndGetResult(app_url(), "RandomAppId", std::move(info));
+  // This should fail because RandomAppId does not exist.
+  ManifestUpdateResult expected_result = RunCommandAndGetResult(
+      app_url(), "RandomAppId", GetNewInstallInfoWithTitle(u"Name"));
   EXPECT_EQ(expected_result, ManifestUpdateResult::kAppUpdateFailed);
 }
 
diff --git a/chrome/browser/web_applications/web_app_helpers.cc b/chrome/browser/web_applications/web_app_helpers.cc
index 19b60f88..e3eb4f4 100644
--- a/chrome/browser/web_applications/web_app_helpers.cc
+++ b/chrome/browser/web_applications/web_app_helpers.cc
@@ -88,6 +88,7 @@
 }
 
 ManifestId GenerateManifestIdFromStartUrlOnly(const GURL& start_url) {
+  CHECK(start_url.is_valid());
   return start_url.GetWithoutRef();
 }
 
diff --git a/chrome/browser/web_applications/web_app_helpers_unittest.cc b/chrome/browser/web_applications/web_app_helpers_unittest.cc
index 732a6b2..7a081bb 100644
--- a/chrome/browser/web_applications/web_app_helpers_unittest.cc
+++ b/chrome/browser/web_applications/web_app_helpers_unittest.cc
@@ -33,7 +33,6 @@
 }
 
 TEST(WebAppHelpers, GenerateManifestIdFromStartUrlOnly) {
-  EXPECT_EQ(GURL(), GenerateManifestIdFromStartUrlOnly(GURL()));
   EXPECT_EQ(GURL("https://example.com/"),
             GenerateManifestIdFromStartUrlOnly(GURL("https://example.com/")));
   EXPECT_EQ(GURL("https://example.com"),
diff --git a/chrome/browser/web_applications/web_app_install_finalizer.cc b/chrome/browser/web_applications/web_app_install_finalizer.cc
index 33aec3e0..acc150c 100644
--- a/chrome/browser/web_applications/web_app_install_finalizer.cc
+++ b/chrome/browser/web_applications/web_app_install_finalizer.cc
@@ -355,7 +355,7 @@
     const WebAppInstallInfo& web_app_info,
     InstallFinalizedCallback callback) {
   CHECK(started_);
-
+  CHECK(web_app_info.start_url.is_valid());
   ManifestId manifest_id = web_app_info.manifest_id;
   if (manifest_id.is_valid()) {
     CHECK(url::Origin::Create(manifest_id)
@@ -366,6 +366,7 @@
     CHECK_IS_TEST();
     manifest_id = GenerateManifestIdFromStartUrlOnly(web_app_info.start_url);
   }
+  CHECK(manifest_id.is_valid());
 
   const AppId app_id = GenerateAppIdFromManifestId(manifest_id);
   const WebApp* existing_web_app = GetWebAppRegistrar().GetAppById(app_id);
diff --git a/chrome/browser/web_applications/web_app_install_info.cc b/chrome/browser/web_applications/web_app_install_info.cc
index 0c81b8c..588b96e 100644
--- a/chrome/browser/web_applications/web_app_install_info.cc
+++ b/chrome/browser/web_applications/web_app_install_info.cc
@@ -7,7 +7,6 @@
 #include <sstream>
 
 #include "chrome/browser/web_applications/web_app_helpers.h"
-#include "components/webapps/common/web_page_metadata.mojom.h"
 #include "third_party/blink/public/mojom/manifest/manifest.mojom.h"
 #include "ui/gfx/skia_util.h"
 
@@ -269,33 +268,6 @@
 
 WebAppInstallInfo& WebAppInstallInfo::operator=(WebAppInstallInfo&&) = default;
 
-WebAppInstallInfo::WebAppInstallInfo(
-    const webapps::mojom::WebPageMetadata& metadata)
-    : manifest_id(web_app::GenerateManifestIdFromStartUrlOnly(
-          metadata.application_url)),
-      title(metadata.application_name),
-      description(metadata.description),
-      start_url(metadata.application_url) {
-  for (const auto& icon : metadata.icons) {
-    apps::IconInfo icon_info;
-    icon_info.url = icon->url;
-    if (icon->square_size_px > 0)
-      icon_info.square_size_px = icon->square_size_px;
-    manifest_icons.push_back(icon_info);
-  }
-  switch (metadata.mobile_capable) {
-    case webapps::mojom::WebPageMobileCapable::UNSPECIFIED:
-      mobile_capable = MOBILE_CAPABLE_UNSPECIFIED;
-      break;
-    case webapps::mojom::WebPageMobileCapable::ENABLED:
-      mobile_capable = MOBILE_CAPABLE;
-      break;
-    case webapps::mojom::WebPageMobileCapable::ENABLED_APPLE:
-      mobile_capable = MOBILE_CAPABLE_APPLE;
-      break;
-  }
-}
-
 WebAppInstallInfo::~WebAppInstallInfo() = default;
 
 WebAppInstallInfo WebAppInstallInfo::Clone() const {
diff --git a/chrome/browser/web_applications/web_app_install_info.h b/chrome/browser/web_applications/web_app_install_info.h
index 01eea408..6d6c321f 100644
--- a/chrome/browser/web_applications/web_app_install_info.h
+++ b/chrome/browser/web_applications/web_app_install_info.h
@@ -23,7 +23,6 @@
 #include "components/services/app_service/public/cpp/protocol_handler_info.h"
 #include "components/services/app_service/public/cpp/share_target.h"
 #include "components/services/app_service/public/cpp/url_handler_info.h"
-#include "components/webapps/common/web_page_metadata.mojom-forward.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
 #include "third_party/blink/public/common/permissions_policy/permissions_policy.h"
 #include "third_party/blink/public/mojom/manifest/display_mode.mojom.h"
@@ -197,8 +196,6 @@
 
   WebAppInstallInfo(WebAppInstallInfo&&);
   WebAppInstallInfo& operator=(WebAppInstallInfo&&);
-
-  explicit WebAppInstallInfo(const webapps::mojom::WebPageMetadata& metadata);
   ~WebAppInstallInfo();
 
   // Creates a deep copy of this struct.
diff --git a/chrome/browser/web_applications/web_contents/web_app_data_retriever.cc b/chrome/browser/web_applications/web_contents/web_app_data_retriever.cc
index 4b1e55c..1668190 100644
--- a/chrome/browser/web_applications/web_contents/web_app_data_retriever.cc
+++ b/chrome/browser/web_applications/web_contents/web_app_data_retriever.cc
@@ -164,7 +164,7 @@
 void WebAppDataRetriever::OnGetWebPageMetadata(
     mojo::AssociatedRemote<webapps::mojom::WebPageMetadataAgent> metadata_agent,
     int last_committed_nav_entry_unique_id,
-    webapps::mojom::WebPageMetadataPtr web_page_metadata) {
+    webapps::mojom::WebPageMetadataPtr metadata) {
   if (ShouldStopRetrieval()) {
     CallCallbackOnError(webapps::InstallableStatusCode::RENDERER_CANCELLED);
     return;
@@ -175,33 +175,60 @@
   content::WebContents* contents = web_contents();
   Observe(nullptr);
 
-  std::unique_ptr<WebAppInstallInfo> info;
-
   content::NavigationEntry* entry =
       contents->GetController().GetLastCommittedEntry();
 
-  if (!entry->IsInitialEntry()) {
-    if (entry->GetUniqueID() == last_committed_nav_entry_unique_id) {
-      info = std::make_unique<WebAppInstallInfo>(*web_page_metadata);
-      if (info->manifest_id.is_empty()) {
-        info->manifest_id = std::move(fallback_install_info_->manifest_id);
-      }
-      if (info->start_url.is_empty()) {
-        info->start_url = std::move(fallback_install_info_->start_url);
-      }
-      if (info->title.empty()) {
-        info->title = std::move(fallback_install_info_->title);
-      }
-    } else {
-      // WebContents navigation state changed during the call. Ignore the mojo
-      // request result. Use default initial info instead.
-      info = std::move(fallback_install_info_);
-    }
+  CHECK(!get_web_app_info_callback_.is_null());
+
+  if (entry->IsInitialEntry()) {
+    // Possibly impossible to get to this state, treat it as an error.
+    fallback_install_info_.reset();
+    std::move(get_web_app_info_callback_).Run(nullptr);
+    return;
   }
 
-  fallback_install_info_.reset();
+  if (entry->GetUniqueID() != last_committed_nav_entry_unique_id) {
+    // WebContents navigation state changed during the call. Ignore the mojo
+    // request result and use default initial info instead.
+    std::move(get_web_app_info_callback_)
+        .Run(std::move(fallback_install_info_));
+    return;
+  }
+  CHECK(metadata);
 
-  DCHECK(!get_web_app_info_callback_.is_null());
+  std::unique_ptr<WebAppInstallInfo> info = std::move(fallback_install_info_);
+  if (!metadata->application_name.empty()) {
+    info->title = metadata->application_name;
+  }
+  if (!metadata->description.empty()) {
+    info->description = metadata->description;
+  }
+  if (metadata->application_url.is_valid()) {
+    info->start_url = metadata->application_url;
+    info->manifest_id =
+        web_app::GenerateManifestIdFromStartUrlOnly(info->start_url);
+  }
+
+  for (const auto& icon : metadata->icons) {
+    apps::IconInfo icon_info;
+    icon_info.url = icon->url;
+    if (icon->square_size_px > 0) {
+      icon_info.square_size_px = icon->square_size_px;
+    }
+    info->manifest_icons.push_back(icon_info);
+  }
+  switch (metadata->mobile_capable) {
+    case webapps::mojom::WebPageMobileCapable::UNSPECIFIED:
+      info->mobile_capable = WebAppInstallInfo::MOBILE_CAPABLE_UNSPECIFIED;
+      break;
+    case webapps::mojom::WebPageMobileCapable::ENABLED:
+      info->mobile_capable = WebAppInstallInfo::MOBILE_CAPABLE;
+      break;
+    case webapps::mojom::WebPageMobileCapable::ENABLED_APPLE:
+      info->mobile_capable = WebAppInstallInfo::MOBILE_CAPABLE_APPLE;
+      break;
+  }
+
   std::move(get_web_app_info_callback_).Run(std::move(info));
 }
 
diff --git a/chrome/browser/webauthn/local_credential_management_mac_unittest.mm b/chrome/browser/webauthn/local_credential_management_mac_unittest.mm
index b17ccd2..0a86ed0 100644
--- a/chrome/browser/webauthn/local_credential_management_mac_unittest.mm
+++ b/chrome/browser/webauthn/local_credential_management_mac_unittest.mm
@@ -18,6 +18,10 @@
 #include "testing/gtest/include/gtest/gtest.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
 
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
 namespace {
 
 static const device::PublicKeyCredentialUserEntity kUser({1, 2, 3},
diff --git a/chrome/build/linux.pgo.txt b/chrome/build/linux.pgo.txt
index 592e120f..2b4bebab 100644
--- a/chrome/build/linux.pgo.txt
+++ b/chrome/build/linux.pgo.txt
@@ -1 +1 @@
-chrome-linux-main-1686743662-938f5d72d478557f00ae04a0b4bf2070f779100d.profdata
+chrome-linux-main-1686765533-ed123a6eb86767f0a48c9442c06a0c2133906043.profdata
diff --git a/chrome/build/mac-arm.pgo.txt b/chrome/build/mac-arm.pgo.txt
index 432bf3c..0fe12946 100644
--- a/chrome/build/mac-arm.pgo.txt
+++ b/chrome/build/mac-arm.pgo.txt
@@ -1 +1 @@
-chrome-mac-arm-main-1686758346-f228fc14950ae53b74df10aa18d8075891994471.profdata
+chrome-mac-arm-main-1686765533-a135384ef988ebc52685b72137ea203f044f4c75.profdata
diff --git a/chrome/build/mac.pgo.txt b/chrome/build/mac.pgo.txt
index e55b0126..02c39da 100644
--- a/chrome/build/mac.pgo.txt
+++ b/chrome/build/mac.pgo.txt
@@ -1 +1 @@
-chrome-mac-main-1686743662-c8267b8ceb10f216f1d5ab5d1b61897c36fe8886.profdata
+chrome-mac-main-1686765533-f0cef0ec260ee1cb9f2018748777938267bb1364.profdata
diff --git a/chrome/build/win32.pgo.txt b/chrome/build/win32.pgo.txt
index ca8d11b9..dd25cf0 100644
--- a/chrome/build/win32.pgo.txt
+++ b/chrome/build/win32.pgo.txt
@@ -1 +1 @@
-chrome-win32-main-1686743662-490e2ee9552b695d673012a14d92724384535b9b.profdata
+chrome-win32-main-1686765533-0419320b99716471702201fbc44a94a5e7181fa3.profdata
diff --git a/chrome/build/win64.pgo.txt b/chrome/build/win64.pgo.txt
index 8161f11..4e885142 100644
--- a/chrome/build/win64.pgo.txt
+++ b/chrome/build/win64.pgo.txt
@@ -1 +1 @@
-chrome-win64-main-1686754776-64fec57127d4e24f1e31fe0b507d059b06d1ce09.profdata
+chrome-win64-main-1686765533-1894346b5888f16faf9050fe055ea8d49e370447.profdata
diff --git a/chrome/common/extensions/extension_constants.cc b/chrome/common/extensions/extension_constants.cc
index 27579fd..a14ac1b 100644
--- a/chrome/common/extensions/extension_constants.cc
+++ b/chrome/common/extensions/extension_constants.cc
@@ -93,8 +93,6 @@
     "accessibility_common_manifest.json";
 const char kAccessibilityCommonGuestManifestFilename[] =
     "accessibility_common_manifest_guest.json";
-const char kAutotestPrivateTestExtensionId[] =
-    "ddammdhioacbehjngdmkjcjbnfginlla";
 const char kChromeVoxExtensionPath[] = "chromeos/accessibility";
 const char kChromeVoxManifestFilename[] = "chromevox_manifest.json";
 const char kChromeVoxGuestManifestFilename[] = "chromevox_manifest_guest.json";
diff --git a/chrome/common/extensions/extension_constants.h b/chrome/common/extensions/extension_constants.h
index 0ad5613bc..61d38ef8 100644
--- a/chrome/common/extensions/extension_constants.h
+++ b/chrome/common/extensions/extension_constants.h
@@ -184,8 +184,6 @@
 extern const char kAccessibilityCommonManifestFilename[];
 // The guest manifest filename of the Accessibility Common extension.
 extern const char kAccessibilityCommonGuestManifestFilename[];
-// Extension ID of the autotest_private test extension.
-extern const char kAutotestPrivateTestExtensionId[];
 // Path to preinstalled ChromeVox screen reader extension (relative to
 // |chrome::DIR_RESOURCES|).
 extern const char kChromeVoxExtensionPath[];
diff --git a/chrome/installer/mini_installer/decompress.cc b/chrome/installer/mini_installer/decompress.cc
index 20028b3..213429a 100644
--- a/chrome/installer/mini_installer/decompress.cc
+++ b/chrome/installer/mini_installer/decompress.cc
@@ -278,8 +278,15 @@
   g_FDICopy(fdi, source_name_utf8, source_path_utf8, 0, &Notify, nullptr,
             &context);
   g_FDIDestroy(fdi);
-  if (context.succeeded)
+  if (context.succeeded) {
+    // https://crbug.com/1443320: We see crashes on Windows 10 when running
+    // setup.exe in which it appears that an entire hunk of the file is zeros or
+    // random memory. There is nothing out of the ordinary in the way that this
+    // file is written (0x8000 byte chunks via normal WriteFile calls). As an
+    // experiment, flush the file before closing it.
+    ::FlushFileBuffers(context.dest_file.GetHandleUnsafe());
     return true;
+  }
 
   // Delete the output file if it was created.
   if (context.dest_file.IsValid())
diff --git a/chrome/services/sharing/nearby/nearby_presence_conversions.h b/chrome/services/sharing/nearby/nearby_presence_conversions.h
index 0bf088ef..d40a5c89 100644
--- a/chrome/services/sharing/nearby/nearby_presence_conversions.h
+++ b/chrome/services/sharing/nearby/nearby_presence_conversions.h
@@ -14,12 +14,10 @@
 
 ::nearby::internal::DeviceType DeviceTypeFromMojom(
     mojom::PresenceDeviceType device_type);
-
 ::nearby::internal::Metadata MetadataFromMojom(mojom::Metadata* metadata);
 
 mojom::IdentityType IdentityTypeToMojom(
     ::nearby::internal::IdentityType identity_type);
-
 mojom::SharedCredentialPtr SharedCredentialToMojom(
     ::nearby::internal::SharedCredential shared_credential);
 
diff --git a/chrome/test/BUILD.gn b/chrome/test/BUILD.gn
index 061c2128..2794317 100644
--- a/chrome/test/BUILD.gn
+++ b/chrome/test/BUILD.gn
@@ -5562,53 +5562,6 @@
   }
 }
 
-if (is_mac) {
-  # TODO(https://crbug.com/1280317): Merge back into unit_tests once all .mm
-  # files are ARCed.
-  source_set("unit_tests_arc") {
-    testonly = true
-    sources = [
-      "../../testing/gtest_mac_unittest.mm",
-      "../browser/apps/app_shim/mach_bootstrap_acceptor_unittest.mm",
-      "../browser/mac/auth_session_request_unittest.mm",
-      "../browser/mac/keystone_glue_unittest.mm",
-      "../browser/ui/cocoa/applescript/apple_event_util_unittest.mm",
-      "../browser/ui/cocoa/first_run_dialog_controller_unittest.mm",
-      "../browser/ui/cocoa/scoped_menu_bar_lock_unittest.mm",
-      "../browser/ui/cocoa/screentime/screentime_tab_helper_unittest.mm",
-      "../browser/ui/cocoa/status_icons/status_icon_mac_unittest.mm",
-      "../browser/ui/cocoa/touchbar/browser_window_default_touch_bar_unittest.mm",
-      "../browser/ui/cocoa/touchbar/credit_card_autofill_touch_bar_controller_unittest.mm",
-    ]
-    if (enable_service_discovery) {
-      # TODO(https://crbug.com/1280317): Return this to the explicitly marked
-      # previous location; search for the name of this file to find it.
-      sources += [
-        "../browser/local_discovery/service_discovery_client_mac_unittest.mm",
-      ]
-    }
-    configs += [ "//build/config/compiler:enable_arc" ]
-    deps = [
-      "//base",
-      "//base/test:test_support",
-      "//chrome/app:command_ids",
-      "//chrome/app/theme:chrome_unscaled_resources_grit",
-      "//chrome/app_shim:app_shim",
-      "//chrome/browser",
-      "//chrome/browser/apps/app_shim",
-      "//chrome/browser/autofill:test_support",
-      "//chrome/browser/mac:keystone_glue",
-      "//chrome/browser/ui:test_support",
-      "//chrome/common:constants",
-      "//chrome/test:test_support",
-      "//content/test:test_support",
-      "//skia",
-      "//testing/gtest",
-      "//third_party/abseil-cpp:absl",
-    ]
-  }
-}
-
 test("unit_tests") {
   use_xvfb = use_xvfb_in_this_config
 
@@ -6657,12 +6610,20 @@
     }
   }
 
+  if (is_apple) {
+    configs += [ "//build/config/compiler:enable_arc" ]
+  }
+
   if (is_mac) {
     sources += [
+      "../../testing/gtest_mac_unittest.mm",
       "../browser/app_controller_mac_unittest.mm",
+      "../browser/apps/app_shim/mach_bootstrap_acceptor_unittest.mm",
       "../browser/device_reauth/mac/device_authenticator_mac_unittest.mm",
       "../browser/global_keyboard_shortcuts_mac_unittest.mm",
+      "../browser/mac/auth_session_request_unittest.mm",
       "../browser/mac/exception_processor_unittest.mm",
+      "../browser/mac/keystone_glue_unittest.mm",
       "../browser/metrics/power/coalition_resource_usage_provider_mac_unittest.mm",
       "../browser/metrics/power/coalition_resource_usage_provider_test_util_mac.cc",
       "../browser/metrics/power/coalition_resource_usage_provider_test_util_mac.h",
@@ -6673,19 +6634,26 @@
       "../browser/notifications/stub_notification_dispatcher_mac.h",
       "../browser/policy/browser_dm_token_storage_mac_unittest.cc",
       "../browser/renderer_host/chrome_render_widget_host_view_mac_history_swiper_unit_test.mm",
+      "../browser/ui/cocoa/applescript/apple_event_util_unittest.mm",
       "../browser/ui/cocoa/bookmarks/bookmark_menu_bridge_unittest.mm",
       "../browser/ui/cocoa/bookmarks/bookmark_menu_cocoa_controller_unittest.mm",
       "../browser/ui/cocoa/confirm_quit_panel_controller_unittest.mm",
       "../browser/ui/cocoa/find_pasteboard_unittest.mm",
+      "../browser/ui/cocoa/first_run_dialog_controller_unittest.mm",
       "../browser/ui/cocoa/history_menu_bridge_unittest.mm",
       "../browser/ui/cocoa/history_menu_cocoa_controller_unittest.mm",
       "../browser/ui/cocoa/history_overlay_controller_unittest.mm",
       "../browser/ui/cocoa/main_menu_builder_unittest.mm",
       "../browser/ui/cocoa/profiles/profile_menu_controller_unittest.mm",
       "../browser/ui/cocoa/renderer_context_menu/render_view_context_menu_mac_unittest.mm",
+      "../browser/ui/cocoa/scoped_menu_bar_lock_unittest.mm",
       "../browser/ui/cocoa/screentime/history_bridge_unittest.cc",
+      "../browser/ui/cocoa/screentime/screentime_tab_helper_unittest.mm",
+      "../browser/ui/cocoa/status_icons/status_icon_mac_unittest.mm",
       "../browser/ui/cocoa/tab_menu_bridge_unittest.mm",
       "../browser/ui/cocoa/test/run_loop_testing_unittest.mm",
+      "../browser/ui/cocoa/touchbar/browser_window_default_touch_bar_unittest.mm",
+      "../browser/ui/cocoa/touchbar/credit_card_autofill_touch_bar_controller_unittest.mm",
       "../browser/ui/cocoa/window_size_autosaver_unittest.mm",
       "../browser/ui/content_settings/content_setting_media_image_model_unittest.mm",
       "../browser/upgrade_detector/directory_monitor_unittest.cc",
@@ -6695,7 +6663,6 @@
     data_deps += [ "//chrome:chrome_framework" ]
 
     deps += [
-      ":unit_tests_arc",
       "//chrome/app_shim",
       "//chrome/browser/apps/app_shim",
       "//chrome/browser/renderer_host:history_swiper",
@@ -8045,6 +8012,7 @@
       "../browser/performance_manager/policies/oom_score_policy_lacros_unittest.cc",
       "../browser/policy/restricted_mgs_policy_provider_lacros_unittest.cc",
       "../browser/signin/signin_ui_delegate_impl_lacros_unittest.cc",
+      "../browser/ui/media_router/cast_notification_controller_lacros_unittest.cc",
       "../browser/upgrade_detector/get_installed_version_lacros_unittest.cc",
       "../common/chrome_paths_lacros_unittest.cc",
     ]
@@ -8690,8 +8658,7 @@
 
     if (is_mac) {
       sources += [
-        # TODO(https://crbug.com/1280317): Put
-        # service_discovery_client_mac_unittest.mm back here.
+        "../browser/local_discovery/service_discovery_client_mac_unittest.mm",
       ]
     } else {
       sources += [
@@ -10146,6 +10113,7 @@
         "../browser/ui/views/browser_accelerator_interactive_uitest.cc",
         "../browser/ui/views/certificate_selector_browsertest.cc",
         "../browser/ui/views/commander_frontend_views_browsertest.cc",
+        "../browser/ui/views/commerce/price_insights_icon_view_interactive_uitest.cc",
         "../browser/ui/views/commerce/price_tracking_icon_view_interactive_uitest.cc",
         "../browser/ui/views/constrained_window_views_browsertest.cc",
         "../browser/ui/views/content_setting_bubble_contents_interactive_uitest.cc",
diff --git a/chrome/test/data/pdf/params_parser_test.ts b/chrome/test/data/pdf/params_parser_test.ts
index 3c7f7c3c..0b932b7 100644
--- a/chrome/test/data/pdf/params_parser_test.ts
+++ b/chrome/test/data/pdf/params_parser_test.ts
@@ -111,8 +111,14 @@
     params = await paramsParser.getViewportFromUrlParams(`${URL}#nameddest=US`);
     chrome.test.assertEq(0, params.page);
 
-    // Checking #page=pagenum nameddest.The document first page has a pagenum
+    // Checking #page=pagenum without setting the page count should not have a
+    // page value.
+    params = await paramsParser.getViewportFromUrlParams(`${URL}#page=6`);
+    chrome.test.assertEq(null, params.page);
+
+    // Checking #page=pagenum nameddest. The document first page has a pagenum
     // value of 1.
+    paramsParser.setPageCount(100);
     params = await paramsParser.getViewportFromUrlParams(`${URL}#page=6`);
     chrome.test.assertEq(5, params.page);
 
@@ -155,6 +161,23 @@
     chrome.test.assertEq(100, params.position!.x);
     chrome.test.assertEq(200, params.position!.y);
 
+    // Checking #page=pagenum with value out of upper bounds sets the value to
+    // the upper bound.
+    paramsParser.setPageCount(5);
+    params = await paramsParser.getViewportFromUrlParams(`${URL}#page=6`);
+    chrome.test.assertEq(4, params.page);
+
+    // Checking #page=pagenum with value out of lower bounds sets the value to
+    // the lower bound.
+    params = await paramsParser.getViewportFromUrlParams(`${URL}#page=0`);
+    chrome.test.assertEq(0, params.page);
+
+    // Checking #page=pagenum with a page count set to 0 should not have a page
+    // value.
+    paramsParser.setPageCount(0);
+    params = await paramsParser.getViewportFromUrlParams(`${URL}#page=1`);
+    chrome.test.assertEq(null, params.page);
+
     chrome.test.succeed();
   },
   /**
@@ -390,8 +413,18 @@
     const paramsParser = getParamsParser();
 
     // Checking #view=FitB.
-    const params =
+    let params =
         await paramsParser.getViewportFromUrlParams(`${URL}#view=FitB`);
+    chrome.test.assertEq(null, params.view);
+    chrome.test.assertEq(null, params.boundingBox);
+
+    paramsParser.setPageCount(0);
+    params = await paramsParser.getViewportFromUrlParams(`${URL}#view=FitB`);
+    chrome.test.assertEq(null, params.view);
+    chrome.test.assertEq(null, params.boundingBox);
+
+    paramsParser.setPageCount(1);
+    params = await paramsParser.getViewportFromUrlParams(`${URL}#view=FitB`);
     chrome.test.assertEq(FittingType.FIT_TO_BOUNDING_BOX, params.view);
     chrome.test.assertTrue(params.boundingBox !== undefined);
     chrome.test.assertEq(10, params.boundingBox.x);
@@ -404,9 +437,19 @@
   async function testParamsViewFitBH() {
     const paramsParser = getParamsParser();
 
-    // Checking #view=FitB.
+    // Checking #view=FitBH.
     let params =
         await paramsParser.getViewportFromUrlParams(`${URL}#view=FitBH`);
+    chrome.test.assertEq(null, params.view);
+    chrome.test.assertEq(null, params.boundingBox);
+
+    paramsParser.setPageCount(0);
+    params = await paramsParser.getViewportFromUrlParams(`${URL}#view=FitBH`);
+    chrome.test.assertEq(null, params.view);
+    chrome.test.assertEq(null, params.boundingBox);
+
+    paramsParser.setPageCount(1);
+    params = await paramsParser.getViewportFromUrlParams(`${URL}#view=FitBH`);
     chrome.test.assertEq(FittingType.FIT_TO_BOUNDING_BOX_WIDTH, params.view);
     chrome.test.assertTrue(params.boundingBox !== undefined);
     chrome.test.assertEq(10, params.boundingBox.x);
@@ -429,9 +472,19 @@
   async function testParamsViewFitBV() {
     const paramsParser = getParamsParser();
 
-    // Checking #view=FitB.
+    // Checking #view=FitBV.
     let params =
         await paramsParser.getViewportFromUrlParams(`${URL}#view=FitBV`);
+    chrome.test.assertEq(null, params.view);
+    chrome.test.assertEq(null, params.boundingBox);
+
+    paramsParser.setPageCount(0);
+    params = await paramsParser.getViewportFromUrlParams(`${URL}#view=FitBV`);
+    chrome.test.assertEq(null, params.view);
+    chrome.test.assertEq(null, params.boundingBox);
+
+    paramsParser.setPageCount(1);
+    params = await paramsParser.getViewportFromUrlParams(`${URL}#view=FitBV`);
     chrome.test.assertEq(FittingType.FIT_TO_BOUNDING_BOX_HEIGHT, params.view);
     chrome.test.assertTrue(params.boundingBox !== undefined);
     chrome.test.assertEq(10, params.boundingBox.x);
diff --git a/chrome/test/data/webui/location_internals/location_internals_test.ts b/chrome/test/data/webui/location_internals/location_internals_test.ts
index 02eebef7..cdbdd45 100644
--- a/chrome/test/data/webui/location_internals/location_internals_test.ts
+++ b/chrome/test/data/webui/location_internals/location_internals_test.ts
@@ -8,7 +8,7 @@
 
 suite('LocationInternalsUITest', function() {
   test('PageLoaded', async function() {
-    const watchTable = document.querySelector<HTMLElement>('#watch-position');
-    assert(watchTable);
+    const watchButton = document.querySelector<HTMLElement>('#watch-btn');
+    assert(watchButton);
   });
 });
diff --git a/chrome/test/v8/wasm_trap_handler_browsertest.cc b/chrome/test/v8/wasm_trap_handler_browsertest.cc
index 1700e3d2..c918f79 100644
--- a/chrome/test/v8/wasm_trap_handler_browsertest.cc
+++ b/chrome/test/v8/wasm_trap_handler_browsertest.cc
@@ -60,7 +60,6 @@
       int original_count = GetRecoveredTrapCount();
       ASSERT_NO_FATAL_FAILURE(RunJSTest(js));
       int new_count = GetRecoveredTrapCount();
-      ASSERT_NO_FATAL_FAILURE(RunJSTest(js));
       // Out-of-bounds writes, on AArch64 Linux platforms, can perform partial
       // writes. On these platforms, we do not use trap handlers to perform
       // bounds checks on writes. So, for tests that are attempting to access
diff --git a/chrome/updater/app/app_install_win.cc b/chrome/updater/app/app_install_win.cc
index edc5cbf9..6e32d7d 100644
--- a/chrome/updater/app/app_install_win.cc
+++ b/chrome/updater/app/app_install_win.cc
@@ -61,6 +61,7 @@
 #include "chrome/updater/win/ui/splash_screen.h"
 #pragma clang diagnostic pop
 
+#include "components/update_client/protocol_parser.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
 
 namespace updater {
@@ -396,10 +397,12 @@
 
   // These functions are called on the main updater sequence.
   void DoInstallApp();
-  void DoInstallAppOffline(const std::string& installer_version,
-                           const base::FilePath& installer_path,
-                           const std::string& install_args,
-                           const std::string& install_data);
+  void DoInstallAppOffline(
+      const update_client::ProtocolParser::Results& results,
+      const std::string& installer_version,
+      const base::FilePath& installer_path,
+      const std::string& install_args,
+      const std::string& install_data);
   void HandleOsNotSupported();
   void InstallComplete(UpdateService::Result result);
 
@@ -571,14 +574,10 @@
                            base::FilePath /*installer_path*/,
                            std::string /*arguments*/,
                            std::string /*install_data*/>& result) {
-                      if (!IsOsSupported(std::get<0>(result))) {
-                        self->HandleOsNotSupported();
-                        return;
-                      }
-
                       self->DoInstallAppOffline(
-                          std::get<1>(result), std::get<2>(result),
-                          std::get<3>(result), std::get<4>(result));
+                          std::get<0>(result), std::get<1>(result),
+                          std::get<2>(result), std::get<3>(result),
+                          std::get<4>(result));
                     },
                     self));
           },
@@ -586,6 +585,7 @@
 }
 
 void AppInstallControllerImpl::DoInstallAppOffline(
+    const update_client::ProtocolParser::Results& results,
     const std::string& installer_version,
     const base::FilePath& installer_path,
     const std::string& install_args,
@@ -602,6 +602,11 @@
   install_progress_observer_ipc_ = std::make_unique<InstallProgressObserverIPC>(
       observer_.get(), ui_thread_id_);
 
+  if (!IsOsSupported(results)) {
+    HandleOsNotSupported();
+    return;
+  }
+
   base::Value::Dict install_settings_dict;
   install_settings_dict.Set(kInstallerVersion, installer_version);
 
diff --git a/chrome/updater/app/server/win/server.cc b/chrome/updater/app/server/win/server.cc
index abbab30a..3f2a1ec1 100644
--- a/chrome/updater/app/server/win/server.cc
+++ b/chrome/updater/app/server/win/server.cc
@@ -410,12 +410,6 @@
     LOG_IF(ERROR, DeleteLegacyEntriesPerUser());
   }
 
-  // TODO(crbug.com/1425609) - revert the CL that introduced this logging
-  // after the bug is resolved.
-  for (const auto& clsid : GetServers(false, updater_scope())) {
-    LogClsidEntries(clsid);
-  }
-
   return true;
 }
 
diff --git a/chrome/updater/ipc/proxy_impl_base_win.h b/chrome/updater/ipc/proxy_impl_base_win.h
index 901bfb43..af259132 100644
--- a/chrome/updater/ipc/proxy_impl_base_win.h
+++ b/chrome/updater/ipc/proxy_impl_base_win.h
@@ -83,11 +83,6 @@
         }
         VLOG(2) << "::CoCreateInstance failed: "
                 << base::win::WStringFromGUID(clsid) << ": " << std::hex << hr;
-
-        // TODO(crbug.com/1425609) - revert the CL that introduced this logging
-        // after the bug is resolved.
-        LogClsidEntries(clsid);
-
         if (hr == REGDB_E_CLASSNOTREG) {
           return base::unexpected(hr);
         }
diff --git a/chrome/updater/test/integration_test_commands.h b/chrome/updater/test/integration_test_commands.h
index d6e24032..3e323ce 100644
--- a/chrome/updater/test/integration_test_commands.h
+++ b/chrome/updater/test/integration_test_commands.h
@@ -130,10 +130,10 @@
   virtual void ExpectLastChecked() const = 0;
   virtual void ExpectLastStarted() const = 0;
   virtual void UninstallApp(const std::string& app_id) const = 0;
-
   virtual void RunOfflineInstall(bool is_legacy_install,
                                  bool is_silent_install) = 0;
-
+  virtual void RunOfflineInstallOsNotSupported(bool is_legacy_install,
+                                               bool is_silent_install) = 0;
   virtual void DMDeregisterDevice() = 0;
   virtual void DMCleanup() = 0;
 
diff --git a/chrome/updater/test/integration_test_commands_system.cc b/chrome/updater/test/integration_test_commands_system.cc
index 93680cba..ffce976 100644
--- a/chrome/updater/test/integration_test_commands_system.cc
+++ b/chrome/updater/test/integration_test_commands_system.cc
@@ -371,6 +371,13 @@
                 Param("silent", is_silent_install ? "true" : "false")});
   }
 
+  void RunOfflineInstallOsNotSupported(bool is_legacy_install,
+                                       bool is_silent_install) override {
+    RunCommand("run_offline_install_os_not_supported",
+               {Param("legacy_install", is_legacy_install ? "true" : "false"),
+                Param("silent", is_silent_install ? "true" : "false")});
+  }
+
   void DMDeregisterDevice() override { RunCommand("dm_deregister_device"); }
   void DMCleanup() override { RunCommand("dm_cleanup"); }
 
diff --git a/chrome/updater/test/integration_test_commands_user.cc b/chrome/updater/test/integration_test_commands_user.cc
index 8e69865..7b78a48 100644
--- a/chrome/updater/test/integration_test_commands_user.cc
+++ b/chrome/updater/test/integration_test_commands_user.cc
@@ -335,6 +335,12 @@
                                      is_silent_install);
   }
 
+  void RunOfflineInstallOsNotSupported(bool is_legacy_install,
+                                       bool is_silent_install) override {
+    updater::test::RunOfflineInstallOsNotSupported(
+        updater_scope_, is_legacy_install, is_silent_install);
+  }
+
   void DMDeregisterDevice() override {
     updater::test::DMDeregisterDevice(updater_scope_);
   }
diff --git a/chrome/updater/test/integration_tests.cc b/chrome/updater/test/integration_tests.cc
index f1d2c16..cc7fd99 100644
--- a/chrome/updater/test/integration_tests.cc
+++ b/chrome/updater/test/integration_tests.cc
@@ -412,6 +412,12 @@
     test_commands_->RunOfflineInstall(is_legacy_install, is_silent_install);
   }
 
+  void RunOfflineInstallOsNotSupported(bool is_legacy_install,
+                                       bool is_silent_install) {
+    test_commands_->RunOfflineInstallOsNotSupported(is_legacy_install,
+                                                    is_silent_install);
+  }
+
   void DMDeregisterDevice() { test_commands_->DMDeregisterDevice(); }
 
   void DMCleanup() { test_commands_->DMCleanup(); }
@@ -645,13 +651,6 @@
 }
 
 TEST_F(IntegrationTest, CheckForUpdate) {
-#if BUILDFLAG(IS_WIN)
-  // TODO(crbug.com/1425609): Remove procmon logging once bug is fixed.
-  const base::ScopedClosureRunner stop_procmon_logging(
-      base::BindOnce(&updater::test::StopProcmonLogging,
-                     updater::test::StartProcmonLogging()));
-#endif  // #if BUILDFLAG(IS_WIN)
-
   ScopedServer test_server(test_commands_);
   ASSERT_NO_FATAL_FAILURE(Install());
 
@@ -1148,6 +1147,8 @@
   ASSERT_NO_FATAL_FAILURE(Uninstall());
 }
 
+#if BUILDFLAG(IS_WIN)
+// TODO(crbug.com/1281688): standalone installers are supported on Windows only.
 TEST_F(IntegrationTest, OfflineInstall) {
   ASSERT_NO_FATAL_FAILURE(Install());
   ASSERT_NO_FATAL_FAILURE(ExpectInstalled());
@@ -1156,7 +1157,16 @@
   ASSERT_NO_FATAL_FAILURE(Uninstall());
 }
 
-TEST_F(IntegrationTest, SilentOfflineInstall) {
+TEST_F(IntegrationTest, OfflineInstallOsNotSupported) {
+  ASSERT_NO_FATAL_FAILURE(Install());
+  ASSERT_NO_FATAL_FAILURE(ExpectInstalled());
+  ASSERT_NO_FATAL_FAILURE(
+      RunOfflineInstallOsNotSupported(/*is_legacy_install=*/false,
+                                      /*is_silent_install=*/false));
+  ASSERT_NO_FATAL_FAILURE(Uninstall());
+}
+
+TEST_F(IntegrationTest, OfflineInstallSilent) {
   ASSERT_NO_FATAL_FAILURE(Install());
   ASSERT_NO_FATAL_FAILURE(ExpectInstalled());
   ASSERT_NO_FATAL_FAILURE(RunOfflineInstall(/*is_legacy_install=*/false,
@@ -1164,7 +1174,16 @@
   ASSERT_NO_FATAL_FAILURE(Uninstall());
 }
 
-TEST_F(IntegrationTest, LegacySilentOfflineInstall) {
+TEST_F(IntegrationTest, OfflineInstallOsNotSupportedSilent) {
+  ASSERT_NO_FATAL_FAILURE(Install());
+  ASSERT_NO_FATAL_FAILURE(ExpectInstalled());
+  ASSERT_NO_FATAL_FAILURE(
+      RunOfflineInstallOsNotSupported(/*is_legacy_install=*/false,
+                                      /*is_silent_install=*/true));
+  ASSERT_NO_FATAL_FAILURE(Uninstall());
+}
+
+TEST_F(IntegrationTest, OfflineInstallSilentLegacy) {
   ASSERT_NO_FATAL_FAILURE(Install());
   ASSERT_NO_FATAL_FAILURE(ExpectInstalled());
   ASSERT_NO_FATAL_FAILURE(RunOfflineInstall(/*is_legacy_install=*/true,
@@ -1172,6 +1191,16 @@
   ASSERT_NO_FATAL_FAILURE(Uninstall());
 }
 
+TEST_F(IntegrationTest, OfflineInstallOsNotSupportedSilentLegacy) {
+  ASSERT_NO_FATAL_FAILURE(Install());
+  ASSERT_NO_FATAL_FAILURE(ExpectInstalled());
+  ASSERT_NO_FATAL_FAILURE(
+      RunOfflineInstallOsNotSupported(/*is_legacy_install=*/true,
+                                      /*is_silent_install=*/true));
+  ASSERT_NO_FATAL_FAILURE(Uninstall());
+}
+#endif  // BUILDFLAG(IS_WIN)
+
 TEST_F(IntegrationTest, CrashUsageStatsEnabled) {
 #if BUILDFLAG(IS_WIN) && defined(ADDRESS_SANITIZER)
   GTEST_SKIP() << "Crash tests disabled for Win ASAN.";
diff --git a/chrome/updater/test/integration_tests_helper.cc b/chrome/updater/test/integration_tests_helper.cc
index aba9d03..c3d1d28 100644
--- a/chrome/updater/test/integration_tests_helper.cc
+++ b/chrome/updater/test/integration_tests_helper.cc
@@ -385,6 +385,10 @@
      WithSwitch("silent",
                 WithSwitch("legacy_install",
                            WithSystemScope(Wrap(&RunOfflineInstall))))},
+    {"run_offline_install_os_not_supported",
+     WithSwitch("silent", WithSwitch("legacy_install",
+                                     WithSystemScope(Wrap(
+                                         &RunOfflineInstallOsNotSupported))))},
     {"dm_deregister_device", WithSystemScope(Wrap(&DMDeregisterDevice))},
     {"dm_cleanup", WithSystemScope(Wrap(&DMCleanup))},
   };
diff --git a/chrome/updater/test/integration_tests_impl.cc b/chrome/updater/test/integration_tests_impl.cc
index 5032112e..47c46fc 100644
--- a/chrome/updater/test/integration_tests_impl.cc
+++ b/chrome/updater/test/integration_tests_impl.cc
@@ -825,6 +825,22 @@
   }
 }
 
+#if !BUILDFLAG(IS_WIN)
+void RunOfflineInstall(UpdaterScope scope,
+                       bool is_legacy_install,
+                       bool is_silent_install) {
+  // TODO(crbug.com/1281688).
+  NOTREACHED();
+}
+
+void RunOfflineInstallOsNotSupported(UpdaterScope scope,
+                                     bool is_legacy_install,
+                                     bool is_silent_install) {
+  // TODO(crbug.com/1281688).
+  NOTREACHED();
+}
+#endif  // IS_WIN
+
 void DMDeregisterDevice(UpdaterScope scope) {
   if (!IsSystemInstall(GetTestScope())) {
     return;
diff --git a/chrome/updater/test/integration_tests_impl.h b/chrome/updater/test/integration_tests_impl.h
index 8e6630f..b83cd287 100644
--- a/chrome/updater/test/integration_tests_impl.h
+++ b/chrome/updater/test/integration_tests_impl.h
@@ -280,6 +280,10 @@
                        bool is_legacy_install,
                        bool is_silent_install);
 
+void RunOfflineInstallOsNotSupported(UpdaterScope scope,
+                                     bool is_legacy_install,
+                                     bool is_silent_install);
+
 base::CommandLine MakeElevated(base::CommandLine command_line);
 
 void DMDeregisterDevice(UpdaterScope scope);
diff --git a/chrome/updater/test/integration_tests_linux.cc b/chrome/updater/test/integration_tests_linux.cc
index e8b44ea9..9e880a7 100644
--- a/chrome/updater/test/integration_tests_linux.cc
+++ b/chrome/updater/test/integration_tests_linux.cc
@@ -229,12 +229,6 @@
                           base::FilePath(FILE_PATH_LITERAL("NONE")));
 }
 
-void RunOfflineInstall(UpdaterScope scope,
-                       bool is_legacy_install,
-                       bool is_silent_install) {
-  // TODO(crbug.com/1286574).
-}
-
 base::CommandLine MakeElevated(base::CommandLine command_line) {
   command_line.PrependWrapper("/usr/bin/sudo");
   return command_line;
diff --git a/chrome/updater/test/integration_tests_mac.mm b/chrome/updater/test/integration_tests_mac.mm
index dcf1d4a..ef6476b 100644
--- a/chrome/updater/test/integration_tests_mac.mm
+++ b/chrome/updater/test/integration_tests_mac.mm
@@ -13,6 +13,7 @@
 #include "base/files/file_util.h"
 #include "base/logging.h"
 #include "base/mac/foundation_util.h"
+#include "base/notreached.h"
 #include "base/path_service.h"
 #include "base/process/launch.h"
 #include "base/run_loop.h"
@@ -350,12 +351,6 @@
                           base::FilePath(FILE_PATH_LITERAL("NONE")));
 }
 
-void RunOfflineInstall(UpdaterScope scope,
-                       bool is_legacy_install,
-                       bool is_silent_install) {
-  // TODO(crbug.com/1286574).
-}
-
 base::CommandLine MakeElevated(base::CommandLine command_line) {
   command_line.PrependWrapper("/usr/bin/sudo");
   return command_line;
diff --git a/chrome/updater/test/integration_tests_win.cc b/chrome/updater/test/integration_tests_win.cc
index 64f70513..1d22320 100644
--- a/chrome/updater/test/integration_tests_win.cc
+++ b/chrome/updater/test/integration_tests_win.cc
@@ -162,7 +162,10 @@
 
   if (is_active_and_sxs) {
     for (const wchar_t* key : {CLIENTS_KEY, UPDATER_KEY}) {
-      EXPECT_EQ(is_installed, RegKeyExists(root, key));
+      // This expectation can fail when Chrome or another program is writing
+      // `dr` in its `CLIENT_STATE_KEY` subkey and the test is checking for a
+      // clean uninstall.
+      EXPECT_EQ(is_installed, RegKeyExists(root, key)) << key;
     }
 
     EXPECT_EQ(is_installed, base::PathExists(*GetGoogleUpdateExePath(scope)));
@@ -532,6 +535,177 @@
   return version.Get();
 }
 
+void RunOfflineInstallWithManifest(UpdaterScope scope,
+                                   bool is_legacy_install,
+                                   bool is_silent_install,
+                                   const std::string& manifest_format,
+                                   int string_resource_id_to_find,
+                                   bool expect_success) {
+  constexpr wchar_t kTestAppID[] = L"{CDABE316-39CD-43BA-8440-6D1E0547AEE6}";
+  const base::Version kTestPV("1.2.3.4");
+  const std::wstring manifest_filename(L"OfflineManifest.gup");
+  const std::wstring cmd_exe_arbitrarily_named(L"arbitrarily_named_cmd.exe");
+  const std::string script_name("test_installer.bat");
+  const std::wstring offline_dir_guid(
+      L"{7B3A5597-DDEA-409B-B900-4C3D2A94A75C}");
+  const HKEY root = UpdaterScopeToHKeyRoot(scope);
+  const std::wstring app_client_state_key = GetAppClientStateKey(kTestAppID);
+
+  EXPECT_TRUE(DeleteRegKey(root, app_client_state_key));
+
+  const absl::optional<base::FilePath> updater_exe =
+      GetUpdaterExecutablePath(scope);
+  ASSERT_TRUE(updater_exe.has_value());
+
+  const base::FilePath exe_dir(updater_exe->DirName());
+  const base::FilePath offline_dir(
+      exe_dir.Append(L"Offline").Append(offline_dir_guid));
+  const base::FilePath offline_app_dir(offline_dir.Append(kTestAppID));
+  const base::FilePath offline_app_scripts_dir(
+      offline_app_dir.Append(L"Scripts"));
+  ASSERT_TRUE(base::CreateDirectory(offline_app_scripts_dir));
+
+  // Create a batch file as the installer script, which creates some registry
+  // values as the installation artifacts.
+  const base::FilePath batch_script_path(
+      offline_app_scripts_dir.AppendASCII(script_name));
+
+  // Create a unique name for a shared event to be waited for in this process
+  // and signaled in the offline installer process to confirm the installer
+  // was run.
+  test::EventHolder event_holder(test::CreateWaitableEventForTest());
+
+  EXPECT_TRUE(base::WriteFile(
+      batch_script_path,
+      [](UpdaterScope scope, const std::string& app_client_state_key,
+         const std::wstring& event_name) -> std::string {
+        const std::string reg_hive = IsSystemInstall(scope) ? "HKLM" : "HKCU";
+
+        base::CommandLine post_install_cmd(
+            GetTestProcessCommandLine(scope, GetTestName()));
+        post_install_cmd.AppendSwitchNative(kTestEventToSignal, event_name);
+        std::vector<std::string> commands;
+        const struct {
+          const char* value_name;
+          const char* type;
+          const std::string value;
+        } reg_items[5] = {
+            {"InstallerResult", "REG_DWORD", "0"},
+            {"InstallerError", "REG_DWORD", "0"},
+            {"InstallerExtraCode1", "REG_DWORD", "0"},
+            {"InstallerResultUIString", "REG_SZ", "CoolApp"},
+            {"InstallerSuccessLaunchCmdLine", "REG_SZ",
+             base::WideToASCII(post_install_cmd.GetCommandLineString())},
+        };
+        for (const auto& reg_item : reg_items) {
+          commands.push_back(base::StringPrintf(
+              "REG.exe ADD \"%s\\%s\" /v %s /t %s /d \"%s\" /f /reg:32",
+              reg_hive.c_str(), app_client_state_key.c_str(),
+              reg_item.value_name, reg_item.type, reg_item.value.c_str()));
+        }
+        return base::JoinString(commands, "\n");
+      }(scope, base::WideToASCII(app_client_state_key), event_holder.name)));
+
+  // The updater only allows `.exe` or `.msi` to run from the offline directory.
+  // Setup `cmd.exe` as the wrapper installer.
+  base::FilePath cmd_exe_path;
+  ASSERT_TRUE(base::PathService::Get(base::DIR_SYSTEM, &cmd_exe_path));
+  cmd_exe_path = cmd_exe_path.Append(L"cmd.exe");
+  ASSERT_TRUE(base::CopyFile(
+      cmd_exe_path, offline_app_dir.Append(cmd_exe_arbitrarily_named)));
+
+  base::FilePath manifest_path = offline_dir.Append(manifest_filename);
+  int64_t exe_size = 0;
+  EXPECT_TRUE(base::GetFileSize(cmd_exe_path, &exe_size));
+  const std::string manifest = base::StringPrintf(
+      manifest_format.c_str(), kTestAppID, kTestPV.GetString().c_str(),
+      exe_size, batch_script_path.value().c_str());
+  EXPECT_TRUE(base::WriteFile(manifest_path, manifest));
+
+  // Trigger offline install.
+  ASSERT_TRUE(LaunchOfflineInstallProcess(
+                  is_legacy_install, updater_exe.value(), scope, kTestAppID,
+                  offline_dir_guid, is_silent_install)
+                  .IsValid());
+
+  if (is_silent_install) {
+    EXPECT_TRUE(WaitForUpdaterExit(scope));
+  } else {
+    // Dismiss the installation completion dialog, then wait for the process
+    // exit.
+    EXPECT_TRUE(WaitFor(
+        base::BindLambdaForTesting([string_resource_id_to_find] {
+          // Enumerate the top-level dialogs to find the setup dialog.
+          WindowEnumerator(
+              ::GetDesktopWindow(), base::BindRepeating([](HWND hwnd) {
+                return WindowEnumerator::IsSystemDialog(hwnd) &&
+                       base::Contains(WindowEnumerator::GetWindowText(hwnd),
+                                      GetLocalizedStringF(
+                                          IDS_INSTALLER_DISPLAY_NAME_BASE,
+                                          GetLocalizedString(
+                                              IDS_FRIENDLY_COMPANY_NAME_BASE)));
+              }),
+              base::BindLambdaForTesting(
+                  [string_resource_id_to_find](HWND hwnd) {
+                    // Enumerates the dialog items to search for installation
+                    // complete message. Once found, close the dialog.
+                    WindowEnumerator(
+                        hwnd,
+                        base::BindLambdaForTesting([string_resource_id_to_find](
+                                                       HWND hwnd) {
+                          return base::Contains(
+                              WindowEnumerator::GetWindowText(hwnd),
+                              GetLocalizedString(string_resource_id_to_find));
+                        }),
+                        base::BindRepeating([](HWND hwnd) {
+                          ::PostMessage(::GetParent(hwnd), WM_CLOSE, 0, 0);
+                        }))
+                        .Run();
+                  }))
+              .Run();
+          return !IsUpdaterRunning();
+        }),
+        base::BindRepeating(
+            [] { VLOG(0) << "Still waiting for the process exit."; })));
+  }
+
+  const base::Version pv =
+      base::MakeRefCounted<PersistedData>(
+          scope, CreateGlobalPrefs(scope)->GetPrefService())
+          ->GetProductVersion(base::WideToASCII(kTestAppID));
+
+  base::win::RegKey key;
+  LONG registry_result =
+      key.Open(root, app_client_state_key.c_str(), Wow6432(KEY_QUERY_VALUE));
+
+  if (!expect_success) {
+    EXPECT_EQ(registry_result, ERROR_FILE_NOT_FOUND);
+    EXPECT_FALSE(pv.IsValid());
+    return;
+  }
+
+  // Updater should have written "pv".
+  ASSERT_TRUE(pv.IsValid());
+  EXPECT_EQ(pv, kTestPV);
+
+  // App installer should have created the expected reg value.
+  std::wstring value;
+  EXPECT_EQ(registry_result, ERROR_SUCCESS);
+  EXPECT_EQ(key.ReadValue(kRegValueLastInstallerResultUIString, &value),
+            ERROR_SUCCESS);
+  EXPECT_EQ(value, L"CoolApp");
+
+  if (!is_silent_install) {
+    // Silent install does not run post-install command. For other cases the
+    // event should have been signaled by the post-install command via the
+    // installer result API.
+    EXPECT_TRUE(
+        event_holder.event.TimedWait(TestTimeouts::action_max_timeout()));
+  }
+
+  EXPECT_TRUE(DeleteRegKey(root, app_client_state_key));
+}
+
 }  // namespace
 
 base::FilePath GetSetupExecutablePath() {
@@ -1726,11 +1900,10 @@
 void RunOfflineInstall(UpdaterScope scope,
                        bool is_legacy_install,
                        bool is_silent_install) {
-  constexpr wchar_t kTestAppID[] = L"{CDABE316-39CD-43BA-8440-6D1E0547AEE6}";
-  const base::Version kTestPV("1.2.3.4");
   constexpr char kManifestFormat[] =
       "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
       "<response protocol=\"3.0\">\n"
+      "  <systemrequirements platform=\"win\"/>\n"
       "  <app appid=\"%ls\" status=\"ok\">\n"
       "    <updatecheck status=\"ok\">\n"
       "      <manifest version=\"%s\">\n"
@@ -1750,159 +1923,40 @@
       "    </data>\n"
       "  </app>\n"
       "</response>\n";
+  RunOfflineInstallWithManifest(scope, is_legacy_install, is_silent_install,
+                                kManifestFormat,
+                                IDS_BUNDLE_INSTALLED_SUCCESSFULLY_BASE, true);
+}
 
-  const std::wstring manifest_filename(L"OfflineManifest.gup");
-  const std::wstring cmd_exe_arbitrarily_named(L"arbitrarily_named_cmd.exe");
-  const std::string script_name("test_installer.bat");
-  const std::wstring offline_dir_guid(
-      L"{7B3A5597-DDEA-409B-B900-4C3D2A94A75C}");
-  const HKEY root = UpdaterScopeToHKeyRoot(scope);
-  const std::wstring app_client_state_key = GetAppClientStateKey(kTestAppID);
-
-  EXPECT_TRUE(DeleteRegKey(root, app_client_state_key));
-
-  const absl::optional<base::FilePath> updater_exe =
-      GetUpdaterExecutablePath(scope);
-  ASSERT_TRUE(updater_exe.has_value());
-
-  const base::FilePath exe_dir(updater_exe->DirName());
-  const base::FilePath offline_dir(
-      exe_dir.Append(L"Offline").Append(offline_dir_guid));
-  const base::FilePath offline_app_dir(offline_dir.Append(kTestAppID));
-  const base::FilePath offline_app_scripts_dir(
-      offline_app_dir.Append(L"Scripts"));
-  ASSERT_TRUE(base::CreateDirectory(offline_app_scripts_dir));
-
-  // Create a batch file as the installer script, which creates some registry
-  // values as the installation artifacts.
-  const base::FilePath batch_script_path(
-      offline_app_scripts_dir.AppendASCII(script_name));
-
-  // Create a unique name for a shared event to be waited for in this process
-  // and signaled in the offline installer process to confirm the installer
-  // was run.
-  test::EventHolder event_holder(test::CreateWaitableEventForTest());
-
-  EXPECT_TRUE(base::WriteFile(
-      batch_script_path,
-      [](UpdaterScope scope, const std::string& app_client_state_key,
-         const std::wstring& event_name) -> std::string {
-        const std::string reg_hive = IsSystemInstall(scope) ? "HKLM" : "HKCU";
-
-        base::CommandLine post_install_cmd(
-            GetTestProcessCommandLine(scope, GetTestName()));
-        post_install_cmd.AppendSwitchNative(kTestEventToSignal, event_name);
-        std::vector<std::string> commands;
-        const struct {
-          const char* value_name;
-          const char* type;
-          const std::string value;
-        } reg_items[5] = {
-            {"InstallerResult", "REG_DWORD", "0"},
-            {"InstallerError", "REG_DWORD", "0"},
-            {"InstallerExtraCode1", "REG_DWORD", "0"},
-            {"InstallerResultUIString", "REG_SZ", "CoolApp"},
-            {"InstallerSuccessLaunchCmdLine", "REG_SZ",
-             base::WideToASCII(post_install_cmd.GetCommandLineString())},
-        };
-        for (const auto& reg_item : reg_items) {
-          commands.push_back(base::StringPrintf(
-              "REG.exe ADD \"%s\\%s\" /v %s /t %s /d \"%s\" /f /reg:32",
-              reg_hive.c_str(), app_client_state_key.c_str(),
-              reg_item.value_name, reg_item.type, reg_item.value.c_str()));
-        }
-        return base::JoinString(commands, "\n");
-      }(scope, base::WideToASCII(app_client_state_key), event_holder.name)));
-
-  // The updater only allows `.exe` or `.msi` to run from the offline directory.
-  // Setup `cmd.exe` as the wrapper installer.
-  base::FilePath cmd_exe_path;
-  ASSERT_TRUE(base::PathService::Get(base::DIR_SYSTEM, &cmd_exe_path));
-  cmd_exe_path = cmd_exe_path.Append(L"cmd.exe");
-  ASSERT_TRUE(base::CopyFile(
-      cmd_exe_path, offline_app_dir.Append(cmd_exe_arbitrarily_named)));
-
-  base::FilePath manifest_path = offline_dir.Append(manifest_filename);
-  int64_t exe_size = 0;
-  EXPECT_TRUE(base::GetFileSize(cmd_exe_path, &exe_size));
-  const std::string manifest = base::StringPrintf(
-      kManifestFormat, kTestAppID, kTestPV.GetString().c_str(), exe_size,
-      batch_script_path.value().c_str());
-  EXPECT_TRUE(base::WriteFile(manifest_path, manifest));
-
-  // Trigger offline install.
-  ASSERT_TRUE(LaunchOfflineInstallProcess(
-                  is_legacy_install, updater_exe.value(), scope, kTestAppID,
-                  offline_dir_guid, is_silent_install)
-                  .IsValid());
-
-  if (is_silent_install) {
-    EXPECT_TRUE(WaitForUpdaterExit(scope));
-  } else {
-    // Dismiss the installation completion dialog, then wait for the process
-    // exit.
-    EXPECT_TRUE(WaitFor(
-        base::BindRepeating([] {
-          // Enumerate the top-level dialogs to find the setup dialog.
-          WindowEnumerator(
-              ::GetDesktopWindow(), base::BindRepeating([](HWND hwnd) {
-                return WindowEnumerator::IsSystemDialog(hwnd) &&
-                       base::Contains(WindowEnumerator::GetWindowText(hwnd),
-                                      GetLocalizedStringF(
-                                          IDS_INSTALLER_DISPLAY_NAME_BASE,
-                                          GetLocalizedString(
-                                              IDS_FRIENDLY_COMPANY_NAME_BASE)));
-              }),
-              base::BindRepeating([](HWND hwnd) {
-                // Enumerates the dialog items to search for installation
-                // complete message. Once found, close the dialog.
-                WindowEnumerator(
-                    hwnd, base::BindRepeating([](HWND hwnd) {
-                      return base::Contains(
-                          WindowEnumerator::GetWindowText(hwnd),
-                          GetLocalizedString(
-                              IDS_BUNDLE_INSTALLED_SUCCESSFULLY_BASE));
-                    }),
-                    base::BindRepeating([](HWND hwnd) {
-                      ::PostMessage(::GetParent(hwnd), WM_CLOSE, 0, 0);
-                    }))
-                    .Run();
-              }))
-              .Run();
-
-          return !IsUpdaterRunning();
-        }),
-        base::BindLambdaForTesting(
-            [] { VLOG(0) << "Still waiting for the process exit."; })));
-  }
-
-  // Updater should have written "pv".
-  const base::Version pv =
-      base::MakeRefCounted<PersistedData>(
-          scope, CreateGlobalPrefs(scope)->GetPrefService())
-          ->GetProductVersion(base::WideToASCII(kTestAppID));
-  ASSERT_TRUE(pv.IsValid());
-  EXPECT_EQ(pv, kTestPV);
-
-  // App installer should have created the expected reg value.
-  base::win::RegKey key;
-  std::wstring value;
-  EXPECT_EQ(
-      key.Open(root, app_client_state_key.c_str(), Wow6432(KEY_QUERY_VALUE)),
-      ERROR_SUCCESS);
-  EXPECT_EQ(key.ReadValue(kRegValueLastInstallerResultUIString, &value),
-            ERROR_SUCCESS);
-  EXPECT_EQ(value, L"CoolApp");
-
-  if (!is_silent_install) {
-    // Silent install does not run post-install command. For other cases the
-    // event should have been signaled by the post-install command via the
-    // installer result API.
-    EXPECT_TRUE(
-        event_holder.event.TimedWait(TestTimeouts::action_max_timeout()));
-  }
-
-  EXPECT_TRUE(DeleteRegKey(root, app_client_state_key));
+void RunOfflineInstallOsNotSupported(UpdaterScope scope,
+                                     bool is_legacy_install,
+                                     bool is_silent_install) {
+  constexpr char kManifestFormat[] =
+      "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
+      "<response protocol=\"3.0\">\n"
+      "  <systemrequirements platform=\"minix\"/>\n"
+      "  <app appid=\"%ls\" status=\"ok\">\n"
+      "    <updatecheck status=\"ok\">\n"
+      "      <manifest version=\"%s\">\n"
+      "        <packages>\n"
+      "          <package hash_sha256=\"sha256hash_foobar\"\n"
+      "            name=\"cmd.exe\" required=\"true\" size=\"%lld\"/>\n"
+      "        </packages>\n"
+      "        <actions>\n"
+      "          <action event=\"install\"\n"
+      "            run=\"cmd.exe\"\n"
+      "            arguments=\"/c &quot;%ls&quot; \"/>\n"
+      "        </actions>\n"
+      "      </manifest>\n"
+      "    </updatecheck>\n"
+      "    <data index=\"verboselogging\" name=\"install\" status=\"ok\">\n"
+      "      {\"distribution\": { \"verbose_logging\": true}}\n"
+      "    </data>\n"
+      "  </app>\n"
+      "</response>\n";
+  RunOfflineInstallWithManifest(scope, is_legacy_install, is_silent_install,
+                                kManifestFormat,
+                                IDS_INSTALL_OS_NOT_SUPPORTED_BASE, false);
 }
 
 base::CommandLine MakeElevated(base::CommandLine command_line) {
diff --git a/chrome/updater/util/win_util.cc b/chrome/updater/util/win_util.cc
index 3cc7d30b..11f4686 100644
--- a/chrome/updater/util/win_util.cc
+++ b/chrome/updater/util/win_util.cc
@@ -1100,7 +1100,6 @@
       base::StrCat({base::StrCat({L"Software\\Classes\\CLSID\\",
                                   base::win::WStringFromGUID(clsid)}),
                     L"\\LocalServer32"}));
-
   for (const HKEY root : {HKEY_LOCAL_MACHINE, HKEY_CURRENT_USER}) {
     for (const REGSAM key_flag : {KEY_WOW64_32KEY, KEY_WOW64_64KEY}) {
       base::win::RegKey key;
diff --git a/chrome/updater/util/win_util_unittest.cc b/chrome/updater/util/win_util_unittest.cc
index af3e461..7a92945 100644
--- a/chrome/updater/util/win_util_unittest.cc
+++ b/chrome/updater/util/win_util_unittest.cc
@@ -510,7 +510,6 @@
   CLSID clsid = {};
   EXPECT_HRESULT_SUCCEEDED(
       ::CLSIDFromProgID(L"InternetExplorer.Application", &clsid));
-
   LogClsidEntries(clsid);
 }
 
diff --git a/chrome/updater/win/setup/setup_util.cc b/chrome/updater/win/setup/setup_util.cc
index 8608617..cc23e63 100644
--- a/chrome/updater/win/setup/setup_util.cc
+++ b/chrome/updater/win/setup/setup_util.cc
@@ -302,7 +302,6 @@
                            bool is_internal,
                            WorkItemList* list) {
   CHECK(list);
-  VLOG(1) << __func__ << ": " << com_server_path << ": " << is_internal;
 
   if (com_server_path.empty()) {
     LOG(DFATAL) << "com_server_path is invalid.";
@@ -310,14 +309,12 @@
   }
 
   for (const auto& clsid : GetServers(is_internal, UpdaterScope::kUser)) {
-    VLOG(1) << "Registering clsid: " << base::win::WStringFromGUID(clsid);
     AddInstallServerWorkItems(HKEY_CURRENT_USER, clsid, com_server_path,
                               is_internal, list);
     AddInstallComProgIdWorkItems(UpdaterScope::kUser, clsid, list);
   }
 
   for (const auto& iid : GetInterfaces(is_internal, UpdaterScope::kUser)) {
-    VLOG(1) << "Registering interface: " << base::win::WStringFromGUID(iid);
     AddInstallComInterfaceWorkItems(HKEY_CURRENT_USER, com_server_path, iid,
                                     list);
   }
@@ -327,7 +324,6 @@
                             bool internal_service,
                             WorkItemList* list) {
   CHECK(::IsUserAnAdmin());
-  VLOG(1) << __func__ << ": " << com_service_path << ": " << internal_service;
 
   if (com_service_path.empty()) {
     LOG(DFATAL) << "com_service_path is invalid.";
@@ -357,13 +353,11 @@
       com_service_command, com_switch, UPDATER_KEY, clsids, {}));
 
   for (const auto& clsid : clsids) {
-    VLOG(1) << "Registering clsid: " << base::win::WStringFromGUID(clsid);
     AddInstallComProgIdWorkItems(UpdaterScope::kSystem, clsid, list);
   }
 
   for (const auto& iid :
        GetInterfaces(internal_service, UpdaterScope::kSystem)) {
-    VLOG(1) << "Registering interface: " << base::win::WStringFromGUID(iid);
     AddInstallComInterfaceWorkItems(HKEY_LOCAL_MACHINE, com_service_path, iid,
                                     list);
   }
diff --git a/chrome/utility/importer/safari_importer_unittest.mm b/chrome/utility/importer/safari_importer_unittest.mm
index 704d730..d728f5f9 100644
--- a/chrome/utility/importer/safari_importer_unittest.mm
+++ b/chrome/utility/importer/safari_importer_unittest.mm
@@ -26,7 +26,9 @@
 #include "sql/database.h"
 #include "testing/platform_test.h"
 
-using base::ASCIIToUTF16;
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
 
 // In order to test the Safari import functionality effectively, we store a
 // simulated Library directory containing dummy data files in the same
diff --git a/chromeos/ash/components/auth_panel/BUILD.gn b/chromeos/ash/components/auth_panel/BUILD.gn
new file mode 100644
index 0000000..86f6f52
--- /dev/null
+++ b/chromeos/ash/components/auth_panel/BUILD.gn
@@ -0,0 +1,23 @@
+# Copyright 2023 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import("//build/config/chromeos/ui_mode.gni")
+
+assert(is_chromeos_ash)
+
+component("auth_panel") {
+  defines = [ "IS_CHROMEOS_ASH_COMPONENTS_AUTH_PANEL" ]
+  deps = [
+    "//base",
+    "//chromeos/ash/components/osauth/public",
+    "//ui/views",
+  ]
+  sources = [
+    "auth_panel.cc",
+    "auth_panel.h",
+    "factor_auth_view.h",
+    "factor_auth_view_factory.cc",
+    "factor_auth_view_factory.h",
+  ]
+}
diff --git a/chromeos/ash/components/auth_panel/DEPS b/chromeos/ash/components/auth_panel/DEPS
new file mode 100644
index 0000000..979199e
--- /dev/null
+++ b/chromeos/ash/components/auth_panel/DEPS
@@ -0,0 +1,30 @@
+noparent = True
+
+
+include_rules = [
+  "+base",
+  "+ui/views",
+  "+chromeos/ash/components/osauth/public",
+
+  # Abseil is allowed by default, but some features are banned. See
+  # //styleguide/c++/c++-features.md.
+  # Please keep this section in sync with //DEPS.
+  '+third_party/abseil-cpp',
+  '-third_party/abseil-cpp/absl/algorithm/container.h',
+  '-third_party/abseil-cpp/absl/container',
+  '-third_party/abseil-cpp/absl/crc',
+  '-third_party/abseil-cpp/absl/flags',
+  '-third_party/abseil-cpp/absl/functional/any_invocable.h',
+  '-third_party/abseil-cpp/absl/functional/bind_front.h',
+  '-third_party/abseil-cpp/absl/functional/function_ref.h',
+  '-third_party/abseil-cpp/absl/hash',
+  '-third_party/abseil-cpp/absl/log',
+  '-third_party/abseil-cpp/absl/random',
+  '-third_party/abseil-cpp/absl/status/statusor.h',
+  '-third_party/abseil-cpp/absl/strings',
+  '+third_party/abseil-cpp/absl/strings/cord.h',
+  '-third_party/abseil-cpp/absl/synchronization',
+  '-third_party/abseil-cpp/absl/time',
+  '-third_party/abseil-cpp/absl/types/any.h',
+  '-third_party/abseil-cpp/absl/types/span.h',
+]
diff --git a/chromeos/ash/components/auth_panel/auth_panel.cc b/chromeos/ash/components/auth_panel/auth_panel.cc
new file mode 100644
index 0000000..a691f81
--- /dev/null
+++ b/chromeos/ash/components/auth_panel/auth_panel.cc
@@ -0,0 +1,60 @@
+// 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 "chromeos/ash/components/auth_panel/auth_panel.h"
+
+#include "base/logging.h"
+#include "base/notreached.h"
+#include "chromeos/ash/components/auth_panel/factor_auth_view.h"
+#include "chromeos/ash/components/auth_panel/factor_auth_view_factory.h"
+#include "chromeos/ash/components/osauth/public/common_types.h"
+
+namespace ash {
+
+AuthPanel::AuthPanel(std::unique_ptr<FactorAuthViewFactory> view_factory)
+    : view_factory_(std::move(view_factory)) {}
+
+AuthPanel::~AuthPanel() = default;
+
+void AuthPanel::InitializeUi(AuthFactorsSet factors,
+                             AuthHubConnector* connector) {
+  CHECK(views_.empty());
+  for (const auto factor : factors) {
+    auto view = view_factory_->CreateFactorAuthView(factor);
+    view->OnFactorStateChanged(AuthFactorState::kCheckingForPresence);
+    views_[factor] = std::move(view);
+  }
+}
+
+void AuthPanel::OnFactorListChanged(FactorsStatusMap factors_with_status) {
+  views_.clear();
+  for (const auto& [factor, status] : factors_with_status) {
+    auto view = view_factory_->CreateFactorAuthView(factor);
+    view->OnFactorStateChanged(status);
+    views_[factor] = std::move(view);
+  }
+}
+
+void AuthPanel::OnFactorStatusesChanged(FactorsStatusMap incremental_update) {
+  for (const auto& [factor, status] : incremental_update) {
+    CHECK(views_.contains(factor));
+    views_[factor]->OnFactorStateChanged(status);
+  }
+}
+
+void AuthPanel::OnFactorAuthFailure(AshAuthFactor factor) {
+  CHECK(views_.contains(factor));
+  views_[factor]->OnAuthFailure();
+}
+
+void AuthPanel::OnFactorAuthSuccess(AshAuthFactor factor) {
+  CHECK(views_.contains(factor));
+  views_[factor]->OnAuthSuccess();
+}
+
+void AuthPanel::OnEndAuthentication() {
+  NOTIMPLEMENTED();
+}
+
+}  // namespace ash
diff --git a/ash/login/ui/auth_panel.h b/chromeos/ash/components/auth_panel/auth_panel.h
similarity index 67%
rename from ash/login/ui/auth_panel.h
rename to chromeos/ash/components/auth_panel/auth_panel.h
index a45897d..25a2a038 100644
--- a/ash/login/ui/auth_panel.h
+++ b/chromeos/ash/components/auth_panel/auth_panel.h
@@ -2,26 +2,31 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef ASH_LOGIN_UI_AUTH_PANEL_H_
-#define ASH_LOGIN_UI_AUTH_PANEL_H_
+#ifndef CHROMEOS_ASH_COMPONENTS_AUTH_PANEL_AUTH_PANEL_H_
+#define CHROMEOS_ASH_COMPONENTS_AUTH_PANEL_AUTH_PANEL_H_
 
 #include <memory>
 
-#include "ash/login/ui/factor_auth_view.h"
-#include "chromeos/ash/components/osauth/public/auth_attempt_consumer.h"
+#include "base/containers/flat_map.h"
 #include "chromeos/ash/components/osauth/public/auth_factor_status_consumer.h"
 #include "chromeos/ash/components/osauth/public/common_types.h"
 
 namespace ash {
 
+class FactorAuthView;
+class FactorAuthViewFactory;
+
 // Controller class that orchestrates the several `FactorAuthView` objects.
-// Responsible for listening for changes in auth factor state, and auth attempt
+// Responsible for :
+// - Listening for changes in auth factor state, auth attempt
 // results, and propagating them to the respective `FactorAuthView` objects.
-// Also responsible for the general layout of the authentication UI, hiding and
+// - The general layout of the authentication UI, hiding and
 // showing UI elements for particular auth factors when their status change.
+// - Tracking selected factors, in the event where a factor can be toggled,
+// for instance, with password/pin.
 class AuthPanel : public AuthFactorStatusConsumer {
  public:
-  AuthPanel();
+  explicit AuthPanel(std::unique_ptr<FactorAuthViewFactory> view_factory);
   AuthPanel(const AuthPanel&) = delete;
   AuthPanel(AuthPanel&&) = delete;
   AuthPanel& operator=(const AuthPanel&) = delete;
@@ -39,8 +44,9 @@
 
  private:
   base::flat_map<AshAuthFactor, std::unique_ptr<FactorAuthView>> views_;
+  std::unique_ptr<FactorAuthViewFactory> view_factory_;
 };
 
 }  // namespace ash
 
-#endif  // ASH_LOGIN_UI_AUTH_PANEL_H_
+#endif  // CHROMEOS_ASH_COMPONENTS_AUTH_PANEL_AUTH_PANEL_H_
diff --git a/ash/login/ui/factor_auth_view.h b/chromeos/ash/components/auth_panel/factor_auth_view.h
similarity index 87%
rename from ash/login/ui/factor_auth_view.h
rename to chromeos/ash/components/auth_panel/factor_auth_view.h
index a3183a0e..80af424 100644
--- a/ash/login/ui/factor_auth_view.h
+++ b/chromeos/ash/components/auth_panel/factor_auth_view.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 ASH_LOGIN_UI_FACTOR_AUTH_VIEW_H_
-#define ASH_LOGIN_UI_FACTOR_AUTH_VIEW_H_
+#ifndef CHROMEOS_ASH_COMPONENTS_AUTH_PANEL_FACTOR_AUTH_VIEW_H_
+#define CHROMEOS_ASH_COMPONENTS_AUTH_PANEL_FACTOR_AUTH_VIEW_H_
 
 #include "chromeos/ash/components/osauth/public/auth_factor_status_consumer.h"
 #include "chromeos/ash/components/osauth/public/common_types.h"
@@ -38,4 +38,4 @@
 
 }  // namespace ash
 
-#endif  //  ASH_LOGIN_UI_FACTOR_AUTH_VIEW_H_
+#endif  //  CHROMEOS_ASH_COMPONENTS_AUTH_PANEL_FACTOR_AUTH_VIEW_H_
diff --git a/chromeos/ash/components/auth_panel/factor_auth_view_factory.cc b/chromeos/ash/components/auth_panel/factor_auth_view_factory.cc
new file mode 100644
index 0000000..889ea3f
--- /dev/null
+++ b/chromeos/ash/components/auth_panel/factor_auth_view_factory.cc
@@ -0,0 +1,38 @@
+// 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 "chromeos/ash/components/auth_panel/factor_auth_view_factory.h"
+
+#include "base/functional/bind.h"
+#include "base/notreached.h"
+#include "chromeos/ash/components/osauth/public/common_types.h"
+
+namespace ash {
+
+std::unique_ptr<FactorAuthView> FactorAuthViewFactory::CreateFactorAuthView(
+    AshAuthFactor factor) {
+  switch (factor) {
+    case AshAuthFactor::kGaiaPassword:
+      return CreatePasswordView();
+    case AshAuthFactor::kCryptohomePin:
+      return nullptr;
+    case AshAuthFactor::kSmartCard:
+      return nullptr;
+    case AshAuthFactor::kSmartUnlock:
+      return nullptr;
+    case AshAuthFactor::kRecovery:
+      return nullptr;
+    case AshAuthFactor::kLegacyPin:
+      return nullptr;
+    case AshAuthFactor::kLegacyFingerprint:
+      return nullptr;
+  }
+}
+
+std::unique_ptr<FactorAuthView> FactorAuthViewFactory::CreatePasswordView() {
+  NOTIMPLEMENTED();
+  return nullptr;
+}
+
+}  // namespace ash
diff --git a/chromeos/ash/components/auth_panel/factor_auth_view_factory.h b/chromeos/ash/components/auth_panel/factor_auth_view_factory.h
new file mode 100644
index 0000000..965d43b
--- /dev/null
+++ b/chromeos/ash/components/auth_panel/factor_auth_view_factory.h
@@ -0,0 +1,44 @@
+// 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 CHROMEOS_ASH_COMPONENTS_AUTH_PANEL_FACTOR_AUTH_VIEW_FACTORY_H_
+#define CHROMEOS_ASH_COMPONENTS_AUTH_PANEL_FACTOR_AUTH_VIEW_FACTORY_H_
+
+#include "base/containers/flat_map.h"
+#include "base/functional/callback_forward.h"
+#include "base/memory/weak_ptr.h"
+#include "chromeos/ash/components/auth_panel/factor_auth_view.h"
+#include "chromeos/ash/components/osauth/public/common_types.h"
+
+namespace ash {
+
+// This class is responsible for creating the different `FactorAuthView` objects
+// that will be owned and managed by `AuthPanel`. This is done mainly to
+// facilitate dependency injection and testing and to centralize the creation
+// logic into one class, freeing up `AuthPanel` to deal exclusively with UI
+// update logic.
+class FactorAuthViewFactory {
+ public:
+  using FactorAuthViewCreator =
+      base::RepeatingCallback<std::unique_ptr<FactorAuthView>()>;
+
+  FactorAuthViewFactory();
+  FactorAuthViewFactory(const FactorAuthViewFactory&) = delete;
+  FactorAuthViewFactory(FactorAuthViewFactory&&) = delete;
+  FactorAuthViewFactory& operator=(const FactorAuthViewFactory&) = delete;
+  FactorAuthViewFactory& operator=(FactorAuthViewFactory&&) = delete;
+  ~FactorAuthViewFactory() = default;
+
+  // Create the respective `FactorAuthView` specified by the `AshAuthFactor`
+  // enum.
+  std::unique_ptr<FactorAuthView> CreateFactorAuthView(AshAuthFactor factor);
+
+ private:
+  // `FactorAuthView` factory functions:
+  std::unique_ptr<FactorAuthView> CreatePasswordView();
+};
+
+}  // namespace ash
+
+#endif  // CHROMEOS_ASH_COMPONENTS_AUTH_PANEL_FACTOR_AUTH_VIEW_FACTORY_H_
diff --git a/chromeos/ash/components/nearby/presence/credentials/nearby_presence_credential_manager_impl.cc b/chromeos/ash/components/nearby/presence/credentials/nearby_presence_credential_manager_impl.cc
index 43e18ac..66532467 100644
--- a/chromeos/ash/components/nearby/presence/credentials/nearby_presence_credential_manager_impl.cc
+++ b/chromeos/ash/components/nearby/presence/credentials/nearby_presence_credential_manager_impl.cc
@@ -19,6 +19,7 @@
 #include "chromeos/ash/components/nearby/presence/credentials/nearby_presence_server_client_impl.h"
 #include "chromeos/ash/components/nearby/presence/credentials/prefs.h"
 #include "chromeos/ash/components/nearby/presence/credentials/proto_conversions.h"
+#include "chromeos/ash/components/nearby/presence/proto/list_public_certificates_rpc.pb.h"
 #include "chromeos/ash/components/nearby/presence/proto/rpc_resources.pb.h"
 #include "chromeos/ash/components/nearby/presence/proto/update_device_rpc.pb.h"
 #include "components/prefs/pref_service.h"
@@ -104,6 +105,7 @@
   //      2. Generate this device's credentials.
   //      3. Upload this device's credentials.
   //      4. Download other devices' credentials.
+  //      5. Save other devices' credentials.
 
   // Construct a request for first time registration to let the server know
   // to return the user's name and image url.
@@ -170,11 +172,12 @@
       /*display_name=*/response.person_name(),
       /*image_url=*/response.image_url());
 
-  // We've completed the 1st of 4 steps of first time registration:
+  // We've completed the 1st of 5 steps of first time registration:
   //   -> 1. Register this device with the server.
   //      2. Generate this device's credentials.
   //      3. Upload this device's credentials.
   //      4. Download other devices' credentials.
+  //      5. Save other devices' credentials.
   // Next, kick off Step 2.
   nearby_presence_->UpdateLocalDeviceMetadataAndGenerateCredentials(
       MetadataToMojom(local_device_data_provider_->GetDeviceMetadata()),
@@ -211,11 +214,12 @@
   local_device_data_provider_->UpdatePersistedSharedCredentials(
       proto_shared_credentials);
 
-  // We've completed the 2nd of 4 steps of first time registration:
+  // We've completed the 2nd of 5 steps of first time registration:
   //      1. Register this device with the server.
   //   -> 2. Generate this device's credentials.
   //      3. Upload this device's credentials.
   //      4. Download other devices' credentials.
+  //      5. Save other devices' credentials.
   // Next, kick off Step 3.
   ScheduleUploadCredentials(proto_shared_credentials);
 }
@@ -233,14 +237,14 @@
               &NearbyPresenceCredentialManagerImpl::UploadCredentials,
               weak_ptr_factory_.GetWeakPtr(), proto_shared_credentials,
               base::BindRepeating(&NearbyPresenceCredentialManagerImpl::
-                                      OnFirstTimeCredentialUpload,
+                                      OnFirstTimeCredentialsUpload,
                                   weak_ptr_factory_.GetWeakPtr())),
           base::DefaultClock::GetInstance());
   first_time_upload_on_demand_scheduler_->Start();
   first_time_upload_on_demand_scheduler_->MakeImmediateRequest();
 }
 
-void NearbyPresenceCredentialManagerImpl::OnFirstTimeCredentialUpload(
+void NearbyPresenceCredentialManagerImpl::OnFirstTimeCredentialsUpload(
     bool success) {
   CHECK(first_time_upload_on_demand_scheduler_);
   if (!success) {
@@ -265,17 +269,74 @@
   first_time_upload_on_demand_scheduler_->HandleResult(/*success=*/true);
   first_time_upload_on_demand_scheduler_.reset();
 
-  // TODO(b/276307539): Currently first time registration is considered
-  // successful on the successful upload of generated credentials, however this
-  // is not fully complete. Next, the CredentialManager needs to download the
-  // credentials before executing the success callback.
-  //
-  // We've completed the 3rd of 4 steps of first time registration:
+  // We've completed the 3rd of 5 steps of first time registration:
   //      1. Register this device with the server.
   //      2. Generate this device's credentials.
   //   -> 3. Upload this device's credentials.
   //      4. Download other devices' credentials.
+  //      5. Save other devices' credentials.
   // Next, kick off Step 4.
+  ScheduleDownloadCredentials();
+}
+
+void NearbyPresenceCredentialManagerImpl::ScheduleDownloadCredentials() {
+  // Next, to complete first time registration, the CredentialManager needs to
+  // download the remote devices' shared credentials and save them to the
+  // Nearby library.
+  first_time_download_on_demand_scheduler_ =
+      ash::nearby::NearbySchedulerFactory::CreateOnDemandScheduler(
+          /*retry_failures=*/true,
+          /*require_connectivity=*/true,
+          prefs::kNearbyPresenceSchedulingFirstTimeDownloadPrefName,
+          pref_service_,
+          base::BindRepeating(
+              &NearbyPresenceCredentialManagerImpl::DownloadCredentials,
+              weak_ptr_factory_.GetWeakPtr(),
+              base::BindRepeating(&NearbyPresenceCredentialManagerImpl::
+                                      OnFirstTimeCredentialsDownload,
+                                  weak_ptr_factory_.GetWeakPtr())),
+          base::DefaultClock::GetInstance());
+  first_time_download_on_demand_scheduler_->Start();
+  first_time_download_on_demand_scheduler_->MakeImmediateRequest();
+}
+
+void NearbyPresenceCredentialManagerImpl::OnFirstTimeCredentialsDownload(
+    std::vector<::nearby::internal::SharedCredential> credentials,
+    bool success) {
+  CHECK(first_time_download_on_demand_scheduler_);
+  if (!success) {
+    // Allow the scheduler to exponentially attempt uploading credentials
+    // until the max. Once it reaches the max attempts, notify consumers of
+    // failure.
+    if (first_time_download_on_demand_scheduler_->GetNumConsecutiveFailures() <
+        kServerCommunicationMaxAttempts) {
+      first_time_download_on_demand_scheduler_->HandleResult(
+          /*success=*/false);
+      return;
+    }
+
+    // We've exceeded the max attempts; registration has failed.
+    first_time_download_on_demand_scheduler_->Stop();
+    first_time_download_on_demand_scheduler_.reset();
+    CHECK(on_registered_callback_);
+    std::move(on_registered_callback_).Run(/*success=*/false);
+    return;
+  }
+
+  first_time_download_on_demand_scheduler_->HandleResult(/*success=*/true);
+  first_time_download_on_demand_scheduler_.reset();
+
+  // TODO(b/276307539): Currently first time registration is considered
+  // successful on the successful download of remote credentials, however this
+  // is not fully complete. Next, the CredentialManager needs to save the
+  // download credentials to the NP library over the mojo pipe.
+  //
+  // We've completed the 4th of 5 steps for first time registration.
+  //      1. Register this device with the server.
+  //      2. Generate this device's credentials.
+  //      3. Upload this device's credentials.
+  //   -> 4. Download other devices' credentials.
+  //      5. Save other devices' credentials.
   CHECK(on_registered_callback_);
   std::move(on_registered_callback_).Run(/*success=*/true);
 }
@@ -304,6 +365,7 @@
 
   // Construct a HTTP client for the request. The HTTP client lifetime is
   // tied to a single request.
+  CHECK(!server_client_);
   server_client_ = NearbyPresenceServerClientImpl::Factory::Create(
       std::make_unique<ash::nearby::NearbyApiCallFlowImpl>(), identity_manager_,
       url_loader_factory_);
@@ -356,4 +418,87 @@
                                 /*success=*/false);
 }
 
+void NearbyPresenceCredentialManagerImpl::DownloadCredentials(
+    base::RepeatingCallback<
+        void(std::vector<::nearby::internal::SharedCredential>, bool)>
+        download_credentials_result_callback) {
+  ash::nearby::proto::ListPublicCertificatesRequest request;
+  request.set_parent(kDeviceIdPrefix +
+                     local_device_data_provider_->GetDeviceId());
+
+  server_response_timer_.Start(
+      FROM_HERE, kServerResponseTimeout,
+      base::BindOnce(
+          &NearbyPresenceCredentialManagerImpl::OnDownloadCredentialsTimeout,
+          weak_ptr_factory_.GetWeakPtr(),
+          download_credentials_result_callback));
+
+  // Construct a HTTP client for the request. The HTTP client lifetime is
+  // tied to a single request.
+  CHECK(!server_client_);
+  server_client_ = NearbyPresenceServerClientImpl::Factory::Create(
+      std::make_unique<ash::nearby::NearbyApiCallFlowImpl>(), identity_manager_,
+      url_loader_factory_);
+  server_client_->ListPublicCertificates(
+      request,
+      base::BindOnce(
+          &NearbyPresenceCredentialManagerImpl::OnDownloadCredentialsSuccess,
+          weak_ptr_factory_.GetWeakPtr(), download_credentials_result_callback),
+      base::BindOnce(
+          &NearbyPresenceCredentialManagerImpl::OnDownloadCredentialsFailure,
+          weak_ptr_factory_.GetWeakPtr(),
+          download_credentials_result_callback));
+}
+
+void NearbyPresenceCredentialManagerImpl::HandleDownloadCredentialsResult(
+    base::RepeatingCallback<
+        void(std::vector<::nearby::internal::SharedCredential>, bool)>
+        download_credentials_result_callback,
+    bool success,
+    std::vector<::nearby::internal::SharedCredential> credentials) {
+  // TODO(b/276307539): Add metrics to record failures.
+  server_client_.reset();
+  CHECK(download_credentials_result_callback);
+  download_credentials_result_callback.Run(/*remote_credentials=*/credentials,
+                                           /*success=*/success);
+}
+
+void NearbyPresenceCredentialManagerImpl::OnDownloadCredentialsTimeout(
+    base::RepeatingCallback<
+        void(std::vector<::nearby::internal::SharedCredential>, bool)>
+        download_credentials_result_callback) {
+  // TODO(b/276307539): Add metrics to record timeout.
+  HandleDownloadCredentialsResult(download_credentials_result_callback,
+                                  /*success=*/false, /*credentials=*/{});
+}
+
+void NearbyPresenceCredentialManagerImpl::OnDownloadCredentialsSuccess(
+    base::RepeatingCallback<
+        void(std::vector<::nearby::internal::SharedCredential>, bool)>
+        download_credentials_result_callback,
+    const ash::nearby::proto::ListPublicCertificatesResponse& response) {
+  server_response_timer_.Stop();
+
+  std::vector<::nearby::internal::SharedCredential> remote_credentials;
+  for (auto public_certificate : response.public_certificates()) {
+    remote_credentials.push_back(
+        PublicCertificateToSharedCredential(public_certificate));
+  }
+
+  HandleDownloadCredentialsResult(download_credentials_result_callback,
+                                  /*success=*/true,
+                                  /*credentials=*/remote_credentials);
+}
+
+void NearbyPresenceCredentialManagerImpl::OnDownloadCredentialsFailure(
+    base::RepeatingCallback<
+        void(std::vector<::nearby::internal::SharedCredential>, bool)>
+        download_credentials_result_callback,
+    ash::nearby::NearbyHttpError error) {
+  // TODO(b/276307539): Add metrics to record the type of NearbyHttpError.
+  server_response_timer_.Stop();
+  HandleDownloadCredentialsResult(download_credentials_result_callback,
+                                  /*success=*/false, /*credentials=*/{});
+}
+
 }  // namespace ash::nearby::presence
diff --git a/chromeos/ash/components/nearby/presence/credentials/nearby_presence_credential_manager_impl.h b/chromeos/ash/components/nearby/presence/credentials/nearby_presence_credential_manager_impl.h
index 41a6e0d9..afe5f142 100644
--- a/chromeos/ash/components/nearby/presence/credentials/nearby_presence_credential_manager_impl.h
+++ b/chromeos/ash/components/nearby/presence/credentials/nearby_presence_credential_manager_impl.h
@@ -29,6 +29,7 @@
 
 namespace ash::nearby::proto {
 class UpdateDeviceResponse;
+class ListPublicCertificatesResponse;
 }  // namespace ash::nearby::proto
 
 namespace nearby::internal {
@@ -92,21 +93,27 @@
       std::vector<mojom::SharedCredentialPtr> shared_credentials,
       mojom::StatusCode status);
 
-  // Callback for first time credential upload/download during first time
+  // Callbacks for first time credential upload/download during first time
   // server registration.
-  void OnFirstTimeCredentialUpload(bool success);
   void ScheduleUploadCredentials(
       std::vector<::nearby::internal::SharedCredential>
           proto_shared_credentials);
+  void OnFirstTimeCredentialsUpload(bool success);
+  void ScheduleDownloadCredentials();
+  void OnFirstTimeCredentialsDownload(
+      std::vector<::nearby::internal::SharedCredential> credentials,
+      bool success);
 
-  // Helper functions to trigger uploading credentials in the NP server. The
-  // helper functions are used for first time server registration to upload
-  // newly generated credentials, daily syncs with the server to upload
-  // credentials if they have changed. These helper functions are also used in
-  // `UpdateCredentials` flows.
+  // Helper functions to trigger uploading and downloading credentials in the NP
+  // server. The helper functions are used for first time server registration to
+  // upload newly generated credentials and download remote devices'
+  // credentials, as well as daily syncs with the server to upload
+  // credentials if they have changed and download remote devices' credentials.
+  // These helper functions are also used in `UpdateCredentials` flows.
   //
-  // They take a repeating callback because UploadCredentials must be bound as a
-  // RepeatingCallback itself as a task in a NearbyScheduler.
+  // They take a repeating callback because `UploadCredentials()` and
+  // `DownloadCredentials()` must be bound as a RepeatingCallback itself as a
+  // task in a NearbyScheduler.
   void UploadCredentials(
       std::vector<::nearby::internal::SharedCredential> credentials,
       base::RepeatingCallback<void(bool)> upload_credentials_result_callback);
@@ -121,6 +128,30 @@
   void OnUploadCredentialsFailure(
       base::RepeatingCallback<void(bool)> upload_credentials_callback,
       ash::nearby::NearbyHttpError error);
+  void DownloadCredentials(
+      base::RepeatingCallback<
+          void(std::vector<::nearby::internal::SharedCredential>, bool)>
+          download_credentials_result_callback);
+  void HandleDownloadCredentialsResult(
+      base::RepeatingCallback<
+          void(std::vector<::nearby::internal::SharedCredential>, bool)>
+          download_credentials_result_callback,
+      bool success,
+      std::vector<::nearby::internal::SharedCredential> credentials);
+  void OnDownloadCredentialsTimeout(
+      base::RepeatingCallback<
+          void(std::vector<::nearby::internal::SharedCredential>, bool)>
+          download_credentials_result_callback);
+  void OnDownloadCredentialsSuccess(
+      base::RepeatingCallback<
+          void(std::vector<::nearby::internal::SharedCredential>, bool)>
+          download_credentials_result_callback,
+      const ash::nearby::proto::ListPublicCertificatesResponse& response);
+  void OnDownloadCredentialsFailure(
+      base::RepeatingCallback<
+          void(std::vector<::nearby::internal::SharedCredential>, bool)>
+          download_credentials_result_callback,
+      ash::nearby::NearbyHttpError error);
 
   // Constructed per RPC request, and destroyed on RPC response (server
   // interaction completed). This field is reused by multiple RPCs during the
@@ -144,6 +175,8 @@
       first_time_registration_on_demand_scheduler_;
   std::unique_ptr<ash::nearby::NearbyScheduler>
       first_time_upload_on_demand_scheduler_;
+  std::unique_ptr<ash::nearby::NearbyScheduler>
+      first_time_download_on_demand_scheduler_;
 
   // Callback to return the result of the first time registration. Not
   // guaranteed to be a valid callback, as this is set only during first time
diff --git a/chromeos/ash/components/nearby/presence/credentials/nearby_presence_credential_manager_impl_unittest.cc b/chromeos/ash/components/nearby/presence/credentials/nearby_presence_credential_manager_impl_unittest.cc
index df6bbb5d..9af1ad5 100644
--- a/chromeos/ash/components/nearby/presence/credentials/nearby_presence_credential_manager_impl_unittest.cc
+++ b/chromeos/ash/components/nearby/presence/credentials/nearby_presence_credential_manager_impl_unittest.cc
@@ -123,6 +123,10 @@
     // Simulate the device id which will be generated in a call to
     // |GetDeviceId|.
     fake_local_device_data_provider_->SetDeviceId(kDeviceId);
+
+    // Simulate the credentials being generated in the NP library.
+    fake_nearby_presence_.SetGenerateCredentialsResponse(
+        BuildSharedCredentials(), mojom::StatusCode::kOk);
   }
 
   void TearDown() override {
@@ -166,6 +170,23 @@
         ->InvokeUpdateDeviceSuccessCallback(update_credentials_response);
   }
 
+  void TriggerFirstTimeDownloadRemoteCredentialSuccess() {
+    // Simulate the scheduler notifying the CredentialManager that the download
+    // task is ready when it has network connectivity.
+    const raw_ptr<FakeNearbyScheduler, ExperimentalAsh>
+        first_time_download_scheduler =
+            scheduler_factory_.pref_name_to_on_demand_instance()
+                .find(prefs::kNearbyPresenceSchedulingFirstTimeDownloadPrefName)
+                ->second.fake_scheduler;
+    first_time_download_scheduler->InvokeRequestCallback();
+
+    // Next, mock and return the server response for fetching remote device
+    // public certificates.
+    ash::nearby::proto::ListPublicCertificatesResponse certificate_response;
+    server_client_factory_.fake_server_client()
+        ->InvokeListPublicCertificatesSuccessCallback({certificate_response});
+  }
+
  protected:
   base::test::TaskEnvironment task_environment_{
       base::test::TaskEnvironment::TimeSource::MOCK_TIME};
@@ -181,10 +202,6 @@
 };
 
 TEST_F(NearbyPresenceCredentialManagerImplTest, RegistrationSuccess) {
-  // Simulate the credentials being generated in the NP library.
-  fake_nearby_presence_.SetGenerateCredentialsResponse(BuildSharedCredentials(),
-                                                       mojom::StatusCode::kOk);
-
   // Wait until after the generated credentials are saved to continue the test.
   base::RunLoop run_loop;
   fake_local_device_data_provider_->SetUpdatePersistedSharedCredentialsCallback(
@@ -203,6 +220,8 @@
 
   TriggerFirstTimeLocalCredentialUploadSuccess();
 
+  TriggerFirstTimeDownloadRemoteCredentialSuccess();
+
   EXPECT_TRUE(credential_manager_->IsLocalDeviceRegistered());
 }
 
@@ -276,10 +295,6 @@
 
 TEST_F(NearbyPresenceCredentialManagerImplTest,
        UploadCredentialsServerTimeout) {
-  // Simulate the credentials being generated in the NP library.
-  fake_nearby_presence_.SetGenerateCredentialsResponse(BuildSharedCredentials(),
-                                                       mojom::StatusCode::kOk);
-
   // Wait until after the generated credentials are saved to continue the test.
   base::RunLoop run_loop;
   fake_local_device_data_provider_->SetUpdatePersistedSharedCredentialsCallback(
@@ -316,10 +331,6 @@
 }
 
 TEST_F(NearbyPresenceCredentialManagerImplTest, UploadCredentialsFailure) {
-  // Simulate the credentials being generated in the NP library.
-  fake_nearby_presence_.SetGenerateCredentialsResponse(BuildSharedCredentials(),
-                                                       mojom::StatusCode::kOk);
-
   // Wait until after the generated credentials are saved to continue the test.
   base::RunLoop run_loop;
   fake_local_device_data_provider_->SetUpdatePersistedSharedCredentialsCallback(
@@ -356,4 +367,79 @@
   EXPECT_TRUE(credential_manager_->IsLocalDeviceRegistered());
 }
 
+TEST_F(NearbyPresenceCredentialManagerImplTest, DownloadCredentialsFailure) {
+  // Wait until after the generated credentials are saved to continue the test.
+  base::RunLoop run_loop;
+  fake_local_device_data_provider_->SetUpdatePersistedSharedCredentialsCallback(
+      run_loop.QuitClosure());
+
+  // Expect success on the callback.
+  base::MockCallback<base::OnceCallback<void(bool)>>
+      on_registered_mock_callback;
+  EXPECT_CALL(on_registered_mock_callback, Run(false)).Times(1);
+  credential_manager_->RegisterPresence(on_registered_mock_callback.Get());
+
+  TriggerFirstTimeRegistrationSuccess();
+
+  // Required for credentials to be generated and passed over the mojo pipe.
+  run_loop.Run();
+
+  TriggerFirstTimeLocalCredentialUploadSuccess();
+
+  // Simulate the scheduler notifying the CredentialManager that the download
+  // task is ready when it has network connectivity.
+  const raw_ptr<FakeNearbyScheduler, ExperimentalAsh>
+      first_time_download_scheduler =
+          scheduler_factory_.pref_name_to_on_demand_instance()
+              .find(prefs::kNearbyPresenceSchedulingFirstTimeDownloadPrefName)
+              ->second.fake_scheduler;
+
+  // Simulate the max number of failures caused by a RPC failure.
+  first_time_download_scheduler->SetNumConsecutiveFailures(
+      kServerCommunicationMaxAttempts);
+  first_time_download_scheduler->InvokeRequestCallback();
+  ash::nearby::proto::ListPublicCertificatesResponse certificate_response;
+  server_client_factory_.fake_server_client()
+      ->InvokeListPublicCertificatesErrorCallback(
+          ash::nearby::NearbyHttpError::kInternalServerError);
+
+  EXPECT_TRUE(credential_manager_->IsLocalDeviceRegistered());
+}
+
+TEST_F(NearbyPresenceCredentialManagerImplTest, DownloadCredentialsTimeout) {
+  // Wait until after the generated credentials are saved to continue the test.
+  base::RunLoop run_loop;
+  fake_local_device_data_provider_->SetUpdatePersistedSharedCredentialsCallback(
+      run_loop.QuitClosure());
+
+  // Expect success on the callback.
+  base::MockCallback<base::OnceCallback<void(bool)>>
+      on_registered_mock_callback;
+  EXPECT_CALL(on_registered_mock_callback, Run(false)).Times(1);
+  credential_manager_->RegisterPresence(on_registered_mock_callback.Get());
+
+  TriggerFirstTimeRegistrationSuccess();
+
+  // Required for credentials to be generated and passed over the mojo pipe.
+  run_loop.Run();
+
+  TriggerFirstTimeLocalCredentialUploadSuccess();
+
+  // Simulate the scheduler notifying the CredentialManager that the download
+  // task is ready when it has network connectivity.
+  const raw_ptr<FakeNearbyScheduler, ExperimentalAsh>
+      first_time_download_scheduler =
+          scheduler_factory_.pref_name_to_on_demand_instance()
+              .find(prefs::kNearbyPresenceSchedulingFirstTimeDownloadPrefName)
+              ->second.fake_scheduler;
+  first_time_download_scheduler->InvokeRequestCallback();
+
+  // Simulate the max number of failures caused by a server response timeout.
+  first_time_download_scheduler->SetNumConsecutiveFailures(
+      kServerCommunicationMaxAttempts);
+  task_environment_.FastForwardBy(kServerResponseTimeout);
+
+  EXPECT_TRUE(credential_manager_->IsLocalDeviceRegistered());
+}
+
 }  // namespace ash::nearby::presence
diff --git a/chromeos/ash/components/nearby/presence/credentials/prefs.cc b/chromeos/ash/components/nearby/presence/credentials/prefs.cc
index 693faa4..4a6da04 100644
--- a/chromeos/ash/components/nearby/presence/credentials/prefs.cc
+++ b/chromeos/ash/components/nearby/presence/credentials/prefs.cc
@@ -21,6 +21,8 @@
     "nearby_presence.scheduling.first_time_registration";
 const char kNearbyPresenceSchedulingFirstTimeUploadPrefName[] =
     "nearby_presence.scheduling.first_time_upload";
+const char kNearbyPresenceSchedulingFirstTimeDownloadPrefName[] =
+    "nearby_presence.scheduling.first_time_download";
 
 }  // namespace prefs
 
@@ -38,6 +40,8 @@
       prefs::kNearbyPresenceSchedulingFirstTimeRegistrationPrefName);
   registry->RegisterDictionaryPref(
       prefs::kNearbyPresenceSchedulingFirstTimeUploadPrefName);
+  registry->RegisterDictionaryPref(
+      prefs::kNearbyPresenceSchedulingFirstTimeDownloadPrefName);
 }
 
 }  // namespace ash::nearby::presence
diff --git a/chromeos/ash/components/nearby/presence/credentials/prefs.h b/chromeos/ash/components/nearby/presence/credentials/prefs.h
index 77be4d5..8076028 100644
--- a/chromeos/ash/components/nearby/presence/credentials/prefs.h
+++ b/chromeos/ash/components/nearby/presence/credentials/prefs.h
@@ -17,6 +17,7 @@
 extern const char kNearbyPresenceSharedCredentialIdListPrefName[];
 extern const char kNearbyPresenceSchedulingFirstTimeRegistrationPrefName[];
 extern const char kNearbyPresenceSchedulingFirstTimeUploadPrefName[];
+extern const char kNearbyPresenceSchedulingFirstTimeDownloadPrefName[];
 
 }  // namespace prefs
 
diff --git a/chromeos/ash/components/nearby/presence/credentials/proto_conversions.cc b/chromeos/ash/components/nearby/presence/credentials/proto_conversions.cc
index c55ecd5..88f7d9f 100644
--- a/chromeos/ash/components/nearby/presence/credentials/proto_conversions.cc
+++ b/chromeos/ash/components/nearby/presence/credentials/proto_conversions.cc
@@ -141,4 +141,42 @@
   return milliseconds / 1000.0;
 }
 
+::nearby::internal::SharedCredential PublicCertificateToSharedCredential(
+    ash::nearby::proto::PublicCertificate certificate) {
+  ::nearby::internal::SharedCredential shared_credential;
+  shared_credential.set_secret_id(certificate.secret_id());
+  shared_credential.set_key_seed(certificate.secret_key());
+  shared_credential.set_connection_signature_verification_key(
+      certificate.public_key());
+  shared_credential.set_start_time_millis(
+      SecondsToMilliseconds(certificate.start_time().seconds()));
+  shared_credential.set_end_time_millis(
+      SecondsToMilliseconds(certificate.end_time().seconds()));
+  shared_credential.set_encrypted_metadata_bytes_v0(
+      certificate.encrypted_metadata_bytes());
+  shared_credential.set_metadata_encryption_key_unsigned_adv_tag(
+      certificate.metadata_encryption_key_tag());
+  shared_credential.set_identity_type(
+      TrustTypeToIdentityType(certificate.trust_type()));
+  return shared_credential;
+}
+
+::nearby::internal::IdentityType TrustTypeToIdentityType(
+    ash::nearby::proto::TrustType trust_type) {
+  switch (trust_type) {
+    case ash::nearby::proto::TrustType::TRUST_TYPE_UNSPECIFIED:
+      return ::nearby::internal::IdentityType::IDENTITY_TYPE_UNSPECIFIED;
+    case ash::nearby::proto::TrustType::TRUST_TYPE_PRIVATE:
+      return ::nearby::internal::IdentityType::IDENTITY_TYPE_PRIVATE;
+    case ash::nearby::proto::TrustType::TRUST_TYPE_TRUSTED:
+      return ::nearby::internal::IdentityType::IDENTITY_TYPE_TRUSTED;
+    default:
+      return ::nearby::internal::IdentityType::IDENTITY_TYPE_UNSPECIFIED;
+  }
+}
+
+int64_t SecondsToMilliseconds(int64_t seconds) {
+  return seconds * 1000.0;
+}
+
 }  // namespace ash::nearby::presence
diff --git a/chromeos/ash/components/nearby/presence/credentials/proto_conversions.h b/chromeos/ash/components/nearby/presence/credentials/proto_conversions.h
index 9c52c8d3..01ce926 100644
--- a/chromeos/ash/components/nearby/presence/credentials/proto_conversions.h
+++ b/chromeos/ash/components/nearby/presence/credentials/proto_conversions.h
@@ -37,6 +37,12 @@
     ::nearby::internal::IdentityType identity_type);
 int64_t MillisecondsToSeconds(int64_t milliseconds);
 
+::nearby::internal::SharedCredential PublicCertificateToSharedCredential(
+    ash::nearby::proto::PublicCertificate certificate);
+::nearby::internal::IdentityType TrustTypeToIdentityType(
+    ash::nearby::proto::TrustType trust_type);
+int64_t SecondsToMilliseconds(int64_t seconds);
+
 }  // namespace ash::nearby::presence
 
 #endif  // CHROMEOS_ASH_COMPONENTS_NEARBY_PRESENCE_CREDENTIALS_PROTO_CONVERSIONS_H_
diff --git a/chromeos/ash/components/nearby/presence/credentials/proto_conversions_unittest.cc b/chromeos/ash/components/nearby/presence/credentials/proto_conversions_unittest.cc
index fed19d8..9c9f0f8 100644
--- a/chromeos/ash/components/nearby/presence/credentials/proto_conversions_unittest.cc
+++ b/chromeos/ash/components/nearby/presence/credentials/proto_conversions_unittest.cc
@@ -21,14 +21,22 @@
                                                       0x33, 0x33, 0x33};
 const std::vector<uint8_t> kMetadataEncryptionTag = {0x44, 0x44, 0x44,
                                                      0x44, 0x44, 0x44};
-constexpr int64_t kStartTimeMillis = 255486129307;
-constexpr int64_t kEndTimeMillis = 64301728896;
 const std::vector<uint8_t> kConnectionSignatureVerificationKey = {
     0x55, 0x55, 0x55, 0x55, 0x55, 0x55};
 const std::vector<uint8_t> kAdvertisementSignatureVerificationKey = {
     0x66, 0x66, 0x66, 0x66, 0x66, 0x66};
 const std::vector<uint8_t> kVersion = {0x77, 0x77, 0x77, 0x77, 0x77, 0x77};
 
+// The start and end time values are converted from milliseconds in the NP
+// library to seconds to be stored in the NP server. When the credentials are
+// downloaded, the start and end times are converted from seconds to
+// milliseconds, and because these values are stored as ints, they are
+// expected to lose preciseness.
+constexpr int64_t kStartTimeMillis_BeforeConversion = 255486129307;
+constexpr int64_t kEndTimeMillis_BeforeConversion = 64301728896;
+constexpr int64_t kStartTimeMillis_AfterConversion = 255486129000;
+constexpr int64_t kEndTimeMillis_AfterConversion = 64301728000;
+
 }  // namespace
 
 namespace ash::nearby::presence {
@@ -82,9 +90,9 @@
 
 TEST_F(ProtoConversionsTest, SharedCredentialFromMojom) {
   mojom::SharedCredentialPtr mojo_cred = mojom::SharedCredential::New(
-      kSecretId, kKeySeed, kStartTimeMillis, kEndTimeMillis,
-      kEncryptedMetadataBytes, kMetadataEncryptionTag,
-      kConnectionSignatureVerificationKey,
+      kSecretId, kKeySeed, kStartTimeMillis_BeforeConversion,
+      kEndTimeMillis_BeforeConversion, kEncryptedMetadataBytes,
+      kMetadataEncryptionTag, kConnectionSignatureVerificationKey,
       kAdvertisementSignatureVerificationKey,
       mojom::IdentityType::kIdentityTypePrivate, kVersion);
   ::nearby::internal::SharedCredential proto_cred =
@@ -93,8 +101,8 @@
             proto_cred.secret_id());
   EXPECT_EQ(std::string(kKeySeed.begin(), kKeySeed.end()),
             proto_cred.key_seed());
-  EXPECT_EQ(kStartTimeMillis, proto_cred.start_time_millis());
-  EXPECT_EQ(kEndTimeMillis, proto_cred.end_time_millis());
+  EXPECT_EQ(kStartTimeMillis_BeforeConversion, proto_cred.start_time_millis());
+  EXPECT_EQ(kEndTimeMillis_BeforeConversion, proto_cred.end_time_millis());
   EXPECT_EQ(std::string(kEncryptedMetadataBytes.begin(),
                         kEncryptedMetadataBytes.end()),
             proto_cred.encrypted_metadata_bytes_v0());
@@ -118,8 +126,8 @@
   shared_credential.set_secret_id(
       std::string(kSecretId.begin(), kSecretId.end()));
   shared_credential.set_key_seed(std::string(kKeySeed.begin(), kKeySeed.end()));
-  shared_credential.set_start_time_millis(kStartTimeMillis);
-  shared_credential.set_end_time_millis(kEndTimeMillis);
+  shared_credential.set_start_time_millis(kStartTimeMillis_BeforeConversion);
+  shared_credential.set_end_time_millis(kEndTimeMillis_BeforeConversion);
   shared_credential.set_encrypted_metadata_bytes_v0(std::string(
       kEncryptedMetadataBytes.begin(), kEncryptedMetadataBytes.end()));
   shared_credential.set_metadata_encryption_key_unsigned_adv_tag(std::string(
@@ -142,9 +150,9 @@
   EXPECT_EQ(std::string(kConnectionSignatureVerificationKey.begin(),
                         kConnectionSignatureVerificationKey.end()),
             proto_cert.public_key());
-  EXPECT_EQ(MillisecondsToSeconds(kStartTimeMillis),
+  EXPECT_EQ(MillisecondsToSeconds(kStartTimeMillis_BeforeConversion),
             proto_cert.start_time().seconds());
-  EXPECT_EQ(MillisecondsToSeconds(kEndTimeMillis),
+  EXPECT_EQ(MillisecondsToSeconds(kEndTimeMillis_BeforeConversion),
             proto_cert.end_time().seconds());
   EXPECT_EQ(std::string(kEncryptedMetadataBytes.begin(),
                         kEncryptedMetadataBytes.end()),
@@ -156,4 +164,42 @@
             proto_cert.trust_type());
 }
 
+TEST_F(ProtoConversionsTest, PublicCertificateToSharedCredential) {
+  ash::nearby::proto::PublicCertificate certificate;
+  certificate.set_secret_id(std::string(kSecretId.begin(), kSecretId.end()));
+  certificate.set_secret_key(std::string(kKeySeed.begin(), kKeySeed.end()));
+  certificate.set_public_key(
+      std::string(kConnectionSignatureVerificationKey.begin(),
+                  kConnectionSignatureVerificationKey.end()));
+  certificate.mutable_start_time()->set_seconds(
+      MillisecondsToSeconds(kStartTimeMillis_BeforeConversion));
+  certificate.mutable_end_time()->set_seconds(
+      MillisecondsToSeconds(kEndTimeMillis_BeforeConversion));
+  certificate.set_encrypted_metadata_bytes(std::string(
+      kEncryptedMetadataBytes.begin(), kEncryptedMetadataBytes.end()));
+  certificate.set_metadata_encryption_key_tag(std::string(
+      kMetadataEncryptionTag.begin(), kMetadataEncryptionTag.end()));
+  certificate.set_trust_type(ash::nearby::proto::TrustType::TRUST_TYPE_PRIVATE);
+
+  ::nearby::internal::SharedCredential proto_cred =
+      PublicCertificateToSharedCredential(certificate);
+  EXPECT_EQ(std::string(kSecretId.begin(), kSecretId.end()),
+            proto_cred.secret_id());
+  EXPECT_EQ(std::string(kKeySeed.begin(), kKeySeed.end()),
+            proto_cred.key_seed());
+  EXPECT_EQ(std::string(kConnectionSignatureVerificationKey.begin(),
+                        kConnectionSignatureVerificationKey.end()),
+            proto_cred.connection_signature_verification_key());
+  EXPECT_EQ(kStartTimeMillis_AfterConversion, proto_cred.start_time_millis());
+  EXPECT_EQ(kEndTimeMillis_AfterConversion, proto_cred.end_time_millis());
+  EXPECT_EQ(std::string(kEncryptedMetadataBytes.begin(),
+                        kEncryptedMetadataBytes.end()),
+            proto_cred.encrypted_metadata_bytes_v0());
+  EXPECT_EQ(
+      std::string(kMetadataEncryptionTag.begin(), kMetadataEncryptionTag.end()),
+      proto_cred.metadata_encryption_key_unsigned_adv_tag());
+  EXPECT_EQ(::nearby::internal::IdentityType::IDENTITY_TYPE_PRIVATE,
+            proto_cred.identity_type());
+}
+
 }  // namespace ash::nearby::presence
diff --git a/chromeos/ash/components/proximity_auth/smart_lock_metrics_recorder.cc b/chromeos/ash/components/proximity_auth/smart_lock_metrics_recorder.cc
index c58b9e2..4b4dd68d7 100644
--- a/chromeos/ash/components/proximity_auth/smart_lock_metrics_recorder.cc
+++ b/chromeos/ash/components/proximity_auth/smart_lock_metrics_recorder.cc
@@ -11,6 +11,13 @@
 SmartLockMetricsRecorder::~SmartLockMetricsRecorder() {}
 
 // static
+void SmartLockMetricsRecorder::RecordSmartLockUnlockAuthMethodChoice(
+    SmartLockAuthMethodChoice auth_method_choice) {
+  UMA_HISTOGRAM_ENUMERATION("SmartLock.AuthMethodChoice.Unlock",
+                            auth_method_choice);
+}
+
+// static
 void SmartLockMetricsRecorder::RecordAuthResultUnlockSuccess(bool success) {
   RecordAuthResultSuccess(success);
   UMA_HISTOGRAM_BOOLEAN("SmartLock.AuthResult.Unlock", success);
diff --git a/chromeos/ash/components/proximity_auth/smart_lock_metrics_recorder.h b/chromeos/ash/components/proximity_auth/smart_lock_metrics_recorder.h
index d34b87b..a9f2460c 100644
--- a/chromeos/ash/components/proximity_auth/smart_lock_metrics_recorder.h
+++ b/chromeos/ash/components/proximity_auth/smart_lock_metrics_recorder.h
@@ -37,6 +37,16 @@
   // //tools/metrics/histograms/enums.xml, and should always reflect it (do not
   // change one without changing the other). Entries should be never modified
   // or deleted. Only additions possible.
+  enum class SmartLockAuthMethodChoice {
+    kSmartLock = 0,
+    kOther = 1,
+    kMaxValue = kOther
+  };
+
+  // This enum is tied directly to a UMA enum defined in
+  // //tools/metrics/histograms/enums.xml, and should always reflect it (do not
+  // change one without changing the other). Entries should be never modified
+  // or deleted. Only additions possible.
   enum class SmartLockAuthEventPasswordState {
     kUnknownState = 0,
     // kNoPairing = 1, (obsolete)
@@ -61,6 +71,9 @@
     kMaxValue = kPrimaryUserAbsent
   };
 
+  static void RecordSmartLockUnlockAuthMethodChoice(
+      SmartLockAuthMethodChoice auth_method_choice);
+
   static void RecordAuthResultUnlockSuccess(bool success = true);
   static void RecordAuthResultUnlockFailure(
       SmartLockAuthResultFailureReason failure_reason);
diff --git a/chromeos/ui/frame/BUILD.gn b/chromeos/ui/frame/BUILD.gn
index a47b953..1461eb5 100644
--- a/chromeos/ui/frame/BUILD.gn
+++ b/chromeos/ui/frame/BUILD.gn
@@ -101,6 +101,8 @@
   sources = [
     "immersive/immersive_fullscreen_controller_test_api.cc",
     "immersive/immersive_fullscreen_controller_test_api.h",
+    "multitask_menu/multitask_menu_view_test_api.cc",
+    "multitask_menu/multitask_menu_view_test_api.h",
   ]
 
   deps = [
diff --git a/chromeos/ui/frame/caption_buttons/frame_caption_button_container_view.cc b/chromeos/ui/frame/caption_buttons/frame_caption_button_container_view.cc
index b92a221..3ec6ef7 100644
--- a/chromeos/ui/frame/caption_buttons/frame_caption_button_container_view.cc
+++ b/chromeos/ui/frame/caption_buttons/frame_caption_button_container_view.cc
@@ -531,7 +531,7 @@
   }
 
   // Tablet nudge is controlled by ash by another class
-  // (`::ash::TabletModeMultitaskCue`).
+  // (`::ash::TabletModeMultitaskCueController`).
   if (TabletState::Get()->InTabletMode()) {
     return;
   }
diff --git a/chromeos/ui/frame/multitask_menu/multitask_menu_nudge_controller.cc b/chromeos/ui/frame/multitask_menu/multitask_menu_nudge_controller.cc
index dc81dca..90610651 100644
--- a/chromeos/ui/frame/multitask_menu/multitask_menu_nudge_controller.cc
+++ b/chromeos/ui/frame/multitask_menu/multitask_menu_nudge_controller.cc
@@ -298,7 +298,7 @@
       DismissNudge();
       break;
     case display::TabletState::kInTabletMode:
-      // Entering tablet mode will call the `TabletModeMultitaskCue`
+      // Entering tablet mode will call the `TabletModeMultitaskCueController`
       // constructor so no work needed.
       // TODO(b/267648014): Combine cue and nudge logic so both are activated in
       // the same place when switching modes.
diff --git a/chromeos/ui/frame/multitask_menu/multitask_menu_nudge_controller.h b/chromeos/ui/frame/multitask_menu/multitask_menu_nudge_controller.h
index f0b066a..bfb1216 100644
--- a/chromeos/ui/frame/multitask_menu/multitask_menu_nudge_controller.h
+++ b/chromeos/ui/frame/multitask_menu/multitask_menu_nudge_controller.h
@@ -135,7 +135,8 @@
   void PerformPulseAnimation(int pulse_count);
 
   // Dismisses the clamshell nudge at the end of the timer if it is still
-  // visible. Tablet nudge is handled by the `TabletModeMultitaskCue` timer.
+  // visible. Tablet nudge is handled by the `TabletModeMultitaskCueController`
+  // timer.
   base::OneShotTimer clamshell_nudge_dismiss_timer_;
 
   views::UniqueWidgetPtr nudge_widget_;
diff --git a/chromeos/ui/frame/multitask_menu/multitask_menu_view.h b/chromeos/ui/frame/multitask_menu/multitask_menu_view.h
index ebd83a5e..a497685 100644
--- a/chromeos/ui/frame/multitask_menu/multitask_menu_view.h
+++ b/chromeos/ui/frame/multitask_menu/multitask_menu_view.h
@@ -77,18 +77,11 @@
   // If the menu is opened because of mouse hover, moving the mouse outside the
   // menu for 3 seconds will result in it auto closing. This function reduces
   // that 3 second delay to zero.
-  // TODO(sammiequon|sophiewen): Move these for testing functions to private
-  // test only api.
   static void SetSkipMouseOutDelayForTesting(bool val);
 
-  SplitButtonView* half_button_for_testing() { return half_button_.get(); }
-  MultitaskButton* full_button_for_testing() { return full_button_.get(); }
-  MultitaskButton* float_button_for_testing() { return float_button_.get(); }
-
-  bool is_reversed_for_testing() const { return is_reversed_; }
-
  private:
   class MenuPreTargetHandler;
+  friend class MultitaskMenuViewTestApi;
 
   // Callbacks for the buttons in the multitask menu view.
   void HalfButtonPressed(SnapDirection direction);
diff --git a/chromeos/ui/frame/multitask_menu/multitask_menu_view_test_api.cc b/chromeos/ui/frame/multitask_menu/multitask_menu_view_test_api.cc
new file mode 100644
index 0000000..26f5ebca
--- /dev/null
+++ b/chromeos/ui/frame/multitask_menu/multitask_menu_view_test_api.cc
@@ -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.
+
+#include "chromeos/ui/frame/multitask_menu/multitask_menu_view_test_api.h"
+
+#include "chromeos/ui/frame/multitask_menu/multitask_menu_view.h"
+
+namespace chromeos {
+
+MultitaskMenuViewTestApi::MultitaskMenuViewTestApi(MultitaskMenuView* view)
+    : multitask_menu_view_(view) {}
+
+MultitaskMenuViewTestApi::~MultitaskMenuViewTestApi() = default;
+
+SplitButtonView* MultitaskMenuViewTestApi::GetHalfButton() {
+  return multitask_menu_view_->half_button_;
+}
+
+MultitaskButton* MultitaskMenuViewTestApi::GetFullButton() {
+  return multitask_menu_view_->full_button_;
+}
+
+MultitaskButton* MultitaskMenuViewTestApi::GetFloatButton() {
+  return multitask_menu_view_->float_button_;
+}
+
+bool MultitaskMenuViewTestApi::GetIsReversed() const {
+  return multitask_menu_view_->is_reversed_;
+}
+
+}  // namespace chromeos
diff --git a/chromeos/ui/frame/multitask_menu/multitask_menu_view_test_api.h b/chromeos/ui/frame/multitask_menu/multitask_menu_view_test_api.h
new file mode 100644
index 0000000..0dd8315
--- /dev/null
+++ b/chromeos/ui/frame/multitask_menu/multitask_menu_view_test_api.h
@@ -0,0 +1,41 @@
+// 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 CHROMEOS_UI_FRAME_MULTITASK_MENU_MULTITASK_MENU_VIEW_TEST_API_H_
+#define CHROMEOS_UI_FRAME_MULTITASK_MENU_MULTITASK_MENU_VIEW_TEST_API_H_
+
+#include "base/memory/raw_ptr.h"
+
+namespace chromeos {
+
+class MultitaskButton;
+class MultitaskMenuView;
+class SplitButtonView;
+
+// Wrapper for MultitaskMenuView that exposes internal state to test
+// functions.
+class MultitaskMenuViewTestApi {
+ public:
+  explicit MultitaskMenuViewTestApi(MultitaskMenuView* view);
+  MultitaskMenuViewTestApi(const MultitaskMenuViewTestApi&) = delete;
+  MultitaskMenuViewTestApi& operator=(const MultitaskMenuViewTestApi&) = delete;
+  ~MultitaskMenuViewTestApi();
+
+  SplitButtonView* GetHalfButton();
+  MultitaskButton* GetFullButton();
+  MultitaskButton* GetFloatButton();
+
+  // The partial button's left/top button normally snaps 2/3 to the left/top,
+  // and the right/bottom button normally snaps 1/3 to the right/bottom. The
+  // user can use the alt key to toggle to and from reversed state, where the
+  // left/top button would snap 1/3 and the right/bottom button would snap 2/3.
+  bool GetIsReversed() const;
+
+ private:
+  const raw_ptr<MultitaskMenuView> multitask_menu_view_;
+};
+
+}  // namespace chromeos
+
+#endif  // CHROMEOS_UI_FRAME_MULTITASK_MENU_MULTITASK_MENU_VIEW_TEST_API_H_
diff --git a/components/BUILD.gn b/components/BUILD.gn
index 2889d81..9aec52a 100644
--- a/components/BUILD.gn
+++ b/components/BUILD.gn
@@ -455,6 +455,7 @@
       "//components/permissions:unit_tests",
       "//components/permissions/prediction_service:unit_tests",
       "//components/privacy_sandbox:unit_tests",
+      "//components/privacy_sandbox/privacy_sandbox_attestations:unit_tests",
       "//components/session_proto_db:unit_tests",
 
       # TODO(chromium: 1169835) components / reporting / storage / resources: unit_tests
diff --git a/components/account_manager_core/account_manager_facade.h b/components/account_manager_core/account_manager_facade.h
index a3a3428705..69dc05f6 100644
--- a/components/account_manager_core/account_manager_facade.h
+++ b/components/account_manager_core/account_manager_facade.h
@@ -96,8 +96,10 @@
     kChromeSettingsTurnOnSyncButton = 16,
     // Launched from ChromeOS Projector App for re-authentication.
     kChromeOSProjectorAppReauth = 17,
+    // Chrome Menu -> Turn on Sync
+    kChromeMenuTurnOnSync = 18,
 
-    kMaxValue = kChromeOSProjectorAppReauth
+    kMaxValue = kChromeMenuTurnOnSync
   };
 
   AccountManagerFacade();
diff --git a/components/account_manager_core/account_manager_facade_impl.cc b/components/account_manager_core/account_manager_facade_impl.cc
index 8a700cc..ca906a7 100644
--- a/components/account_manager_core/account_manager_facade_impl.cc
+++ b/components/account_manager_core/account_manager_facade_impl.cc
@@ -102,6 +102,7 @@
         kChromeSyncPromoAddAccount:
     case AccountManagerFacade::AccountAdditionSource::
         kChromeSettingsTurnOnSyncButton:
+    case AccountManagerFacade::AccountAdditionSource::kChromeMenuTurnOnSync:
       return false;
     // These are reauthentication cases. ARC visibility shouldn't change for
     // reauthentication.
diff --git a/components/autofill/core/browser/BUILD.gn b/components/autofill/core/browser/BUILD.gn
index ed39597..6b77fa5 100644
--- a/components/autofill/core/browser/BUILD.gn
+++ b/components/autofill/core/browser/BUILD.gn
@@ -337,6 +337,8 @@
     "payments/iban_save_manager.h",
     "payments/legal_message_line.cc",
     "payments/legal_message_line.h",
+    "payments/mandatory_reauth_manager.cc",
+    "payments/mandatory_reauth_manager.h",
     "payments/offer_notification_handler.cc",
     "payments/offer_notification_handler.h",
     "payments/otp_unmask_delegate.h",
@@ -692,6 +694,8 @@
     "mock_merchant_promo_code_manager.h",
     "mock_single_field_form_fill_router.cc",
     "mock_single_field_form_fill_router.h",
+    "payments/test/mock_mandatory_reauth_manager.cc",
+    "payments/test/mock_mandatory_reauth_manager.h",
     "payments/test/test_credit_card_otp_authenticator.cc",
     "payments/test/test_credit_card_otp_authenticator.h",
     "payments/test_authentication_requester.cc",
@@ -1014,6 +1018,7 @@
     sources += [
       "metrics/payments/better_auth_metrics_unittest.cc",
       "payments/credit_card_fido_authenticator_unittest.cc",
+      "payments/mandatory_reauth_manager_unittest.cc",
     ]
   }
 
diff --git a/components/autofill/core/browser/DEPS b/components/autofill/core/browser/DEPS
index 19f270a..6f8c55085 100644
--- a/components/autofill/core/browser/DEPS
+++ b/components/autofill/core/browser/DEPS
@@ -14,6 +14,7 @@
   "+components/security_state",
   "+components/security_interstitials/core/pref_names.h",
   "+components/signin/public",
+  "+components/strings",
   "+components/sync",
   "+components/translate/core/browser",
   "+components/translate/core/common",
diff --git a/components/autofill/core/browser/autofill_client.cc b/components/autofill/core/browser/autofill_client.cc
index ab30a52..6937be4 100644
--- a/components/autofill/core/browser/autofill_client.cc
+++ b/components/autofill/core/browser/autofill_client.cc
@@ -129,6 +129,8 @@
     base::OnceClosure cancel_mandatory_reauth_callback,
     base::RepeatingClosure close_mandatory_reauth_callback) {}
 
+void AutofillClient::ShowMandatoryReauthOptInConfirmation() {}
+
 #if !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_IOS)
 void AutofillClient::HideVirtualCardEnrollBubbleAndIconIfVisible() {
   // This is overridden by platform subclasses. Currently only
diff --git a/components/autofill/core/browser/autofill_client.h b/components/autofill/core/browser/autofill_client.h
index 270660d..6b54013d 100644
--- a/components/autofill/core/browser/autofill_client.h
+++ b/components/autofill/core/browser/autofill_client.h
@@ -516,6 +516,11 @@
       base::OnceClosure cancel_mandatory_reauth_callback,
       base::RepeatingClosure close_mandatory_reauth_callback);
 
+  // Should only be called when we are sure re-showing the bubble will display a
+  // confirmation bubble. If the most recent bubble was an opt-in bubble and it
+  // was accepted, this will display the re-auth opt-in confirmation bubble.
+  virtual void ShowMandatoryReauthOptInConfirmation();
+
 #if !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_IOS)
   // Hides the virtual card enroll bubble and icon if it is visible.
   virtual void HideVirtualCardEnrollBubbleAndIconIfVisible();
diff --git a/components/autofill/core/browser/form_data_importer.cc b/components/autofill/core/browser/form_data_importer.cc
index 8e2e363..19cbdcd 100644
--- a/components/autofill/core/browser/form_data_importer.cc
+++ b/components/autofill/core/browser/form_data_importer.cc
@@ -1055,17 +1055,19 @@
   form_associator_.OnBrowsingHistoryCleared(deletion_info);
 }
 
-void FormDataImporter::SetGuidOfCardIfNoInteractiveAuthenticationFlowCompleted(
-    absl::optional<std::string>
-        guid_of_card_if_no_interactive_authentication_flow_completed) {
-  guid_of_card_if_no_interactive_authentication_flow_completed_ =
-      std::move(guid_of_card_if_no_interactive_authentication_flow_completed);
+void FormDataImporter::
+    SetCardIdentifierIfNonInteractiveAuthenticationFlowCompleted(
+        absl::optional<absl::variant<CardGuid, CardLastFourDigits>>
+            card_identifier_if_non_interactive_authentication_flow_completed) {
+  card_identifier_if_non_interactive_authentication_flow_completed_ = std::move(
+      card_identifier_if_non_interactive_authentication_flow_completed);
 }
 
-const absl::optional<std::string>&
-FormDataImporter::GetGuidOfCardIfNoInteractiveAuthenticationFlowCompleted()
+const absl::optional<absl::variant<FormDataImporter::CardGuid,
+                                   FormDataImporter::CardLastFourDigits>>&
+FormDataImporter::GetCardIdentifierIfNonInteractiveAuthenticationFlowCompleted()
     const {
-  return guid_of_card_if_no_interactive_authentication_flow_completed_;
+  return card_identifier_if_non_interactive_authentication_flow_completed_;
 }
 
 }  // namespace autofill
diff --git a/components/autofill/core/browser/form_data_importer.h b/components/autofill/core/browser/form_data_importer.h
index 3360a93..a8169bf 100644
--- a/components/autofill/core/browser/form_data_importer.h
+++ b/components/autofill/core/browser/form_data_importer.h
@@ -38,18 +38,21 @@
 // Owned by `ChromeAutofillClient`.
 class FormDataImporter : public PersonalDataManagerObserver {
  public:
-  // Record type of the credit card import candidate extracted from the form, if
-  // one exists.
+  // Record type of the credit card extracted from the form, if one exists.
+  // TODO(crbug.com/1412326): Remove this enum and user CreditCard::RecordType
+  // instead.
   enum CreditCardImportType {
-    // No card was successfully imported from the form.
+    // No card was successfully extracted from the form.
     kNoCard,
-    // The imported card is already stored locally on the device.
+    // The extracted card is already stored locally on the device.
     kLocalCard,
-    // The imported card is already known to be a server card (either masked or
+    // The extracted card is already known to be a server card (either masked or
     // unmasked).
     kServerCard,
-    // The imported card is not currently stored with the browser.
+    // The extracted card is not currently stored with the browser.
     kNewCard,
+    // The extracted card is already known to be a virtual card.
+    kVirtualCard,
   };
 
   // The parameters should outlive the FormDataImporter.
@@ -63,6 +66,10 @@
 
   ~FormDataImporter() override;
 
+  using CardGuid = base::StrongAlias<class CardGuidTag, std::string>;
+  using CardLastFourDigits =
+      base::StrongAlias<class CardLastFourDigitsTag, std::string>;
+
   // Imports the form data, submitted by the user, into
   // `personal_data_manager_`. If a new credit card was detected and
   // `payment_methods_autofill_enabled` is set to `true`, also begins the
@@ -136,14 +143,16 @@
   }
 
   // This should only set
-  // `guid_of_card_if_no_interactive_authentication_flow_completed_` to a value
-  // when there was an autofill with no interactive authentication, otherwise it
-  // should set to nullopt.
-  void SetGuidOfCardIfNoInteractiveAuthenticationFlowCompleted(
-      absl::optional<std::string>
-          guid_of_card_if_no_interactive_authentication_flow_completed);
-  const absl::optional<std::string>&
-  GetGuidOfCardIfNoInteractiveAuthenticationFlowCompleted() const;
+  // `card_identifier_if_non_interactive_authentication_flow_completed_` to a
+  // value when there was an autofill with no interactive authentication,
+  // otherwise it should set to nullopt. If we are in the virtual card case,
+  // this will be set to the last four digits of the virtual card number.
+  // Otherwise, this will be set to the GUID of the card.
+  void SetCardIdentifierIfNonInteractiveAuthenticationFlowCompleted(
+      absl::optional<absl::variant<CardGuid, CardLastFourDigits>>
+          card_identifier_if_non_interactive_authentication_flow_completed);
+  const absl::optional<absl::variant<CardGuid, CardLastFourDigits>>&
+  GetCardIdentifierIfNonInteractiveAuthenticationFlowCompleted() const;
 
  protected:
   void set_credit_card_save_manager_for_testing(
@@ -389,14 +398,14 @@
   FormAssociator form_associator_;
 
   // Optional that will have a value when the most recent payments autofill flow
-  // had no interactive authentication. It will contain the GUID of the card
-  // where the most recent no interactive authentication has occurred. If this
-  // is empty upon form submission, it implies that the most recent autofill had
-  // an interactive authentication. Set when
-  // `SetGuidOfCardIfNoInteractiveAuthenticationFlowCompleted()` is called, and
-  // cleared on page navigation.
-  absl::optional<std::string>
-      guid_of_card_if_no_interactive_authentication_flow_completed_;
+  // had no interactive authentication. It will contain the GUID or last four
+  // digits of the card where the most recent non-interactive authentication has
+  // succeeded. If this is empty upon form submission, it implies that the most
+  // recent autofill had an interactive authentication. Set when
+  // `SetCardIdentifierIfNonInteractiveAuthenticationFlowCompleted()` is called,
+  // and cleared on page navigation.
+  absl::optional<absl::variant<CardGuid, CardLastFourDigits>>
+      card_identifier_if_non_interactive_authentication_flow_completed_;
 
   friend class AutofillMergeTest;
   friend class FormDataImporterTest;
diff --git a/components/autofill/core/browser/payments/credit_card_access_manager.cc b/components/autofill/core/browser/payments/credit_card_access_manager.cc
index e79ae1f..7eddda0 100644
--- a/components/autofill/core/browser/payments/credit_card_access_manager.cc
+++ b/components/autofill/core/browser/payments/credit_card_access_manager.cc
@@ -14,6 +14,7 @@
 #include "base/functional/not_fn.h"
 #include "base/metrics/histogram_functions.h"
 #include "base/ranges/algorithm.h"
+#include "base/strings/utf_string_conversions.h"
 #include "base/task/sequenced_task_runner.h"
 #include "base/task/task_traits.h"
 #include "base/task/thread_pool.h"
@@ -77,7 +78,7 @@
   if (client_) {
     if (auto* form_data_importer = client_->GetFormDataImporter()) {
       form_data_importer
-          ->SetGuidOfCardIfNoInteractiveAuthenticationFlowCompleted(
+          ->SetCardIdentifierIfNonInteractiveAuthenticationFlowCompleted(
               absl::nullopt);
     }
   }
@@ -1117,8 +1118,8 @@
     // This local card autofill flow did not have any interactive
     // authentication, so notify the FormDataImporter of this.
     client_->GetFormDataImporter()
-        ->SetGuidOfCardIfNoInteractiveAuthenticationFlowCompleted(
-            card_->guid());
+        ->SetCardIdentifierIfNonInteractiveAuthenticationFlowCompleted(
+            FormDataImporter::CardGuid(card_->guid()));
 
     // `accessor_->OnCreditCardFetched()` makes a copy of `card` and `cvc`
     // before it asynchronously fills them into the form. Thus we can safely
@@ -1185,10 +1186,13 @@
         // If the server responded with success and the real pan, no interactive
         // authentication happened. It's also possible that the server does not
         // provide the real pan but requests an authentication which is handled
-        // below.
+        // below. In this case, since the virtual card has a randomly generated
+        // GUID and is not stored in the autofill table, we must set the card
+        // identifier as the last four digits of the virtual card.
         client_->GetFormDataImporter()
-            ->SetGuidOfCardIfNoInteractiveAuthenticationFlowCompleted(
-                card_->guid());
+            ->SetCardIdentifierIfNonInteractiveAuthenticationFlowCompleted(
+                FormDataImporter::CardLastFourDigits(
+                    base::UTF16ToUTF8(card_->LastFourDigits())));
 
         autofill_metrics::LogServerCardUnmaskResult(
             autofill_metrics::ServerCardUnmaskResult::kRiskBasedUnmasked,
diff --git a/components/autofill/core/browser/payments/credit_card_access_manager_unittest.cc b/components/autofill/core/browser/payments/credit_card_access_manager_unittest.cc
index ab4c281..f4426bf 100644
--- a/components/autofill/core/browser/payments/credit_card_access_manager_unittest.cc
+++ b/components/autofill/core/browser/payments/credit_card_access_manager_unittest.cc
@@ -71,6 +71,7 @@
 #include "services/metrics/public/cpp/ukm_builders.h"
 #include "services/network/public/cpp/shared_url_loader_factory.h"
 #include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/abseil-cpp/absl/types/variant.h"
 #include "ui/base/l10n/l10n_util.h"
 #include "ui/gfx/geometry/rect.h"
 #include "url/gurl.h"
@@ -786,10 +787,17 @@
 
   // There was no interactive authentication in this flow, so check that this
   // is signaled correctly.
-  const absl::optional<std::string>& guid =
-      autofill_client_.GetFormDataImporter()
-          ->GetGuidOfCardIfNoInteractiveAuthenticationFlowCompleted();
-  EXPECT_EQ(guid, kTestGUID);
+  absl::optional<absl::variant<FormDataImporter::CardGuid,
+                               FormDataImporter::CardLastFourDigits>>
+      card_identifier =
+          autofill_client_.GetFormDataImporter()
+              ->GetCardIdentifierIfNonInteractiveAuthenticationFlowCompleted();
+  ASSERT_TRUE(card_identifier.has_value());
+  ASSERT_TRUE(absl::holds_alternative<FormDataImporter::CardGuid>(
+      card_identifier.value()));
+  ASSERT_EQ(
+      absl::get<FormDataImporter::CardGuid>(card_identifier.value()).value(),
+      kTestGUID);
 }
 
 // Ensures that FetchCreditCard() reports a failure when a card does not exist.
@@ -848,9 +856,10 @@
     }
     // Expect that we did not signal that there was no interactive
     // authentication.
-    EXPECT_FALSE(autofill_client_.GetFormDataImporter()
-                     ->GetGuidOfCardIfNoInteractiveAuthenticationFlowCompleted()
-                     .has_value());
+    EXPECT_FALSE(
+        autofill_client_.GetFormDataImporter()
+            ->GetCardIdentifierIfNonInteractiveAuthenticationFlowCompleted()
+            .has_value());
   }
 }
 
@@ -2539,11 +2548,22 @@
   EXPECT_EQ(accessor_->expiry_year(), base::UTF8ToUTF16(test::NextYear()));
 
   // There was no interactive authentication in this flow, so check that this
+  // is signaled correctly. The card identifier in the virtual card case should
+  // be the last four digits of the card number.
+  // There was no interactive authentication in this flow, so check that this
   // is signaled correctly.
-  const absl::optional<std::string>& guid =
-      autofill_client_.GetFormDataImporter()
-          ->GetGuidOfCardIfNoInteractiveAuthenticationFlowCompleted();
-  EXPECT_EQ(guid, kTestGUID);
+  absl::optional<absl::variant<FormDataImporter::CardGuid,
+                               FormDataImporter::CardLastFourDigits>>
+      card_identifier =
+          autofill_client_.GetFormDataImporter()
+              ->GetCardIdentifierIfNonInteractiveAuthenticationFlowCompleted();
+  ASSERT_TRUE(card_identifier.has_value());
+  ASSERT_TRUE(absl::holds_alternative<FormDataImporter::CardLastFourDigits>(
+      card_identifier.value()));
+  ASSERT_EQ(
+      absl::get<FormDataImporter::CardLastFourDigits>(card_identifier.value())
+          .value(),
+      response.real_pan.substr(response.real_pan.size() - 4));
 
   // Expect the metrics are logged correctly.
   histogram_tester.ExpectUniqueSample(
@@ -2577,9 +2597,10 @@
           .with_cvc(u"123"));
 
   // Expect that we did not signal that there was no interactive authentication.
-  EXPECT_FALSE(autofill_client_.GetFormDataImporter()
-                   ->GetGuidOfCardIfNoInteractiveAuthenticationFlowCompleted()
-                   .has_value());
+  EXPECT_FALSE(
+      autofill_client_.GetFormDataImporter()
+          ->GetCardIdentifierIfNonInteractiveAuthenticationFlowCompleted()
+          .has_value());
 
   // Expect the metrics are logged correctly.
   histogram_tester.ExpectUniqueSample(
@@ -2610,9 +2631,10 @@
           .with_cvc(u"123"));
 
   // Expect that we did not signal that there was no interactive authentication.
-  EXPECT_FALSE(autofill_client_.GetFormDataImporter()
-                   ->GetGuidOfCardIfNoInteractiveAuthenticationFlowCompleted()
-                   .has_value());
+  EXPECT_FALSE(
+      autofill_client_.GetFormDataImporter()
+          ->GetCardIdentifierIfNonInteractiveAuthenticationFlowCompleted()
+          .has_value());
 
   // Expect the metrics are logged correctly.
   histogram_tester.ExpectUniqueSample(
@@ -2665,9 +2687,10 @@
   }
 
   // Expect that we did not signal that there was no interactive authentication.
-  EXPECT_FALSE(autofill_client_.GetFormDataImporter()
-                   ->GetGuidOfCardIfNoInteractiveAuthenticationFlowCompleted()
-                   .has_value());
+  EXPECT_FALSE(
+      autofill_client_.GetFormDataImporter()
+          ->GetCardIdentifierIfNonInteractiveAuthenticationFlowCompleted()
+          .has_value());
 
   // Expect the metrics are logged correctly.
   histogram_tester.ExpectUniqueSample(
@@ -3094,20 +3117,23 @@
       autofill_metrics::ServerCardUnmaskResult::kFlowCancelled, 1);
 }
 
-// Test that the CreditCardAccessManager's destructor resets the GUID of the
-// card that had no interactive authentication flows completed in the associated
-// FormDataImporter.
-TEST_F(CreditCardAccessManagerTest, DestructorResetsCardGuid) {
+// Test that the CreditCardAccessManager's destructor resets the identifier of
+// the card that had no interactive authentication flows completed in the
+// associated FormDataImporter.
+TEST_F(CreditCardAccessManagerTest, DestructorResetsCardIdentifier) {
   auto* form_data_importer = autofill_client_.GetFormDataImporter();
-  form_data_importer->SetGuidOfCardIfNoInteractiveAuthenticationFlowCompleted(
-      "TestGuid");
-  EXPECT_TRUE(form_data_importer
-                  ->GetGuidOfCardIfNoInteractiveAuthenticationFlowCompleted()
-                  .has_value());
+  form_data_importer
+      ->SetCardIdentifierIfNonInteractiveAuthenticationFlowCompleted(
+          FormDataImporter::CardGuid("TestGuid"));
+  EXPECT_TRUE(
+      form_data_importer
+          ->GetCardIdentifierIfNonInteractiveAuthenticationFlowCompleted()
+          .has_value());
   autofill_driver_.reset();
-  EXPECT_FALSE(form_data_importer
-                   ->GetGuidOfCardIfNoInteractiveAuthenticationFlowCompleted()
-                   .has_value());
+  EXPECT_FALSE(
+      form_data_importer
+          ->GetCardIdentifierIfNonInteractiveAuthenticationFlowCompleted()
+          .has_value());
 }
 
 // Params of the CreditCardAccessManagerCardMetadataTest:
diff --git a/components/autofill/core/browser/payments/mandatory_reauth_manager.cc b/components/autofill/core/browser/payments/mandatory_reauth_manager.cc
new file mode 100644
index 0000000..ae9fb74c
--- /dev/null
+++ b/components/autofill/core/browser/payments/mandatory_reauth_manager.cc
@@ -0,0 +1,243 @@
+// 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 "components/autofill/core/browser/payments/mandatory_reauth_manager.h"
+
+#include "base/strings/utf_string_conversions.h"
+#include "components/autofill/core/browser/data_model/credit_card.h"
+#include "components/autofill/core/browser/personal_data_manager.h"
+#include "components/device_reauth/device_authenticator.h"
+#include "components/strings/grit/components_chromium_strings.h"
+#include "components/strings/grit/components_google_chrome_strings.h"
+#include "ui/base/l10n/l10n_util.h"
+
+namespace autofill::payments {
+
+MandatoryReauthManager::MandatoryReauthManager(AutofillClient* client)
+    : client_(client) {}
+MandatoryReauthManager::~MandatoryReauthManager() = default;
+
+bool MandatoryReauthManager::ShouldOfferOptin(
+    const CreditCard& card_extracted_from_form,
+    const absl::optional<absl::variant<FormDataImporter::CardGuid,
+                                       FormDataImporter::CardLastFourDigits>>&
+        card_identifier_if_non_interactive_authentication_flow_completed,
+    FormDataImporter::CreditCardImportType import_type) {
+  // We should not offer to update a user pref in off the record mode.
+  if (client_->IsOffTheRecord()) {
+    return false;
+  }
+
+  // If the user prefs denote that we should not display the re-auth opt-in
+  // bubble, return that we should not offer mandatory re-auth opt-in.
+  if (!client_->GetPersonalDataManager()
+           ->ShouldShowPaymentMethodsMandatoryReauthPromo()) {
+    return false;
+  }
+
+  // If the device authenticator is not present or we can not authenticate with
+  // biometrics, there will be no way to re-auth if the user enrolls, so return
+  // that we should not offer mandatory re-auth opt-in.
+  // TODO(crbug.com/4555994): Offer opt-in if the user only has biometric or
+  // screen lock available, instead of only if the user has biometric available.
+  if (scoped_refptr<device_reauth::DeviceAuthenticator> device_authenticator =
+          client_->GetDeviceAuthenticator();
+      !device_authenticator ||
+      !device_authenticator->CanAuthenticateWithBiometrics()) {
+    return false;
+  }
+
+  // If `card_identifier_if_non_interactive_authentication_flow_completed` is
+  // not present, this can mean one of two things: 1) No card was autofilled 2)
+  // All autofilled cards went through an interactive authentication flow. In
+  // the first case it makes no sense to show a reauth proposal because this is
+  // not an autofill moment. In the second case, we don't want to show an opt-in
+  // prompt because the user never experienced non-interactive authentication,
+  // and actually just went through an interactive authentication. Displaying a
+  // prompt to enable re-authentication could be confusing.
+  if (!card_identifier_if_non_interactive_authentication_flow_completed
+           .has_value()) {
+    return false;
+  }
+
+  // We want to offer re-auth if the most recent payments autofill was a
+  // non-interactive authentication.
+  // `card_identifier_if_non_interactive_authentication_flow_completed` is set
+  // when a non-interactive authentication occurs, and
+  // `card_extracted_from_form` contains the card details of the card extracted
+  // from the form. Thus, below contains extra logic to check that
+  // `card_extracted_from_form` matches
+  // `card_identifier_if_non_interactive_authentication_flow_completed` to
+  // ensure they are the same card, which implies that the most recent payments
+  // autofill was a non-interactive authentication.
+  switch (import_type) {
+    case FormDataImporter::CreditCardImportType::kLocalCard: {
+      // From `import_type` we know that the submitted card exists as a local
+      // card in the PersonalDataManager. If
+      // `card_identifier_if_non_interactive_authentication_flow_completed`
+      // holds no card GUID, that means that the card that was most recently
+      // filled with non-interactive authentication was not a local card, so we
+      // should not offer re-auth. This is possible when a user goes through a
+      // non-interactive authentication flow with a card that is not a local
+      // card, then types in a local card manually into the form.
+      if (!absl::holds_alternative<FormDataImporter::CardGuid>(
+              card_identifier_if_non_interactive_authentication_flow_completed
+                  .value())) {
+        return false;
+      }
+
+      return LastFilledCardMatchesSubmittedCard(
+          absl::get<FormDataImporter::CardGuid>(
+              card_identifier_if_non_interactive_authentication_flow_completed
+                  .value()),
+          card_extracted_from_form);
+    }
+    case FormDataImporter::CreditCardImportType::kServerCard: {
+      // From `import_type` we know that the submitted card exists as a server
+      // card in the PersonalDataManager. If
+      // `card_identifier_if_non_interactive_authentication_flow_completed`
+      // holds no card GUID, that means that the card that was most recently
+      // filled with non-interactive authentication was not a server card, so we
+      // should not offer re-auth. This is possible when a user goes through a
+      // non-interactive authentication flow with a card that is not a server
+      // card, then types in a server card manually into the form.
+      if (!absl::holds_alternative<FormDataImporter::CardGuid>(
+              card_identifier_if_non_interactive_authentication_flow_completed
+                  .value())) {
+        return false;
+      }
+
+      for (CreditCard* local_card :
+           client_->GetPersonalDataManager()->GetLocalCreditCards()) {
+        if (local_card->IsLocalOrServerDuplicateOf(card_extracted_from_form)) {
+          // We found a matching local card for this server card. We then need
+          // to check that the local card version of this card was the card most
+          // recently filled into the form with non-interactive authentication,
+          // as we should show the opt-in prompt in this case.
+          return LastFilledCardMatchesSubmittedCard(
+              absl::get<FormDataImporter::CardGuid>(
+                  card_identifier_if_non_interactive_authentication_flow_completed
+                      .value()),
+              *local_card);
+        }
+      }
+
+      // We could not find a matching local card for this server card, so we
+      // should not offer re-auth opt-in as there is no re-auth functionality
+      // for server cards.
+      return false;
+    }
+    case FormDataImporter::CreditCardImportType::kVirtualCard: {
+      // From `import_type` we know that the submitted card exists as a virtual
+      // card in the fetched virtual cards cache. If
+      // `card_identifier_if_non_interactive_authentication_flow_completed`
+      // holds no card last four digits, that means that the card that was most
+      // recently filled with non-interactive authentication was not a virtual
+      // card, so we should not offer re-auth. This is possible when a user goes
+      // through a non-interactive authentication flow with a card that is not a
+      // virtual card, then types in a virtual card manually into the form.
+      if (!absl::holds_alternative<FormDataImporter::CardLastFourDigits>(
+              card_identifier_if_non_interactive_authentication_flow_completed
+                  .value())) {
+        return false;
+      }
+
+      // If we have extracted a virtual card, we must check the last four digits
+      // of the virtual card green pathed against the last four digits of the
+      // card extracted from the form, as we do not store virtual cards in the
+      // autofill table, so the card extracted from the form will not have a
+      // GUID.
+      return base::UTF8ToUTF16(
+                 absl::get<FormDataImporter::CardLastFourDigits>(
+                     card_identifier_if_non_interactive_authentication_flow_completed
+                         .value())
+                     .value()) == card_extracted_from_form.LastFourDigits();
+    }
+    case FormDataImporter::CreditCardImportType::kNewCard:
+    case FormDataImporter::CreditCardImportType::kNoCard:
+      // We should not offer mandatory re-auth opt-in for new cards or undefined
+      // cards.
+      return false;
+  }
+}
+
+void MandatoryReauthManager::StartOptInFlow() {
+  client_->ShowMandatoryReauthOptInPrompt(
+      base::BindOnce(&MandatoryReauthManager::OnUserAcceptedOptInPrompt,
+                     weak_ptr_factory_.GetWeakPtr()),
+      base::BindOnce(&MandatoryReauthManager::OnUserCancelledOptInPrompt,
+                     weak_ptr_factory_.GetWeakPtr()),
+      base::BindRepeating(&MandatoryReauthManager::OnUserClosedOptInPrompt,
+                          weak_ptr_factory_.GetWeakPtr()));
+}
+
+void MandatoryReauthManager::OnUserAcceptedOptInPrompt() {
+  scoped_refptr<device_reauth::DeviceAuthenticator> device_authenticator =
+      client_->GetDeviceAuthenticator();
+  CHECK(device_authenticator);
+
+  // `device_authenticator` is a scoped_refptr, so we need to keep it alive
+  // until the callback that uses it is complete.
+  base::OnceClosure bind_device_authenticator =
+      base::DoNothingWithBoundArgs(device_authenticator);
+#if BUILDFLAG(IS_MAC) || BUILDFLAG(IS_WIN)
+  device_authenticator->AuthenticateWithMessage(
+      l10n_util::GetStringUTF16(IDS_PAYMENTS_AUTOFILL_MANDATORY_REAUTH_PROMPT),
+      base::BindOnce(
+          &MandatoryReauthManager::OnOptInAuthenticationStepCompleted,
+          weak_ptr_factory_.GetWeakPtr())
+          .Then(base::IgnoreArgs(std::move(bind_device_authenticator))));
+#elif BUILDFLAG(IS_ANDROID)
+  // TODO(crbug.com/1427216): Convert this to
+  // DeviceAuthenticator::AuthenticateWithMessage() with the correct message
+  // once it is supported. Currently, the message is "Verify it's you".
+  device_authenticator->Authenticate(
+      device_reauth::DeviceAuthRequester::kPaymentsAutofillOptIn,
+      base::BindOnce(
+          &MandatoryReauthManager::OnOptInAuthenticationStepCompleted,
+          weak_ptr_factory_.GetWeakPtr())
+          .Then(base::IgnoreArgs(std::move(bind_device_authenticator))),
+      /*use_last_valid_auth=*/true);
+#else
+  NOTREACHED_NORETURN();
+#endif
+}
+
+void MandatoryReauthManager::OnOptInAuthenticationStepCompleted(bool success) {
+  if (success) {
+    client_->GetPersonalDataManager()->SetPaymentMethodsMandatoryReauthEnabled(
+        /*enabled=*/true);
+    client_->ShowMandatoryReauthOptInConfirmation();
+  } else {
+    client_->GetPersonalDataManager()
+        ->IncrementPaymentMethodsMandatoryReauthPromoShownCounter();
+  }
+}
+
+void MandatoryReauthManager::OnUserCancelledOptInPrompt() {
+  client_->GetPersonalDataManager()->SetPaymentMethodsMandatoryReauthEnabled(
+      /*enabled=*/false);
+}
+void MandatoryReauthManager::OnUserClosedOptInPrompt() {
+  client_->GetPersonalDataManager()
+      ->IncrementPaymentMethodsMandatoryReauthPromoShownCounter();
+}
+
+bool MandatoryReauthManager::LastFilledCardMatchesSubmittedCard(
+    FormDataImporter::CardGuid guid_of_last_filled_card,
+    const CreditCard& card_extracted_from_form) {
+  // Get the card stored with the same GUID as the most recent card filled
+  // into the form. If we do not have a card stored, then that means the
+  // user deleted it after filling the form but before submitting. Thus we
+  // should return that we should not offer re-auth opt-in.
+  CreditCard* stored_card =
+      client_->GetPersonalDataManager()->GetCreditCardByGUID(
+          guid_of_last_filled_card.value());
+  if (!stored_card) {
+    return false;
+  }
+
+  return stored_card->MatchingCardDetails(card_extracted_from_form);
+}
+}  // namespace autofill::payments
diff --git a/components/autofill/core/browser/payments/mandatory_reauth_manager.h b/components/autofill/core/browser/payments/mandatory_reauth_manager.h
new file mode 100644
index 0000000..c5de3d94
--- /dev/null
+++ b/components/autofill/core/browser/payments/mandatory_reauth_manager.h
@@ -0,0 +1,90 @@
+// 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 COMPONENTS_AUTOFILL_CORE_BROWSER_PAYMENTS_MANDATORY_REAUTH_MANAGER_H_
+#define COMPONENTS_AUTOFILL_CORE_BROWSER_PAYMENTS_MANDATORY_REAUTH_MANAGER_H_
+
+#include "base/memory/scoped_refptr.h"
+#include "components/autofill/core/browser/autofill_client.h"
+#include "components/autofill/core/browser/data_model/credit_card.h"
+#include "components/autofill/core/browser/form_data_importer.h"
+
+namespace autofill {
+
+class AutofillClient;
+
+namespace payments {
+
+class MandatoryReauthManager {
+ public:
+  explicit MandatoryReauthManager(AutofillClient* client);
+  MandatoryReauthManager(const MandatoryReauthManager&) = delete;
+  MandatoryReauthManager& operator=(const MandatoryReauthManager&) = delete;
+  virtual ~MandatoryReauthManager();
+
+  // Returns true if the user conditions denote that we should offer opt-in for
+  // this user, false otherwise. `card_extracted_from_form` is the card
+  // extracted during form submission, and `import_type` is the type of the card
+  // that was submitted in the form.
+  // `card_identifier_if_non_interactive_authentication_flow_completed` will be
+  // present if a payments autofill occurred with non-interactive
+  // authentication. `import_type` indicates that the submitted card corresponds
+  // to an already saved local card, server card, etc., or if this is a new
+  // card. `import_type` will be used in conjunction with
+  // `card_extracted_from_form` and
+  // `card_identifier_if_non_interactive_authentication_flow_completed` to help
+  // match the card submitted in the form with the card that was successfully
+  // autofilled with non-interactive authentication. If there is a match, then
+  // we know the most recent card filled with non-interactive authentication was
+  // the card that was submitted in the form, so we should offer re-auth opt-in.
+  virtual bool ShouldOfferOptin(
+      const CreditCard& card_extracted_from_form,
+      const absl::optional<absl::variant<FormDataImporter::CardGuid,
+                                         FormDataImporter::CardLastFourDigits>>&
+          card_identifier_if_non_interactive_authentication_flow_completed,
+      FormDataImporter::CreditCardImportType import_type);
+
+  // Starts the opt-in flow. This flow includes an opt-in bubble, an
+  // authentication step, and then a confirmation bubble. This function should
+  // only be called after we have checked that we should offer opt-in by calling
+  // `ShouldOfferOptin()`.
+  virtual void StartOptInFlow();
+
+  // Triggered when the user accepts the opt-in prompt. This will initiate an
+  // authentication.
+  virtual void OnUserAcceptedOptInPrompt();
+
+  // Triggered when the user completes the authentication step in
+  // the opt-in flow. If this is successful, it will enroll the user into
+  // mandatory re-auth, and display a confirmation bubble. Otherwise it will
+  // increment the promo shown counter.
+  virtual void OnOptInAuthenticationStepCompleted(bool success);
+
+  // Triggered when the user cancels the opt-in prompt.
+  virtual void OnUserCancelledOptInPrompt();
+
+  // Triggered when the user closes the opt-in prompt.
+  virtual void OnUserClosedOptInPrompt();
+
+ private:
+  // Returns true if the autofill table contains a CreditCard for
+  // `guid_of_last_filled_card` that matches `card_extracted_from_form`. If the
+  // card is not present anymore when this function is called, it will return
+  // false. This can occur if the user deleted the card from the autofill table
+  // after filling it.
+  bool LastFilledCardMatchesSubmittedCard(
+      FormDataImporter::CardGuid guid_of_last_filled_card,
+      const CreditCard& card_extracted_from_form);
+
+  // Raw pointer to the web content's AutofillClient.
+  raw_ptr<AutofillClient> client_;
+
+  base::WeakPtrFactory<MandatoryReauthManager> weak_ptr_factory_{this};
+};
+
+}  // namespace payments
+
+}  // namespace autofill
+
+#endif  // COMPONENTS_AUTOFILL_CORE_BROWSER_PAYMENTS_MANDATORY_REAUTH_MANAGER_H_
diff --git a/components/autofill/core/browser/payments/mandatory_reauth_manager_unittest.cc b/components/autofill/core/browser/payments/mandatory_reauth_manager_unittest.cc
new file mode 100644
index 0000000..9c9077f
--- /dev/null
+++ b/components/autofill/core/browser/payments/mandatory_reauth_manager_unittest.cc
@@ -0,0 +1,341 @@
+// 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 "components/autofill/core/browser/payments/mandatory_reauth_manager.h"
+#include "base/strings/utf_string_conversions.h"
+
+#include "base/memory/scoped_refptr.h"
+#include "base/test/task_environment.h"
+#include "components/autofill/core/browser/autofill_test_utils.h"
+#include "components/autofill/core/browser/test_autofill_client.h"
+#include "components/autofill/core/browser/test_personal_data_manager.h"
+#include "components/autofill/core/common/autofill_payments_features.h"
+#include "components/autofill/core/common/autofill_prefs.h"
+#include "components/device_reauth/mock_device_authenticator.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace autofill::payments {
+
+class MandatoryReauthManagerTest : public testing::Test {
+ public:
+  void SetUp() override {
+    autofill_client_ = std::make_unique<TestAutofillClient>();
+    mock_device_authenticator_ =
+        static_cast<device_reauth::MockDeviceAuthenticator*>(
+            autofill_client_->GetDeviceAuthenticator().get());
+    mandatory_reauth_manager_ =
+        std::make_unique<MandatoryReauthManager>(autofill_client_.get());
+    autofill_client_->GetPersonalDataManager()->Init(
+        /*profile_database=*/nullptr,
+        /*account_database=*/nullptr,
+        /*pref_service=*/autofill_client_->GetPrefs(),
+        /*local_state=*/autofill_client_->GetPrefs(),
+        /*identity_manager=*/nullptr,
+        /*history_service=*/nullptr,
+        /*sync_service=*/nullptr,
+        /*strike_database=*/nullptr,
+        /*image_fetcher=*/nullptr,
+        /*is_off_the_record=*/false);
+    test::SetCreditCardInfo(&server_card_, "Test User", "1111" /* Visa */,
+                            test::NextMonth().c_str(), test::NextYear().c_str(),
+                            "1");
+    SetCanAuthenticateWithBiometrics(true);
+  }
+
+ protected:
+  void SetCanAuthenticateWithBiometrics(bool value) {
+    ON_CALL(*mock_device_authenticator_, CanAuthenticateWithBiometrics)
+        .WillByDefault(testing::Return(value));
+  }
+
+  base::test::TaskEnvironment task_environment_;
+  std::unique_ptr<TestAutofillClient> autofill_client_;
+  raw_ptr<device_reauth::MockDeviceAuthenticator> mock_device_authenticator_;
+  std::unique_ptr<MandatoryReauthManager> mandatory_reauth_manager_;
+  CreditCard local_card_ = test::GetCreditCard();
+  CreditCard server_card_ = test::GetMaskedServerCard();
+  CreditCard virtual_card_ = test::GetVirtualCard();
+};
+
+// Test that the MandatoryReauthManager returns that we should offer re-auth
+// opt-in if the conditions for offering it are all met for local cards.
+TEST_F(MandatoryReauthManagerTest, ShouldOfferOptin_LocalCard) {
+  base::test::ScopedFeatureList feature_list;
+  feature_list.InitAndEnableFeature(
+      features::kAutofillEnablePaymentsMandatoryReauth);
+
+  autofill_client_->GetPersonalDataManager()->AddCreditCard(local_card_);
+
+  EXPECT_TRUE(mandatory_reauth_manager_->ShouldOfferOptin(
+      local_card_, FormDataImporter::CardGuid(local_card_.guid()),
+      FormDataImporter::kLocalCard));
+}
+
+// Test that the MandatoryReauthManager returns that we should not offer re-auth
+// opt-in if the card identifier stored has the last four digits instead of a
+// GUID. This can occur if a user encounters non-interactive authentication with
+// a virtual card autofill, but then deletes the card in the form and manually
+// types in a known local card. For test thoroughness of edge cases, we have
+// made the last four digits be the same as the last four digits of the local
+// card.
+TEST_F(MandatoryReauthManagerTest,
+       ShouldOfferOptin_LocalCard_InvalidCardIdentifier) {
+  base::test::ScopedFeatureList feature_list(
+      features::kAutofillEnablePaymentsMandatoryReauth);
+
+  autofill_client_->GetPersonalDataManager()->AddCreditCard(local_card_);
+
+  EXPECT_FALSE(mandatory_reauth_manager_->ShouldOfferOptin(
+      local_card_,
+      FormDataImporter::CardLastFourDigits(
+          base::UTF16ToUTF8(local_card_.LastFourDigits())),
+      FormDataImporter::kLocalCard));
+}
+
+// Test that the MandatoryReauthManager returns that we should not offer re-auth
+// opt-in if the conditions for offering it are all met, but the feature flag is
+// off.
+TEST_F(MandatoryReauthManagerTest, ShouldOfferOptin_LocalCard_FlagOff) {
+  base::test::ScopedFeatureList feature_list;
+  feature_list.InitAndDisableFeature(
+      features::kAutofillEnablePaymentsMandatoryReauth);
+
+  autofill_client_->GetPersonalDataManager()->AddCreditCard(local_card_);
+
+  EXPECT_FALSE(mandatory_reauth_manager_->ShouldOfferOptin(
+      local_card_, FormDataImporter::CardGuid(local_card_.guid()),
+      FormDataImporter::kLocalCard));
+}
+
+// Test that the MandatoryReauthManager returns that we should not offer re-auth
+// opt-in if the conditions for offering it are all met but we are in off the
+// record mode.
+TEST_F(MandatoryReauthManagerTest, ShouldOfferOptin_OffTheRecord) {
+  base::test::ScopedFeatureList feature_list(
+      features::kAutofillEnablePaymentsMandatoryReauth);
+
+  autofill_client_->GetPersonalDataManager()->AddCreditCard(local_card_);
+
+  autofill_client_->set_is_off_the_record(true);
+
+  EXPECT_FALSE(mandatory_reauth_manager_->ShouldOfferOptin(
+      local_card_, FormDataImporter::CardGuid(local_card_.guid()),
+      FormDataImporter::kLocalCard));
+}
+
+// Test that the MandatoryReauthManager returns that we should offer re-auth
+// opt-in if the conditions for offering it are all met for virtual cards.
+TEST_F(MandatoryReauthManagerTest, ShouldOfferOptin_VirtualCard) {
+  base::test::ScopedFeatureList feature_list(
+      features::kAutofillEnablePaymentsMandatoryReauth);
+
+  EXPECT_TRUE(mandatory_reauth_manager_->ShouldOfferOptin(
+      virtual_card_,
+      FormDataImporter::CardLastFourDigits(
+          base::UTF16ToUTF8(virtual_card_.LastFourDigits())),
+      FormDataImporter::kVirtualCard));
+}
+
+// Test that the MandatoryReauthManager returns that we should not offer re-auth
+// opt-in for a virtual card if the card identifier is a GUID instead of a last
+// four digits.
+TEST_F(MandatoryReauthManagerTest,
+       ShouldOfferOptin_VirtualCard_InvalidCardIdentifier) {
+  base::test::ScopedFeatureList feature_list(
+      features::kAutofillEnablePaymentsMandatoryReauth);
+
+  EXPECT_FALSE(mandatory_reauth_manager_->ShouldOfferOptin(
+      virtual_card_, FormDataImporter::CardGuid(virtual_card_.guid()),
+      FormDataImporter::kVirtualCard));
+}
+
+// Test that the MandatoryReauthManager returns that we should not offer re-auth
+// opt-in if the conditions if the last four digits in the virtual card case do
+// not match.
+TEST_F(MandatoryReauthManagerTest,
+       ShouldNotOfferOptin_LastFourDigitsDontMatch_VirtualCard) {
+  base::test::ScopedFeatureList feature_list(
+      features::kAutofillEnablePaymentsMandatoryReauth);
+
+  EXPECT_FALSE(mandatory_reauth_manager_->ShouldOfferOptin(
+      virtual_card_, FormDataImporter::CardLastFourDigits("1234"),
+      FormDataImporter::kVirtualCard));
+}
+
+// Test that the MandatoryReauthManager returns that we should not offer re-auth
+// opt-in if the user has already made a decision on opting in or out of
+// re-auth.
+TEST_F(MandatoryReauthManagerTest, ShouldOfferOptin_UserAlreadyMadeDecision) {
+  base::test::ScopedFeatureList feature_list(
+      features::kAutofillEnablePaymentsMandatoryReauth);
+
+  mandatory_reauth_manager_->OnUserCancelledOptInPrompt();
+
+  autofill_client_->GetPersonalDataManager()->AddCreditCard(local_card_);
+
+  EXPECT_FALSE(mandatory_reauth_manager_->ShouldOfferOptin(
+      local_card_, FormDataImporter::CardGuid(local_card_.guid()),
+      FormDataImporter::kLocalCard));
+  EXPECT_TRUE(autofill_client_->GetPrefs()->GetUserPrefValue(
+      prefs::kAutofillPaymentMethodsMandatoryReauth));
+}
+
+// Test that the MandatoryReauthManager returns that we should not offer re-auth
+// opt-in if biometrics are not available on the device.
+TEST_F(MandatoryReauthManagerTest, ShouldOfferOptin_BiometricsNotAvailable) {
+  base::test::ScopedFeatureList feature_list(
+      features::kAutofillEnablePaymentsMandatoryReauth);
+
+  SetCanAuthenticateWithBiometrics(false);
+
+  autofill_client_->GetPersonalDataManager()->AddCreditCard(local_card_);
+
+  EXPECT_FALSE(mandatory_reauth_manager_->ShouldOfferOptin(
+      local_card_, FormDataImporter::CardGuid(local_card_.guid()),
+      FormDataImporter::kLocalCard));
+}
+
+// Test that the MandatoryReauthManager returns that we should not offer re-auth
+// opt-in if the conditions for offering re-auth are met, but any card autofills
+// that occurred required interactive authentication.
+TEST_F(MandatoryReauthManagerTest,
+       ShouldOfferOptin_AllCardAutofillsRequiredInteractiveAuthentication) {
+  base::test::ScopedFeatureList feature_list(
+      features::kAutofillEnablePaymentsMandatoryReauth);
+
+  autofill_client_->GetPersonalDataManager()->AddCreditCard(local_card_);
+
+  EXPECT_FALSE(mandatory_reauth_manager_->ShouldOfferOptin(
+      local_card_, absl::nullopt, FormDataImporter::kLocalCard));
+}
+
+// Test that the MandatoryReauthManager returns that we should offer re-auth
+// opt-in if we have a matching local card for a server card submitted in the
+// form.
+TEST_F(MandatoryReauthManagerTest,
+       ShouldOfferOptin_ServerCardWithMatchingLocalCard) {
+  base::test::ScopedFeatureList feature_list(
+      features::kAutofillEnablePaymentsMandatoryReauth);
+
+  autofill_client_->GetPersonalDataManager()->AddCreditCard(local_card_);
+
+  EXPECT_TRUE(mandatory_reauth_manager_->ShouldOfferOptin(
+      server_card_, FormDataImporter::CardGuid(local_card_.guid()),
+      FormDataImporter::kServerCard));
+}
+
+// Test that the MandatoryReauthManager returns that we should not offer re-auth
+// opt-in if we do not have a matching local card for a server card submitted in
+// the form.
+TEST_F(MandatoryReauthManagerTest,
+       ShouldOfferOptin_ServerCardWithNoMatchingLocalCard) {
+  base::test::ScopedFeatureList feature_list(
+      features::kAutofillEnablePaymentsMandatoryReauth);
+
+  autofill_client_->GetPersonalDataManager()->AddCreditCard(server_card_);
+
+  EXPECT_FALSE(mandatory_reauth_manager_->ShouldOfferOptin(
+      server_card_, FormDataImporter::CardGuid(server_card_.guid()),
+      FormDataImporter::kServerCard));
+}
+
+// Test that the MandatoryReauthManager returns that we should not offer re-auth
+// opt-in if we do not have a stored card that matches the card extracted from
+// the form.
+TEST_F(MandatoryReauthManagerTest,
+       ShouldOfferOptin_NoStoredCardForExtractedCard) {
+  base::test::ScopedFeatureList feature_list(
+      features::kAutofillEnablePaymentsMandatoryReauth);
+
+  EXPECT_FALSE(mandatory_reauth_manager_->ShouldOfferOptin(
+      server_card_, FormDataImporter::CardGuid(server_card_.guid()),
+      FormDataImporter::kServerCard));
+}
+
+// Test that starting the re-auth opt-in flow will trigger the re-auth opt-in
+// prompt to be shown.
+TEST_F(MandatoryReauthManagerTest, StartOptInFlow) {
+  mandatory_reauth_manager_->StartOptInFlow();
+  EXPECT_TRUE(autofill_client_->GetMandatoryReauthOptInPromptWasShown());
+}
+
+// Test that the MandatoryReauthManager correctly handles the case where the
+// user accepts the re-auth prompt.
+TEST_F(MandatoryReauthManagerTest, OnUserAcceptedOptInPrompt) {
+#if BUILDFLAG(IS_MAC) || BUILDFLAG(IS_WIN)
+  ON_CALL(*mock_device_authenticator_, AuthenticateWithMessage)
+#elif BUILDFLAG(IS_ANDROID)
+  ON_CALL(*mock_device_authenticator_, Authenticate)
+#endif  // BUILDFLAG(IS_MAC) || BUILDFLAG(IS_WIN)
+      .WillByDefault(
+          testing::WithArg<1>([](base::OnceCallback<void(bool)> callback) {
+            std::move(callback).Run(false);
+          }));
+
+  // We need to call `StartOptInFlow()` here to ensure the device
+  // authenticator gets set.
+  static_cast<MandatoryReauthManager*>(mandatory_reauth_manager_.get())
+      ->StartOptInFlow();
+  mandatory_reauth_manager_->OnUserAcceptedOptInPrompt();
+
+  EXPECT_FALSE(autofill_client_->GetPrefs()->GetBoolean(
+      prefs::kAutofillPaymentMethodsMandatoryReauth));
+  EXPECT_FALSE(autofill_client_->GetMandatoryReauthOptInPromptWasReshown());
+
+#if BUILDFLAG(IS_MAC) || BUILDFLAG(IS_WIN)
+  ON_CALL(*mock_device_authenticator_, AuthenticateWithMessage)
+#elif BUILDFLAG(IS_ANDROID)
+  ON_CALL(*mock_device_authenticator_, Authenticate)
+#endif  // BUILDFLAG(IS_MAC) || BUILDFLAG(IS_WIN)
+      .WillByDefault(
+          testing::WithArg<1>([](base::OnceCallback<void(bool)> callback) {
+            std::move(callback).Run(true);
+          }));
+
+  // We need to call `StartOptInFlow()` here to ensure the device
+  // authenticator gets set.
+  static_cast<MandatoryReauthManager*>(mandatory_reauth_manager_.get())
+      ->StartOptInFlow();
+  mandatory_reauth_manager_->OnUserAcceptedOptInPrompt();
+
+  EXPECT_TRUE(autofill_client_->GetPrefs()->GetBoolean(
+      prefs::kAutofillPaymentMethodsMandatoryReauth));
+  EXPECT_TRUE(autofill_client_->GetMandatoryReauthOptInPromptWasReshown());
+  EXPECT_EQ(autofill_client_->GetPrefs()->GetInteger(
+                prefs::kAutofillPaymentMethodsMandatoryReauthPromoShownCounter),
+            1);
+  EXPECT_TRUE(autofill_client_->GetPrefs()->GetUserPrefValue(
+      prefs::kAutofillPaymentMethodsMandatoryReauth));
+}
+
+// Test that the MandatoryReauthManager correctly handles the case where the
+// user cancels the re-auth prompt.
+TEST_F(MandatoryReauthManagerTest, OnUserCancelledOptInPrompt) {
+  EXPECT_FALSE(autofill_client_->GetPrefs()->GetUserPrefValue(
+      prefs::kAutofillPaymentMethodsMandatoryReauth));
+
+  mandatory_reauth_manager_->OnUserCancelledOptInPrompt();
+
+  EXPECT_TRUE(autofill_client_->GetPrefs()->GetUserPrefValue(
+      prefs::kAutofillPaymentMethodsMandatoryReauth));
+  EXPECT_FALSE(autofill_client_->GetPrefs()->GetBoolean(
+      prefs::kAutofillPaymentMethodsMandatoryReauth));
+}
+
+// Test that the MandatoryReauthManager correctly handles the case where the
+// user closed the re-auth prompt.
+TEST_F(MandatoryReauthManagerTest, OnUserClosedOptInPrompt) {
+  EXPECT_EQ(autofill_client_->GetPrefs()->GetInteger(
+                prefs::kAutofillPaymentMethodsMandatoryReauthPromoShownCounter),
+            0);
+
+  mandatory_reauth_manager_->OnUserClosedOptInPrompt();
+
+  EXPECT_EQ(autofill_client_->GetPrefs()->GetInteger(
+                prefs::kAutofillPaymentMethodsMandatoryReauthPromoShownCounter),
+            1);
+}
+
+}  // namespace autofill::payments
diff --git a/components/autofill/core/browser/payments/test/mock_mandatory_reauth_manager.cc b/components/autofill/core/browser/payments/test/mock_mandatory_reauth_manager.cc
new file mode 100644
index 0000000..6bb7f224
--- /dev/null
+++ b/components/autofill/core/browser/payments/test/mock_mandatory_reauth_manager.cc
@@ -0,0 +1,14 @@
+// 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 "components/autofill/core/browser/payments/test/mock_mandatory_reauth_manager.h"
+
+namespace autofill::payments {
+
+MockMandatoryReauthManager::MockMandatoryReauthManager()
+    : MandatoryReauthManager(nullptr) {}
+
+MockMandatoryReauthManager::~MockMandatoryReauthManager() = default;
+
+}  // namespace autofill::payments
diff --git a/components/autofill/core/browser/payments/test/mock_mandatory_reauth_manager.h b/components/autofill/core/browser/payments/test/mock_mandatory_reauth_manager.h
new file mode 100644
index 0000000..6093608
--- /dev/null
+++ b/components/autofill/core/browser/payments/test/mock_mandatory_reauth_manager.h
@@ -0,0 +1,36 @@
+// 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 COMPONENTS_AUTOFILL_CORE_BROWSER_PAYMENTS_TEST_MOCK_MANDATORY_REAUTH_MANAGER_H_
+#define COMPONENTS_AUTOFILL_CORE_BROWSER_PAYMENTS_TEST_MOCK_MANDATORY_REAUTH_MANAGER_H_
+
+#include "components/autofill/core/browser/payments/mandatory_reauth_manager.h"
+
+#include "testing/gmock/include/gmock/gmock.h"
+
+namespace autofill::payments {
+
+class MockMandatoryReauthManager : public MandatoryReauthManager {
+ public:
+  MockMandatoryReauthManager();
+  ~MockMandatoryReauthManager() override;
+
+  MOCK_METHOD(bool,
+              ShouldOfferOptin,
+              ((const CreditCard&),
+               (const absl::optional<
+                   absl::variant<FormDataImporter::CardGuid,
+                                 FormDataImporter::CardLastFourDigits>>&),
+               (autofill::FormDataImporter::CreditCardImportType)),
+              (override));
+  MOCK_METHOD(void, StartOptInFlow, (), (override));
+  MOCK_METHOD(void, OnUserAcceptedOptInPrompt, (), (override));
+  MOCK_METHOD(void, OnOptInAuthenticationStepCompleted, (bool), (override));
+  MOCK_METHOD(void, OnUserCancelledOptInPrompt, (), (override));
+  MOCK_METHOD(void, OnUserClosedOptInPrompt, (), (override));
+};
+
+}  // namespace autofill::payments
+
+#endif  // COMPONENTS_AUTOFILL_CORE_BROWSER_PAYMENTS_TEST_MOCK_MANDATORY_REAUTH_MANAGER_H_
diff --git a/components/autofill/core/browser/test_autofill_client.h b/components/autofill/core/browser/test_autofill_client.h
index 341a3db..80771398 100644
--- a/components/autofill/core/browser/test_autofill_client.h
+++ b/components/autofill/core/browser/test_autofill_client.h
@@ -464,6 +464,25 @@
 #endif  // BUILDFLAG(IS_MAC) || BUILDFLAG(IS_WIN) || BUILDFLAG(IS_ANDROID)
   }
 
+  void ShowMandatoryReauthOptInPrompt(
+      base::OnceClosure accept_mandatory_reauth_callback,
+      base::OnceClosure cancel_mandatory_reauth_callback,
+      base::RepeatingClosure close_mandatory_reauth_callback) override {
+    mandatory_reauth_opt_in_prompt_was_shown_ = true;
+  }
+
+  bool GetMandatoryReauthOptInPromptWasShown() {
+    return mandatory_reauth_opt_in_prompt_was_shown_;
+  }
+
+  void ShowMandatoryReauthOptInConfirmation() override {
+    mandatory_reauth_opt_in_prompt_was_reshown_ = true;
+  }
+
+  bool GetMandatoryReauthOptInPromptWasReshown() {
+    return mandatory_reauth_opt_in_prompt_was_reshown_;
+  }
+
   void LoadRiskData(
       base::OnceCallback<void(const std::string&)> callback) override {
     std::move(callback).Run("some risk data");
@@ -721,6 +740,11 @@
   // otherwise.
   bool offer_to_save_iban_bubble_was_shown_ = false;
 
+  // Populated if mandatory re-auth opt-in was offered, or re-offered,
+  // respectively.
+  bool mandatory_reauth_opt_in_prompt_was_shown_ = false;
+  bool mandatory_reauth_opt_in_prompt_was_reshown_ = false;
+
   std::vector<std::string> migration_card_selection_;
 
   // A mock translate driver which provides the language state.
diff --git a/components/autofill_payments_strings.grdp b/components/autofill_payments_strings.grdp
index 30bf59d..1e55e2af 100644
--- a/components/autofill_payments_strings.grdp
+++ b/components/autofill_payments_strings.grdp
@@ -759,16 +759,16 @@
       This is the <ph name="NUMBER_OF_DIGITS">$1<ex>3</ex></ph>-digit code on the <ph name="SIDE_OF_CARD">$2<ex>back of your card</ex></ph>
     </message>
   <message name="IDS_AUTOFILL_CARD_UNMASK_OTP_INPUT_DIALOG_TITLE" desc="The title for the dialog that lets the user type in the OTP they received on Desktop. This message is shown right next to the logo for the dialog, and it lets the users know the action that is required in this dialog, which is to enter their verification code (OTP).">
-    Enter verification code
+    Enter code
   </message>
   <message name="IDS_AUTOFILL_CARD_UNMASK_OTP_INPUT_DIALOG_FOOTER_MESSAGE" desc="The footer message for the dialog that lets the user type in the OTP they received on Desktop. This message instructs the user on what to do if they can't find their OTP code, for example if the OTP code was sent by email but it never came. It contains a link that comes from IDS_AUTOFILL_CARD_UNMASK_OTP_INPUT_DIALOG_NEW_CODE_MESSAGE which allows them to get a new code sent if clicked.">
-    Can't find your code? <ph name="IDS_AUTOFILL_CARD_UNMASK_OTP_INPUT_DIALOG_NEW_CODE_MESSAGE">$1<ex>Get new code</ex></ph>
+    Didn't receive your code? <ph name="IDS_AUTOFILL_CARD_UNMASK_OTP_INPUT_DIALOG_NEW_CODE_MESSAGE">$1<ex>Get new code</ex></ph>
   </message>
   <message name="IDS_AUTOFILL_CARD_UNMASK_OTP_INPUT_DIALOG_NEW_CODE_MESSAGE" desc="The second sentence of the footer message for the dialog that lets the user type in the OTP they received on Desktop. The entire footer message is for when the user can't find his OTP, for example if it was sent by email but the email never came. This is shown as a link that the user can click on to get a new OTP code, and will be substituted into IDS_AUTOFILL_CARD_UNMASK_OTP_INPUT_DIALOG_FOOTER_MESSAGE.">
     Get new code
   </message>
   <message name="IDS_AUTOFILL_CARD_UNMASK_OTP_INPUT_DIALOG_TEXTFIELD_PLACEHOLDER_MESSAGE" desc="The textfield placeholder text for the dialog that lets the user type in the OTP they received on Desktop. This message is shown when the textfield is empty, and it lets the user know the number of digits required for their OTP.">
-    Enter <ph name="NUMBER_OF_DIGITS">$1<ex>6</ex></ph>-digit code
+    Enter <ph name="NUMBER_OF_DIGITS">$1<ex>6</ex></ph>-digit verification code
   </message>
   <message name="IDS_AUTOFILL_CARD_UNMASK_OTP_INPUT_DIALOG_PENDING_MESSAGE" desc="The textfield placeholder text for the dialog that lets the user type in the OTP they received on Desktop. This message is shown after the user submits an OTP, and the dialog goes into pending state. It informs the user that we are currently verifying that the OTP they typed in is correct.">
     Verifying code...
diff --git a/components/autofill_payments_strings_grdp/IDS_AUTOFILL_CARD_UNMASK_OTP_INPUT_DIALOG_FOOTER_MESSAGE.png.sha1 b/components/autofill_payments_strings_grdp/IDS_AUTOFILL_CARD_UNMASK_OTP_INPUT_DIALOG_FOOTER_MESSAGE.png.sha1
index a1a94fc..a2869cdc 100644
--- a/components/autofill_payments_strings_grdp/IDS_AUTOFILL_CARD_UNMASK_OTP_INPUT_DIALOG_FOOTER_MESSAGE.png.sha1
+++ b/components/autofill_payments_strings_grdp/IDS_AUTOFILL_CARD_UNMASK_OTP_INPUT_DIALOG_FOOTER_MESSAGE.png.sha1
@@ -1 +1 @@
-8f23ad144de070d65c33bf9c76131f307d9001a7
\ No newline at end of file
+06fc7c2b8b0f8dcd890f0c298766119abe20e150
\ No newline at end of file
diff --git a/components/autofill_payments_strings_grdp/IDS_AUTOFILL_CARD_UNMASK_OTP_INPUT_DIALOG_TEXTFIELD_PLACEHOLDER_MESSAGE.png.sha1 b/components/autofill_payments_strings_grdp/IDS_AUTOFILL_CARD_UNMASK_OTP_INPUT_DIALOG_TEXTFIELD_PLACEHOLDER_MESSAGE.png.sha1
index a1a94fc..a2869cdc 100644
--- a/components/autofill_payments_strings_grdp/IDS_AUTOFILL_CARD_UNMASK_OTP_INPUT_DIALOG_TEXTFIELD_PLACEHOLDER_MESSAGE.png.sha1
+++ b/components/autofill_payments_strings_grdp/IDS_AUTOFILL_CARD_UNMASK_OTP_INPUT_DIALOG_TEXTFIELD_PLACEHOLDER_MESSAGE.png.sha1
@@ -1 +1 @@
-8f23ad144de070d65c33bf9c76131f307d9001a7
\ No newline at end of file
+06fc7c2b8b0f8dcd890f0c298766119abe20e150
\ No newline at end of file
diff --git a/components/autofill_payments_strings_grdp/IDS_AUTOFILL_CARD_UNMASK_OTP_INPUT_DIALOG_TITLE.png.sha1 b/components/autofill_payments_strings_grdp/IDS_AUTOFILL_CARD_UNMASK_OTP_INPUT_DIALOG_TITLE.png.sha1
index a1a94fc..a2869cdc 100644
--- a/components/autofill_payments_strings_grdp/IDS_AUTOFILL_CARD_UNMASK_OTP_INPUT_DIALOG_TITLE.png.sha1
+++ b/components/autofill_payments_strings_grdp/IDS_AUTOFILL_CARD_UNMASK_OTP_INPUT_DIALOG_TITLE.png.sha1
@@ -1 +1 @@
-8f23ad144de070d65c33bf9c76131f307d9001a7
\ No newline at end of file
+06fc7c2b8b0f8dcd890f0c298766119abe20e150
\ No newline at end of file
diff --git a/components/background_task_scheduler/android/java/src/org/chromium/components/background_task_scheduler/TaskInfo.java b/components/background_task_scheduler/android/java/src/org/chromium/components/background_task_scheduler/TaskInfo.java
index cbe2b5b..85c8bc1 100644
--- a/components/background_task_scheduler/android/java/src/org/chromium/components/background_task_scheduler/TaskInfo.java
+++ b/components/background_task_scheduler/android/java/src/org/chromium/components/background_task_scheduler/TaskInfo.java
@@ -19,7 +19,6 @@
  */
 public class TaskInfo {
     public static final String SERIALIZED_TASK_EXTRAS = "serialized_task_extras";
-    private static final String TAG = "BkgrdTaskInfo";
 
     /**
      * Common interface for all types of task information.
diff --git a/components/background_task_scheduler/internal/android/java/src/org/chromium/components/background_task_scheduler/internal/BackgroundTaskSchedulerImpl.java b/components/background_task_scheduler/internal/android/java/src/org/chromium/components/background_task_scheduler/internal/BackgroundTaskSchedulerImpl.java
index 8dcdb18..b86a20a 100644
--- a/components/background_task_scheduler/internal/android/java/src/org/chromium/components/background_task_scheduler/internal/BackgroundTaskSchedulerImpl.java
+++ b/components/background_task_scheduler/internal/android/java/src/org/chromium/components/background_task_scheduler/internal/BackgroundTaskSchedulerImpl.java
@@ -19,7 +19,6 @@
  * To get an instance of this class, use {@link BackgroundTaskSchedulerFactory#getScheduler()}.
  */
 class BackgroundTaskSchedulerImpl implements BackgroundTaskScheduler {
-    private static final String TAG = "BkgrdTaskScheduler";
     private static final String SWITCH_IGNORE_BACKGROUND_TASKS = "ignore-background-tasks";
 
     private final BackgroundTaskSchedulerDelegate mSchedulerDelegate;
diff --git a/components/browser_ui/media/android/java/src/org/chromium/components/browser_ui/media/MediaNotificationManager.java b/components/browser_ui/media/android/java/src/org/chromium/components/browser_ui/media/MediaNotificationManager.java
index 80e6eb5..27627ae 100644
--- a/components/browser_ui/media/android/java/src/org/chromium/components/browser_ui/media/MediaNotificationManager.java
+++ b/components/browser_ui/media/android/java/src/org/chromium/components/browser_ui/media/MediaNotificationManager.java
@@ -15,8 +15,6 @@
  * Each notification is associated with a different {@link MediaNotificationController}.
  */
 public class MediaNotificationManager {
-    private static final String TAG = "MediaNotification";
-
     // Maps the notification ids to their corresponding notification managers.
     private static SparseArray<MediaNotificationController> sControllers;
 
diff --git a/components/browser_ui/media/android/java/src/org/chromium/components/browser_ui/media/MediaSessionHelper.java b/components/browser_ui/media/android/java/src/org/chromium/components/browser_ui/media/MediaSessionHelper.java
index fb3e245..2598a7a 100644
--- a/components/browser_ui/media/android/java/src/org/chromium/components/browser_ui/media/MediaSessionHelper.java
+++ b/components/browser_ui/media/android/java/src/org/chromium/components/browser_ui/media/MediaSessionHelper.java
@@ -43,8 +43,6 @@
  * WebContents to a delegate (ultimately, to {@link MediaNotificationController}).
  */
 public class MediaSessionHelper implements MediaImageCallback {
-    private static final String TAG = "MediaSession";
-
     private static final String UNICODE_PLAY_CHARACTER = "\u25B6";
     @VisibleForTesting
     public static final int HIDE_NOTIFICATION_DELAY_MILLIS = 2500;
diff --git a/components/browser_ui/modaldialog/android/java/src/org/chromium/components/browser_ui/modaldialog/ModalDialogView.java b/components/browser_ui/modaldialog/android/java/src/org/chromium/components/browser_ui/modaldialog/ModalDialogView.java
index df5308f..481464b 100644
--- a/components/browser_ui/modaldialog/android/java/src/org/chromium/components/browser_ui/modaldialog/ModalDialogView.java
+++ b/components/browser_ui/modaldialog/android/java/src/org/chromium/components/browser_ui/modaldialog/ModalDialogView.java
@@ -34,7 +34,6 @@
  * Generic dialog view for app modal or tab modal alert dialogs.
  */
 public class ModalDialogView extends BoundedLinearLayout implements View.OnClickListener {
-    private static final String TAG = "ModalDialogView";
     private static final String UMA_SECURITY_FILTERED_TOUCH_RESULT =
             "Android.ModalDialog.SecurityFilteredTouchResult";
 
diff --git a/components/browser_ui/site_settings/android/java/src/org/chromium/components/browser_ui/site_settings/WebsitePreference.java b/components/browser_ui/site_settings/android/java/src/org/chromium/components/browser_ui/site_settings/WebsitePreference.java
index 9ed179fd..54c8276 100644
--- a/components/browser_ui/site_settings/android/java/src/org/chromium/components/browser_ui/site_settings/WebsitePreference.java
+++ b/components/browser_ui/site_settings/android/java/src/org/chromium/components/browser_ui/site_settings/WebsitePreference.java
@@ -35,7 +35,6 @@
     private final SiteSettingsCategory mCategory;
 
     // TODO(crbug.com/1076571): Move these constants to dimens.xml
-    private static final int FAVICON_PADDING_DP = 4;
     private static final int TEXT_SIZE_SP = 13;
 
     // Whether the favicon has been fetched already.
diff --git a/components/browser_ui/widget/android/java/src/org/chromium/components/browser_ui/widget/RoundedCornerImageView.java b/components/browser_ui/widget/android/java/src/org/chromium/components/browser_ui/widget/RoundedCornerImageView.java
index bebff2a0..54cdd80d 100644
--- a/components/browser_ui/widget/android/java/src/org/chromium/components/browser_ui/widget/RoundedCornerImageView.java
+++ b/components/browser_ui/widget/android/java/src/org/chromium/components/browser_ui/widget/RoundedCornerImageView.java
@@ -47,7 +47,6 @@
 
     private final Paint mRoundedBackgroundPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
     private Paint mRoundedContentPaint;
-    private final Matrix mScaleMatrix = new Matrix();
     private boolean mRoundCorners;
     // True, if constructor had a chance to run.
     // This is needed, because ImageView's constructor may trigger updates on our end
diff --git a/components/browsing_topics/browsing_topics_service_impl.cc b/components/browsing_topics/browsing_topics_service_impl.cc
index 0f58399..8d7eeed 100644
--- a/components/browsing_topics/browsing_topics_service_impl.cc
+++ b/components/browsing_topics/browsing_topics_service_impl.cc
@@ -436,9 +436,8 @@
 
     auto result_topic = blink::mojom::EpochTopic::New();
     result_topic->topic = candidate_topic.topic().value();
-    result_topic->config_version = base::StrCat(
-        {"chrome.", base::NumberToString(
-                        blink::features::kBrowsingTopicsConfigVersion.Get())});
+    result_topic->config_version =
+        base::StrCat({"chrome.", base::NumberToString(CurrentConfigVersion())});
     result_topic->model_version =
         base::NumberToString(candidate_topic.model_version());
     result_topic->taxonomy_version =
diff --git a/components/browsing_topics/browsing_topics_state.cc b/components/browsing_topics/browsing_topics_state.cc
index 49703871..7487c130 100644
--- a/components/browsing_topics/browsing_topics_state.cc
+++ b/components/browsing_topics/browsing_topics_state.cc
@@ -246,8 +246,7 @@
   std::string hex_encoded_hmac_key = base::HexEncode(hmac_key_);
   result_dict.Set(kHexEncodedHmacKeyNameKey, base::HexEncode(hmac_key_));
 
-  result_dict.Set(kConfigVersionNameKey,
-                  blink::features::kBrowsingTopicsConfigVersion.Get());
+  result_dict.Set(kConfigVersionNameKey, CurrentConfigVersion());
 
   return result_dict;
 }
@@ -322,8 +321,7 @@
     return ParseResult{.success = false, .should_save_state_to_file = true};
 
   // If the config is has been updated, start with a fresh `epoch_`.
-  if (*config_version_in_storage !=
-      blink::features::kBrowsingTopicsConfigVersion.Get()) {
+  if (*config_version_in_storage != CurrentConfigVersion()) {
     return ParseResult{.success = true, .should_save_state_to_file = true};
   }
 
diff --git a/components/browsing_topics/browsing_topics_state_unittest.cc b/components/browsing_topics/browsing_topics_state_unittest.cc
index 351a500..31ece08 100644
--- a/components/browsing_topics/browsing_topics_state_unittest.cc
+++ b/components/browsing_topics/browsing_topics_state_unittest.cc
@@ -69,10 +69,6 @@
   BrowsingTopicsStateTest()
       : task_environment_(new base::test::TaskEnvironment(
             base::test::TaskEnvironment::TimeSource::MOCK_TIME)) {
-    feature_list_.InitAndEnableFeatureWithParameters(
-        blink::features::kBrowsingTopicsParameters,
-        {{"config_version", "123"}});
-
     OverrideHmacKeyForTesting(kTestKey);
 
     EXPECT_TRUE(temp_dir_.CreateUniqueTempDir());
@@ -165,7 +161,7 @@
   EXPECT_TRUE(base::PathExists(TestFilePath()));
   EXPECT_EQ(
       GetTestFileContent(),
-      "{\"config_version\": 123,\"epochs\": [ ],\"hex_encoded_hmac_key\": "
+      "{\"config_version\": 1,\"epochs\": [ ],\"hex_encoded_hmac_key\": "
       "\"0100000000000000000000000000000000000000000000000000000000000000\","
       "\"next_scheduled_calculation_time\": \"0\"}");
 }
@@ -193,7 +189,7 @@
   EXPECT_FALSE(state.HasScheduledSaveForTesting());
 
   std::string expected_content = base::StrCat(
-      {"{\"config_version\": 123,\"epochs\": [ ],\"hex_encoded_hmac_key\": "
+      {"{\"config_version\": 1,\"epochs\": [ ],\"hex_encoded_hmac_key\": "
        "\"0100000000000000000000000000000000000000000000000000000000000000"
        "\",\"next_scheduled_calculation_time\": \"",
        base::NumberToString(state.next_scheduled_calculation_time()
@@ -389,7 +385,7 @@
   CreateOrOverrideTestFile(std::move(epochs),
                            /*next_scheduled_calculation_time=*/kTime2,
                            /*hex_encoded_hmac_key=*/"123",
-                           /*config_version=*/123);
+                           /*config_version=*/1);
 
   BrowsingTopicsState state(temp_dir_.GetPath(), base::DoNothing());
   task_environment_->RunUntilIdle();
@@ -412,7 +408,7 @@
   CreateOrOverrideTestFile(std::move(epochs),
                            /*next_scheduled_calculation_time=*/kTime2,
                            /*hex_encoded_hmac_key=*/base::HexEncode(kTestKey2),
-                           /*config_version=*/123);
+                           /*config_version=*/1);
 
   BrowsingTopicsState state(temp_dir_.GetPath(), base::DoNothing());
   task_environment_->RunUntilIdle();
@@ -603,7 +599,7 @@
   EXPECT_TRUE(base::PathExists(TestFilePath()));
   EXPECT_EQ(
       GetTestFileContent(),
-      "{\"config_version\": 123,\"epochs\": [ ],\"hex_encoded_hmac_key\": "
+      "{\"config_version\": 1,\"epochs\": [ ],\"hex_encoded_hmac_key\": "
       "\"0100000000000000000000000000000000000000000000000000000000000000\","
       "\"next_scheduled_calculation_time\": \"0\"}");
 }
diff --git a/components/browsing_topics/common/common_types.cc b/components/browsing_topics/common/common_types.cc
index d563004..373dbb0 100644
--- a/components/browsing_topics/common/common_types.cc
+++ b/components/browsing_topics/common/common_types.cc
@@ -6,6 +6,10 @@
 
 namespace browsing_topics {
 
+ConfigVersion CurrentConfigVersion() {
+  return ConfigVersion::kDefault;
+}
+
 ApiUsageContextQueryResult::ApiUsageContextQueryResult() = default;
 
 ApiUsageContextQueryResult::ApiUsageContextQueryResult(
diff --git a/components/browsing_topics/common/common_types.h b/components/browsing_topics/common/common_types.h
index c5cd65aa..cb08636 100644
--- a/components/browsing_topics/common/common_types.h
+++ b/components/browsing_topics/common/common_types.h
@@ -17,6 +17,16 @@
 using HashedDomain = base::StrongAlias<class HashedHostTag, int64_t>;
 using Topic = base::StrongAlias<class TopicTag, int>;
 
+// Explicitly typed config version.
+enum ConfigVersion {
+  kDefault = 1,
+
+  kMaxValue = kDefault,
+};
+
+// Returns the current configuration version.
+COMPONENT_EXPORT(BROWSING_TOPICS_COMMON) ConfigVersion CurrentConfigVersion();
+
 // Represents the source of the caller.
 enum class ApiCallerSource {
   // The API usage is from document.browsingTopics().
diff --git a/components/browsing_topics/mojom/browsing_topics_internals.mojom b/components/browsing_topics/mojom/browsing_topics_internals.mojom
index 6eb8916..fd34d1fd 100644
--- a/components/browsing_topics/mojom/browsing_topics_internals.mojom
+++ b/components/browsing_topics/mojom/browsing_topics_internals.mojom
@@ -27,6 +27,18 @@
   // enabled.
   bool browsing_topics_bypass_ip_is_publicly_routable_check_enabled;
 
+  // Whether the deprecatedBrowsingTopics XHR attribute is allowed.
+  bool browsing_topics_xhr_enabled;
+
+  // Whether document.browsingTopics() is allowed.
+  bool browsing_topics_document_api_enabled;
+
+  // The configuration version derived from topics parameters.
+  int32 config_version;
+
+  // Whether the `BrowsingTopicsParameters` feature is enabled.
+  bool browsing_topics_parameters_enabled;
+
   // The number of epochs from where to calculate the topics to give to a
   // requesting contexts.
   int32 number_of_epochs_to_expose;
@@ -44,6 +56,11 @@
   // instead of one of the top topics.
   int32 use_random_topic_probability_percent;
 
+  // Maximum duration between when a epoch is calculated and when a site starts
+  // using that new epoch's topics. The time chosen is a per-site random point
+  // in time between [calculation time, calculation time + max duration).
+  mojo_base.mojom.TimeDelta max_epoch_introduction_delay;
+
   // How many epochs of API usage data (i.e. topics observations) will be based
   // off for the filtering of topics for a calling context.
   int32 number_of_epochs_of_observation_data_to_use_for_filtering;
@@ -62,13 +79,14 @@
   // load.
   int32 max_number_of_api_usage_context_domains_to_store_per_page_load;
 
-  // The configuration version applicable to all epochs.
-  int32 config_version;
-
   // The taxonomy version. This only affects the topics classification that
   // occurs during this browser session, and doesn't affect the pre-existing
   // epochs.
   int32 taxonomy_version;
+
+  // Comma separated Topic IDs to be blocked. Descendant topics of each blocked
+  // topic will be blocked as well.
+  string disabled_topics_list;
 };
 
 // Struct representing the state of one topic that will be displayed by WebUI.
diff --git a/components/components_chromium_strings.grd b/components/components_chromium_strings.grd
index 73c4c6a5..9780b57 100644
--- a/components/components_chromium_strings.grd
+++ b/components/components_chromium_strings.grd
@@ -218,6 +218,12 @@
           Applications &gt; System Preferences &gt; Network &gt; Advanced &gt; Proxies
           and deselect any proxies that have been selected.
         </message>
+        <message name="IDS_PAYMENTS_AUTOFILL_MANDATORY_REAUTH_PROMPT" desc="Message to be displayed to the user in the device authentication prompt when they click on the mandatory auth toggle on the Payments settings page, irrespective to enable or disable it. When mandatory auth toggle is enabled, we would authenticate the user before autofilling in the payment details. Note: MacOS has the wording 'Chromium is trying to' prepended onto the message during user auth.">
+          modify settings for filling payment methods.
+        </message>
+        <message name="IDS_PAYMENTS_AUTOFILL_EDIT_CARD_MANDATORY_REAUTH_PROMPT" desc="Message to be displayed to the user in the device authentication prompt when they try to edit a local card on the Payments settings page if the mandatory auth toggle is enabled. If the user auth was succesful, we would then show the edit card dialog. Note: MacOS has the wording 'Chromium is trying to' prepended onto the message during user auth.">
+          edit payment methods.
+        </message>
       </if>
       <!-- The ChromeOS version of this string is defined in //components/error_page_strings.grdp. -->
       <if expr="is_linux">
@@ -245,6 +251,12 @@
           LAN Settings
           and deselect the &quot;Use a proxy server for your LAN&quot; checkbox.
         </message>
+        <message name="IDS_PAYMENTS_AUTOFILL_MANDATORY_REAUTH_PROMPT" desc="Message to be displayed to the user in the device authentication prompt when they click on the mandatory auth toggle on the Payments settings page, irrespective to enable or disable it. When mandatory auth toggle is enabled, we would authenticate the user before autofilling in the payment details.">
+          Chromium is trying to modify settings for filling payment methods.
+        </message>
+        <message name="IDS_PAYMENTS_AUTOFILL_EDIT_CARD_MANDATORY_REAUTH_PROMPT" desc="Message to be displayed to the user in the device authentication prompt when they try to edit a local card on the Payments settings page if the mandatory auth toggle is enabled. If the user auth was succesful, we would then show the edit card dialog.">
+          Chromium is trying to edit payment methods.
+        </message>
       </if>
 
       <!-- About Flags UI -->
diff --git a/chrome/app/chromium_strings_grd/IDS_PAYMENTS_AUTOFILL_EDIT_CARD_MANDATORY_REAUTH_PROMPT.png.sha1 b/components/components_chromium_strings_grd/IDS_PAYMENTS_AUTOFILL_EDIT_CARD_MANDATORY_REAUTH_PROMPT.png.sha1
similarity index 100%
rename from chrome/app/chromium_strings_grd/IDS_PAYMENTS_AUTOFILL_EDIT_CARD_MANDATORY_REAUTH_PROMPT.png.sha1
rename to components/components_chromium_strings_grd/IDS_PAYMENTS_AUTOFILL_EDIT_CARD_MANDATORY_REAUTH_PROMPT.png.sha1
diff --git a/chrome/app/chromium_strings_grd/IDS_PAYMENTS_AUTOFILL_MANDATORY_REAUTH_PROMPT.png.sha1 b/components/components_chromium_strings_grd/IDS_PAYMENTS_AUTOFILL_MANDATORY_REAUTH_PROMPT.png.sha1
similarity index 100%
rename from chrome/app/chromium_strings_grd/IDS_PAYMENTS_AUTOFILL_MANDATORY_REAUTH_PROMPT.png.sha1
rename to components/components_chromium_strings_grd/IDS_PAYMENTS_AUTOFILL_MANDATORY_REAUTH_PROMPT.png.sha1
diff --git a/components/components_google_chrome_strings.grd b/components/components_google_chrome_strings.grd
index 05da7c7..25923c85 100644
--- a/components/components_google_chrome_strings.grd
+++ b/components/components_google_chrome_strings.grd
@@ -218,6 +218,12 @@
           Applications &gt; System Preferences &gt; Network &gt; Advanced &gt; Proxies
           and deselect any proxies that have been selected.
         </message>
+        <message name="IDS_PAYMENTS_AUTOFILL_MANDATORY_REAUTH_PROMPT" desc="Message to be displayed to the user in the device authentication prompt when they click on the mandatory auth toggle on the Payments settings page, irrespective to enable or disable it. When mandatory auth toggle is enabled, we would authenticate the user before autofilling in the payment details. Note: MacOS has the wording 'Google Chrome is trying to' prepended onto the message during user auth.">
+          modify settings for filling payment methods.
+        </message>
+        <message name="IDS_PAYMENTS_AUTOFILL_EDIT_CARD_MANDATORY_REAUTH_PROMPT" desc="Message to be displayed to the user in the device authentication prompt when they try to edit a local card on the Payments settings page if the mandatory auth toggle is enabled. If the user auth was succesful, we would then show the edit card dialog. Note: MacOS has the wording 'Google Chrome is trying to' prepended onto the message during user auth.">
+          edit payment methods.
+        </message>
       </if>
       <!-- The ChromeOS version of this string is defined in //components/error_page_strings.grdp. -->
       <if expr="is_linux">
@@ -245,6 +251,12 @@
           LAN Settings
           and deselect &quot;Use a proxy server for your LAN&quot;.
         </message>
+        <message name="IDS_PAYMENTS_AUTOFILL_MANDATORY_REAUTH_PROMPT" desc="Message to be displayed to the user in the device authentication prompt when they click on the mandatory auth toggle on the Payments settings page, irrespective to enable or disable it. When mandatory auth toggle is enabled, we would authenticate the user before autofilling in the payment details.">
+          Google Chrome is trying to modify settings for filling payment methods.
+        </message>
+        <message name="IDS_PAYMENTS_AUTOFILL_EDIT_CARD_MANDATORY_REAUTH_PROMPT" desc="Message to be displayed to the user in the device authentication prompt when they try to edit a local card on the Payments settings page if the mandatory auth toggle is enabled. If the user auth was succesful, we would then show the edit card dialog.">
+          Google Chrome is trying to edit payment methods.
+        </message>
       </if>
 
       <!-- About Flags UI -->
diff --git a/chrome/app/google_chrome_strings_grd/IDS_PAYMENTS_AUTOFILL_EDIT_CARD_MANDATORY_REAUTH_PROMPT.png.sha1 b/components/components_google_chrome_strings_grd/IDS_PAYMENTS_AUTOFILL_EDIT_CARD_MANDATORY_REAUTH_PROMPT.png.sha1
similarity index 100%
rename from chrome/app/google_chrome_strings_grd/IDS_PAYMENTS_AUTOFILL_EDIT_CARD_MANDATORY_REAUTH_PROMPT.png.sha1
rename to components/components_google_chrome_strings_grd/IDS_PAYMENTS_AUTOFILL_EDIT_CARD_MANDATORY_REAUTH_PROMPT.png.sha1
diff --git a/chrome/app/google_chrome_strings_grd/IDS_PAYMENTS_AUTOFILL_MANDATORY_REAUTH_PROMPT.png.sha1 b/components/components_google_chrome_strings_grd/IDS_PAYMENTS_AUTOFILL_MANDATORY_REAUTH_PROMPT.png.sha1
similarity index 100%
rename from chrome/app/google_chrome_strings_grd/IDS_PAYMENTS_AUTOFILL_MANDATORY_REAUTH_PROMPT.png.sha1
rename to components/components_google_chrome_strings_grd/IDS_PAYMENTS_AUTOFILL_MANDATORY_REAUTH_PROMPT.png.sha1
diff --git a/components/content_relationship_verification/android/java/src/org/chromium/components/content_relationship_verification/OriginVerificationScheduler.java b/components/content_relationship_verification/android/java/src/org/chromium/components/content_relationship_verification/OriginVerificationScheduler.java
index 7bf061e..80aaf43 100644
--- a/components/content_relationship_verification/android/java/src/org/chromium/components/content_relationship_verification/OriginVerificationScheduler.java
+++ b/components/content_relationship_verification/android/java/src/org/chromium/components/content_relationship_verification/OriginVerificationScheduler.java
@@ -22,8 +22,6 @@
  * website will only performed at most once.
  */
 public class OriginVerificationScheduler {
-    private static final String TAG = "OriginVerification";
-
     private static final String HTTP_SCHEME = "http";
     private static final String HTTPS_SCHEME = "https";
 
diff --git a/components/crash/android/java/src/org/chromium/components/crash/browser/PackagePaths.java b/components/crash/android/java/src/org/chromium/components/crash/browser/PackagePaths.java
index f3eb0e4..b74cc2f 100644
--- a/components/crash/android/java/src/org/chromium/components/crash/browser/PackagePaths.java
+++ b/components/crash/android/java/src/org/chromium/components/crash/browser/PackagePaths.java
@@ -20,8 +20,6 @@
  * This class builds paths for the Chrome package.
  */
 public abstract class PackagePaths {
-    private static final String TAG = "PackagePaths";
-
     // Prevent instantiation.
     private PackagePaths() {}
 
diff --git a/components/cronet/android/fake/java/org/chromium/net/test/FakeUrlRequest.java b/components/cronet/android/fake/java/org/chromium/net/test/FakeUrlRequest.java
index 0e16b341..98562c7 100644
--- a/components/cronet/android/fake/java/org/chromium/net/test/FakeUrlRequest.java
+++ b/components/cronet/android/fake/java/org/chromium/net/test/FakeUrlRequest.java
@@ -48,7 +48,6 @@
  * testing Cronet usage on Android.
  */
 final class FakeUrlRequest extends UrlRequestBase {
-    private static final int DEFAULT_UPLOAD_BUFFER_SIZE = 8192;
     // Used for logging errors.
     private static final String TAG = FakeUrlRequest.class.getSimpleName();
     // Callback used to report responses to the client.
diff --git a/components/cronet/android/test/javatests/src/org/chromium/net/CronetLoggerTestRule.java b/components/cronet/android/test/javatests/src/org/chromium/net/CronetLoggerTestRule.java
index 81508bd..7e19e4ed 100644
--- a/components/cronet/android/test/javatests/src/org/chromium/net/CronetLoggerTestRule.java
+++ b/components/cronet/android/test/javatests/src/org/chromium/net/CronetLoggerTestRule.java
@@ -18,8 +18,6 @@
  * @param <T> The actual type of the class extending CronetLogger.
  */
 public class CronetLoggerTestRule<T extends CronetLogger> implements TestRule {
-    private static final String TAG = CronetLoggerTestRule.class.getSimpleName();
-
     private Class<T> mTestLoggerClazz;
 
     // Expose the fake logger to the test.
diff --git a/components/cronet/android/test/javatests/src/org/chromium/net/CronetUrlRequestContextTest.java b/components/cronet/android/test/javatests/src/org/chromium/net/CronetUrlRequestContextTest.java
index 8d34111f..16e5781 100644
--- a/components/cronet/android/test/javatests/src/org/chromium/net/CronetUrlRequestContextTest.java
+++ b/components/cronet/android/test/javatests/src/org/chromium/net/CronetUrlRequestContextTest.java
@@ -62,15 +62,12 @@
     @Rule
     public final CronetTestRule mTestRule = CronetTestRule.withManualEngineStartup();
 
-    private static final String TAG = CronetUrlRequestContextTest.class.getSimpleName();
-
     // URLs used for tests.
     private static final String MOCK_CRONET_TEST_FAILED_URL =
             "http://mock.failed.request/-2";
     private static final String MOCK_CRONET_TEST_SUCCESS_URL =
             "http://mock.http/success.txt";
     private static final int MAX_FILE_SIZE = 1000000000;
-    private static final int NUM_EVENT_FILES = 10;
 
     private EmbeddedTestServer mTestServer;
     private String mUrl;
diff --git a/components/cronet/android/test/javatests/src/org/chromium/net/MockCertVerifierTest.java b/components/cronet/android/test/javatests/src/org/chromium/net/MockCertVerifierTest.java
index 72c9c19..46476f2e 100644
--- a/components/cronet/android/test/javatests/src/org/chromium/net/MockCertVerifierTest.java
+++ b/components/cronet/android/test/javatests/src/org/chromium/net/MockCertVerifierTest.java
@@ -22,8 +22,6 @@
  */
 @RunWith(AndroidJUnit4.class)
 public class MockCertVerifierTest {
-    private static final String TAG = MockCertVerifierTest.class.getSimpleName();
-
     @Rule
     public final CronetTestRule mTestRule = CronetTestRule.withManualEngineStartup();
 
diff --git a/components/cronet/android/test/javatests/src/org/chromium/net/QuicTest.java b/components/cronet/android/test/javatests/src/org/chromium/net/QuicTest.java
index 3ebed46a..b9e5049 100644
--- a/components/cronet/android/test/javatests/src/org/chromium/net/QuicTest.java
+++ b/components/cronet/android/test/javatests/src/org/chromium/net/QuicTest.java
@@ -36,8 +36,6 @@
     @Rule
     public final CronetTestRule mTestRule = CronetTestRule.withManualEngineStartup();
 
-    private static final String TAG = QuicTest.class.getSimpleName();
-
     @Before
     public void setUp() throws Exception {
         // Load library first, since we need the Quic test server's URL.
diff --git a/components/crx_file/crx_creator.cc b/components/crx_file/crx_creator.cc
index b7c80863..913d935 100644
--- a/components/crx_file/crx_creator.cc
+++ b/components/crx_file/crx_creator.cc
@@ -6,6 +6,7 @@
 
 #include "base/files/file.h"
 #include "base/files/file_path.h"
+#include "base/logging.h"
 #include "components/crx_file/crx3.pb.h"
 #include "components/crx_file/crx_file.h"
 #include "crypto/rsa_private_key.h"
@@ -139,7 +140,10 @@
     crypto::RSAPrivateKey* signing_key,
     const std::string& verified_contents) {
   CrxFileHeader header;
-  base::File file(zip_path, base::File::FLAG_OPEN | base::File::FLAG_READ);
+  base::File file(zip_path, base::File::FLAG_OPEN | base::File::FLAG_READ |
+                                base::File::FLAG_WIN_SHARE_DELETE);
+  PLOG_IF(ERROR, !file.IsValid())
+      << "Failed to open " << zip_path << ": " << file.error_details();
   const CreatorResult signing_result =
       SignArchiveAndCreateHeader(output_path, &file, signing_key, &header);
   if (signing_result != CreatorResult::OK)
diff --git a/components/device_reauth/device_authenticator.h b/components/device_reauth/device_authenticator.h
index 759f3724..54a2bf50 100644
--- a/components/device_reauth/device_authenticator.h
+++ b/components/device_reauth/device_authenticator.h
@@ -65,7 +65,11 @@
   // and re-auth is triggered.
   kVirtualCardAutofill = 11,
 
-  kMaxValue = kVirtualCardAutofill,
+  // The prompt displayed while we were trying to opt the user into payments
+  // autofill re-auth.
+  kPaymentsAutofillOptIn = 12,
+
+  kMaxValue = kPaymentsAutofillOptIn,
 };
 
 // This interface encapsulates operations related to biometric authentication.
diff --git a/components/embedder_support/android/java/src/org/chromium/components/embedder_support/util/UrlUtilities.java b/components/embedder_support/android/java/src/org/chromium/components/embedder_support/util/UrlUtilities.java
index 5d4fe90..a30a55ba 100644
--- a/components/embedder_support/android/java/src/org/chromium/components/embedder_support/util/UrlUtilities.java
+++ b/components/embedder_support/android/java/src/org/chromium/components/embedder_support/util/UrlUtilities.java
@@ -35,8 +35,6 @@
  */
 @JNINamespace("embedder_support")
 public class UrlUtilities {
-    private static final String TAG = "UrlUtilities";
-
     /** Regular expression for prefixes to strip from publisher hostnames. */
     private static final Pattern HOSTNAME_PREFIX_PATTERN =
             Pattern.compile("^(www[0-9]*|web|ftp|wap|home|mobile|amp)\\.");
diff --git a/components/embedder_support/android/java/src/org/chromium/components/embedder_support/view/ContentView.java b/components/embedder_support/android/java/src/org/chromium/components/embedder_support/view/ContentView.java
index 0e909aa..17482e03 100644
--- a/components/embedder_support/android/java/src/org/chromium/components/embedder_support/view/ContentView.java
+++ b/components/embedder_support/android/java/src/org/chromium/components/embedder_support/view/ContentView.java
@@ -51,8 +51,6 @@
         implements ViewEventSink.InternalAccessDelegate, SmartClipProvider,
                    OnHierarchyChangeListener, OnSystemUiVisibilityChangeListener, OnDragListener,
                    DragEventDispatchDestination {
-    private static final String TAG = "ContentView";
-
     // Default value to signal that the ContentView's size need not be overridden.
     public static final int DEFAULT_MEASURE_SPEC =
             MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
diff --git a/components/exo/client_controlled_shell_surface.cc b/components/exo/client_controlled_shell_surface.cc
index adcf83a3..2b612d0 100644
--- a/components/exo/client_controlled_shell_surface.cc
+++ b/components/exo/client_controlled_shell_surface.cc
@@ -515,13 +515,6 @@
   UpdateCornerRadius();
 }
 
-void ClientControlledShellSurface::SetTopInset(int height) {
-  TRACE_EVENT1("exo", "ClientControlledShellSurface::SetTopInset", "height",
-               height);
-
-  pending_top_inset_height_ = height;
-}
-
 void ClientControlledShellSurface::OnWindowStateChangeEvent(
     chromeos::WindowStateType current_state,
     chromeos::WindowStateType next_state) {
diff --git a/components/exo/client_controlled_shell_surface.h b/components/exo/client_controlled_shell_surface.h
index 6b54e42..e453692 100644
--- a/components/exo/client_controlled_shell_surface.h
+++ b/components/exo/client_controlled_shell_surface.h
@@ -128,9 +128,6 @@
   // otherwise committed by OnPostWidgetCommit().
   void CommitPendingScale();
 
-  // Set top inset for surface.
-  void SetTopInset(int height);
-
   // Sends the request to change the zoom level to the client.
   void ChangeZoomLevel(ZoomChange change);
 
@@ -301,9 +298,6 @@
       const gfx::Rect& window_bounds,
       chromeos::WindowStateType window_state) const;
 
-  int top_inset_height_ = 0;
-  int pending_top_inset_height_ = 0;
-
   double scale_ = 1.0;
   // The pending scale is initialized to 0.0 to indicate that the scale is not
   // yet initialized.
diff --git a/components/exo/shell_surface_base.cc b/components/exo/shell_surface_base.cc
index 5901ad8..7e23113 100644
--- a/components/exo/shell_surface_base.cc
+++ b/components/exo/shell_surface_base.cc
@@ -429,6 +429,11 @@
   non_system_modal_window_was_active_ = non_system_modal_window_was_active;
 }
 
+void ShellSurfaceBase::SetTopInset(int height) {
+  TRACE_EVENT1("exo", "ShellSurfaceBase::SetTopInset", "height", height);
+  pending_top_inset_height_ = height;
+}
+
 void ShellSurfaceBase::SetBoundsForShadows(
     const absl::optional<gfx::Rect>& shadow_bounds) {
   if (shadow_bounds_ != shadow_bounds) {
@@ -652,6 +657,21 @@
   }
 }
 
+void ShellSurfaceBase::UpdateTopInset() {
+  if (!widget_) {
+    // It is possible to get here before the widget has actually been created.
+    // The state will be set once the widget gets created.
+    return;
+  }
+
+  // Apply new top inset height.
+  if (pending_top_inset_height_ != top_inset_height_) {
+    widget_->GetNativeWindow()->SetProperty(aura::client::kTopViewInset,
+                                            pending_top_inset_height_);
+    top_inset_height_ = pending_top_inset_height_;
+  }
+}
+
 void ShellSurfaceBase::SetChildAxTreeId(ui::AXTreeID child_ax_tree_id) {
   GetViewAccessibility().OverrideChildTreeID(child_ax_tree_id);
   this->NotifyAccessibilityEvent(ax::mojom::Event::kChildrenChanged, false);
@@ -1585,13 +1605,15 @@
   // As setting the pinned mode may have come in earlier we apply it now.
   UpdatePinned();
 
+  UpdateTopInset();
+
   aura::Window* window = widget_->GetNativeWindow();
   window->SetName(base::StringPrintf("ExoShellSurface-%d", shell_id++));
   window->AddChild(host_window());
   window->SetEventTargetingPolicy(
       aura::EventTargetingPolicy::kTargetAndDescendants);
   if (is_menu_) {
-    // Sets menu config id to kGroupintPropertyKey if the window is menu.
+    // Sets menu config id to kGroupingPropertyKey if the window is menu.
     window->SetNativeWindowProperty(
         views::TooltipManager::kGroupingPropertyKey,
         reinterpret_cast<void*>(views::MenuConfig::kMenuControllerGroupingId));
@@ -1914,6 +1936,8 @@
   // in a single commit process, we need to ensure that it's not reset halfway
   // in the current commit by resetting it here.
   shadow_bounds_changed_ = false;
+
+  UpdateTopInset();
 }
 
 void ShellSurfaceBase::SetContainerInternal(int container) {
diff --git a/components/exo/shell_surface_base.h b/components/exo/shell_surface_base.h
index 05d85634..a3f23f2 100644
--- a/components/exo/shell_surface_base.h
+++ b/components/exo/shell_surface_base.h
@@ -259,6 +259,7 @@
   void Pin(bool trusted) override;
   void Unpin() override;
   void SetSystemModal(bool system_modal) override;
+  void SetTopInset(int height) override;
 
   // SurfaceObserver:
   void OnSurfaceDestroying(Surface* surface) override;
@@ -460,6 +461,11 @@
   gfx::Size minimum_size_;
   gfx::Size maximum_size_;
 
+  // Effective and pending top inset (header) heights, that are reserved or
+  // occupied by the top window frame.
+  int top_inset_height_ = 0;
+  int pending_top_inset_height_ = 0;
+
   // The orientation to be applied when widget is being created. Only set when
   // widget is not created yet orientation lock is being set. This is currently
   // only used by ClientControlledShellSurface.
@@ -491,6 +497,8 @@
 
   void UpdatePinned();
 
+  void UpdateTopInset();
+
   // Returns the resizability of the window. Useful to get the resizability
   // without actually updating it.
   bool CalculateCanResize() const;
diff --git a/components/exo/sub_surface.h b/components/exo/sub_surface.h
index c4e84f9..61d4bb5c 100644
--- a/components/exo/sub_surface.h
+++ b/components/exo/sub_surface.h
@@ -104,6 +104,7 @@
   void Pin(bool trusted) override {}
   void Unpin() override {}
   void SetSystemModal(bool system_modal) override {}
+  void SetTopInset(int height) override {}
   SecurityDelegate* GetSecurityDelegate() override;
 
   // Overridden from SurfaceObserver:
diff --git a/components/exo/surface.cc b/components/exo/surface.cc
index 42f3f90..80064fa6 100644
--- a/components/exo/surface.cc
+++ b/components/exo/surface.cc
@@ -1866,6 +1866,12 @@
   }
 }
 
+void Surface::SetTopInset(int height) {
+  if (delegate_) {
+    delegate_->SetTopInset(height);
+  }
+}
+
 void Surface::OnFullscreenStateChanged(bool fullscreen) {
   for (SurfaceObserver& observer : observers_) {
     observer.OnFullscreenStateChanged(fullscreen);
diff --git a/components/exo/surface.h b/components/exo/surface.h
index a16bc76..697f859 100644
--- a/components/exo/surface.h
+++ b/components/exo/surface.h
@@ -472,6 +472,9 @@
   // A negative number removes it.
   void SetClientAccessibilityId(int id);
 
+  // Set top inset for surface.
+  void SetTopInset(int height);
+
   // Inform observers and subsurfaces about new fullscreen state
   void OnFullscreenStateChanged(bool fullscreen);
 
diff --git a/components/exo/surface_delegate.h b/components/exo/surface_delegate.h
index 3b23cd2..a395e66 100644
--- a/components/exo/surface_delegate.h
+++ b/components/exo/surface_delegate.h
@@ -109,6 +109,9 @@
   // Sets the system modality.
   virtual void SetSystemModal(bool modal) = 0;
 
+  // Sets the top inset (header height).
+  virtual void SetTopInset(int height) = 0;
+
   // Returns the SecurityDelegate which this surface should use to perform
   // security-sensitive operations. See go/secure-exo-ids for more information.
   virtual SecurityDelegate* GetSecurityDelegate() = 0;
diff --git a/components/exo/surface_tree_host.h b/components/exo/surface_tree_host.h
index be6b6635..d148e8eb 100644
--- a/components/exo/surface_tree_host.h
+++ b/components/exo/surface_tree_host.h
@@ -126,6 +126,7 @@
   void Pin(bool trusted) override {}
   void Unpin() override {}
   void SetSystemModal(bool system_modal) override {}
+  void SetTopInset(int height) override {}
   SecurityDelegate* GetSecurityDelegate() override;
 
   // display::DisplayObserver:
diff --git a/components/exo/wayland/protocol/aura-shell.xml b/components/exo/wayland/protocol/aura-shell.xml
index e234b34..6462eee 100644
--- a/components/exo/wayland/protocol/aura-shell.xml
+++ b/components/exo/wayland/protocol/aura-shell.xml
@@ -24,7 +24,7 @@
     DEALINGS IN THE SOFTWARE.
   </copyright>
 
-  <interface name="zaura_shell" version="54">
+  <interface name="zaura_shell" version="55">
     <description summary="aura_shell">
       The global interface exposing aura shell capabilities is used to
       instantiate an interface extension for a wl_surface object.
@@ -764,7 +764,7 @@
     </event>
   </interface>
 
-  <interface name="zaura_toplevel" version="54">
+  <interface name="zaura_toplevel" version="55">
     <description summary="aura shell interface to the toplevel shell">
       An interface to the toplevel shell, which allows the
       client to access shell specific functionality.
@@ -1156,6 +1156,15 @@
       </description>
       <arg name="region" type="object" interface="wl_region" allow-null="true"/>
     </request>
+
+    <!-- Version 55 additions -->
+    <request name="set_top_inset" since="55">
+      <description summary="set top inset to the surface">
+        Sets the top inset to the surface. This represents the header height
+        and should be non negative.
+      </description>
+      <arg name="height" type="int" summary="Top inset of client window. Should be non negative."/>
+    </request>
   </interface>
 
   <interface name="zaura_popup" version="46">
diff --git a/components/exo/wayland/zaura_shell.cc b/components/exo/wayland/zaura_shell.cc
index 3686f57..97f8350 100644
--- a/components/exo/wayland/zaura_shell.cc
+++ b/components/exo/wayland/zaura_shell.cc
@@ -907,6 +907,10 @@
   shell_surface_->UnsetSnap();
 }
 
+void AuraToplevel::SetTopInset(int top_inset) {
+  shell_surface_->SetTopInset(top_inset);
+}
+
 template <class T>
 void AddState(wl_array* states, T state) {
   T* value = static_cast<T*>(wl_array_add(states, sizeof(T)));
@@ -1450,6 +1454,12 @@
                       : absl::nullopt);
 }
 
+void aura_toplevel_set_top_inset(wl_client* client,
+                                 wl_resource* resource,
+                                 int32_t top_inset) {
+  GetUserDataAs<AuraToplevel>(resource)->SetTopInset(top_inset);
+}
+
 const struct zaura_toplevel_interface aura_toplevel_implementation = {
     aura_toplevel_set_orientation_lock,
     aura_toplevel_surface_submission_in_pixel_coordinates,
@@ -1475,6 +1485,7 @@
     aura_toplevel_unset_snap,
     aura_toplevel_set_persistable,
     aura_toplevel_set_shape,
+    aura_toplevel_set_top_inset,
 };
 
 void aura_popup_surface_submission_in_pixel_coordinates(wl_client* client,
diff --git a/components/exo/wayland/zaura_shell.h b/components/exo/wayland/zaura_shell.h
index 7873f91..e6f57a25 100644
--- a/components/exo/wayland/zaura_shell.h
+++ b/components/exo/wayland/zaura_shell.h
@@ -32,7 +32,7 @@
 namespace wayland {
 class SerialTracker;
 
-constexpr uint32_t kZAuraShellVersion = 54;
+constexpr uint32_t kZAuraShellVersion = 55;
 
 // Adds bindings to the Aura Shell. Normally this implies Ash on ChromeOS
 // builds. On non-ChromeOS builds the protocol provides access to Aura windowing
@@ -151,6 +151,7 @@
   void SetSnapSecondary(float snap_ratio);
   void IntentToSnap(uint32_t snap_direction);
   void UnsetSnap();
+  void SetTopInset(int top_inset);
 
   void OnConfigure(const gfx::Rect& bounds,
                    chromeos::WindowStateType state_type,
diff --git a/components/exo/wayland/zaura_shell_unittest.cc b/components/exo/wayland/zaura_shell_unittest.cc
index 69998c6..7acb5b6 100644
--- a/components/exo/wayland/zaura_shell_unittest.cc
+++ b/components/exo/wayland/zaura_shell_unittest.cc
@@ -149,6 +149,7 @@
   MOCK_METHOD(void, Pin, (bool trusted), (override));
   MOCK_METHOD(void, Unpin, (), (override));
   MOCK_METHOD(void, SetSystemModal, (bool modal), (override));
+  MOCK_METHOD(void, SetTopInset, (int height), (override));
   MOCK_METHOD(SecurityDelegate*, GetSecurityDelegate, (), (override));
 };
 
diff --git a/components/external_intents/android/javatests/src/org/chromium/components/external_intents/ExternalNavigationHandlerTest.java b/components/external_intents/android/javatests/src/org/chromium/components/external_intents/ExternalNavigationHandlerTest.java
index 2ad7a66..86a24be 100644
--- a/components/external_intents/android/javatests/src/org/chromium/components/external_intents/ExternalNavigationHandlerTest.java
+++ b/components/external_intents/android/javatests/src/org/chromium/components/external_intents/ExternalNavigationHandlerTest.java
@@ -153,10 +153,6 @@
     private static final String WEBAPK_PACKAGE_NAME = WEBAPK_PACKAGE_PREFIX + ".template";
     private static final String INVALID_WEBAPK_PACKAGE_NAME = WEBAPK_PACKAGE_PREFIX + ".invalid";
 
-    private static final String[] SUPERVISOR_START_ACTIONS = {
-            "com.google.android.instantapps.START", "com.google.android.instantapps.nmr1.INSTALL",
-            "com.google.android.instantapps.nmr1.VIEW"};
-
     @Rule
     public MockitoRule mMockitoRule = MockitoJUnit.rule().strictness(Strictness.STRICT_STUBS);
 
diff --git a/components/gcm_driver/android/java/src/org/chromium/components/gcm_driver/SubscriptionFlagManager.java b/components/gcm_driver/android/java/src/org/chromium/components/gcm_driver/SubscriptionFlagManager.java
index f47d0e2..30d074697 100644
--- a/components/gcm_driver/android/java/src/org/chromium/components/gcm_driver/SubscriptionFlagManager.java
+++ b/components/gcm_driver/android/java/src/org/chromium/components/gcm_driver/SubscriptionFlagManager.java
@@ -14,7 +14,6 @@
  * before native has loaded.
  */
 public class SubscriptionFlagManager {
-    private static final String TAG = "SubscriptionFlagManager";
     private static final String PREF_PACKAGE =
             "org.chromium.components.gcm_driver.subscription_flags";
 
diff --git a/components/image_fetcher/android/junit/src/org/chromium/components/image_fetcher/NetworkImageFetcherTest.java b/components/image_fetcher/android/junit/src/org/chromium/components/image_fetcher/NetworkImageFetcherTest.java
index cbd79c1..d49e6e19 100644
--- a/components/image_fetcher/android/junit/src/org/chromium/components/image_fetcher/NetworkImageFetcherTest.java
+++ b/components/image_fetcher/android/junit/src/org/chromium/components/image_fetcher/NetworkImageFetcherTest.java
@@ -37,7 +37,6 @@
 public class NetworkImageFetcherTest {
     private static final String UMA_CLIENT_NAME = "TestUmaClient";
     private static final String URL = "http://google.com/test.png";
-    private static final String PATH = "test/path/cache/test.png";
     private static final int WIDTH_PX = 10;
     private static final int HEIGHT_PX = 20;
 
diff --git a/components/infobars/android/java/src/org/chromium/components/infobars/InfoBar.java b/components/infobars/android/java/src/org/chromium/components/infobars/InfoBar.java
index 4698f009..3d1c337 100644
--- a/components/infobars/android/java/src/org/chromium/components/infobars/InfoBar.java
+++ b/components/infobars/android/java/src/org/chromium/components/infobars/InfoBar.java
@@ -26,8 +26,6 @@
  */
 @JNINamespace("infobars")
 public abstract class InfoBar implements InfoBarInteractionHandler, InfoBarUiItem {
-    private static final String TAG = "InfoBar";
-
     /**
      * Interface for InfoBar to interact with its container.
      */
diff --git a/components/infobars/android/java/src/org/chromium/components/infobars/InfoBarControlLayoutTest.java b/components/infobars/android/java/src/org/chromium/components/infobars/InfoBarControlLayoutTest.java
index cee89ae..12c0a27 100644
--- a/components/infobars/android/java/src/org/chromium/components/infobars/InfoBarControlLayoutTest.java
+++ b/components/infobars/android/java/src/org/chromium/components/infobars/InfoBarControlLayoutTest.java
@@ -32,7 +32,6 @@
     private static final int SWITCH_ID_2 = 2;
     private static final int SWITCH_ID_3 = 3;
     private static final int SWITCH_ID_4 = 4;
-    private static final int SWITCH_ID_5 = 5;
     private static final int INFOBAR_WIDTH = 3200;
 
     private Context mContext;
diff --git a/components/media_router/browser/android/java/src/org/chromium/components/media_router/caf/remoting/StreamPositionExtrapolator.java b/components/media_router/browser/android/java/src/org/chromium/components/media_router/caf/remoting/StreamPositionExtrapolator.java
index ae4fd2a4..a11341427 100644
--- a/components/media_router/browser/android/java/src/org/chromium/components/media_router/caf/remoting/StreamPositionExtrapolator.java
+++ b/components/media_router/browser/android/java/src/org/chromium/components/media_router/caf/remoting/StreamPositionExtrapolator.java
@@ -10,8 +10,6 @@
  * position.
  */
 public class StreamPositionExtrapolator {
-    private static final String TAG = "MediaFling";
-
     private long mDuration;
     private long mLastKnownPosition;
     private long mTimestamp;
diff --git a/components/media_router/browser/android/junit/src/org/chromium/components/media_router/JSONTestUtils.java b/components/media_router/browser/android/junit/src/org/chromium/components/media_router/JSONTestUtils.java
index 19d52cd9..5bf1b44 100644
--- a/components/media_router/browser/android/junit/src/org/chromium/components/media_router/JSONTestUtils.java
+++ b/components/media_router/browser/android/junit/src/org/chromium/components/media_router/JSONTestUtils.java
@@ -15,7 +15,6 @@
  * Utilities for comparing JSON objects and strings.
  */
 public class JSONTestUtils {
-    private static final String TAG = "MediaRouter";
     private static final String ANY_PREFIX = "ANY_";
 
     private static boolean isPureJSONObjectEqual(JSONObject expected, JSONObject actual) {
diff --git a/components/media_router/browser/android/junit/src/org/chromium/components/media_router/caf/CafMediaRouteProviderTest.java b/components/media_router/browser/android/junit/src/org/chromium/components/media_router/caf/CafMediaRouteProviderTest.java
index 06728c8..d38b3ef 100644
--- a/components/media_router/browser/android/junit/src/org/chromium/components/media_router/caf/CafMediaRouteProviderTest.java
+++ b/components/media_router/browser/android/junit/src/org/chromium/components/media_router/caf/CafMediaRouteProviderTest.java
@@ -59,11 +59,6 @@
         shadows = {ShadowMediaRouter.class, ShadowCastContext.class, ShadowLooper.class,
                 ShadowCastMediaSource.class})
 public class CafMediaRouteProviderTest {
-    private static final String SUPPORTED_SOURCE = "cast:DEADBEEF";
-
-    private static final String SUPPORTED_AUTOJOIN_SOURCE = "cast:DEADBEEF"
-            + "?clientId=12345&autoJoinPolicy=" + CastMediaSource.AUTOJOIN_TAB_AND_ORIGIN_SCOPED;
-
     private Context mContext;
     private CafMediaRouteProvider mProvider;
     private MediaRouterTestHelper mMediaRouterHelper;
diff --git a/components/media_router/browser/android/junit/src/org/chromium/components/media_router/caf/CafMessageHandlerTest.java b/components/media_router/browser/android/junit/src/org/chromium/components/media_router/caf/CafMessageHandlerTest.java
index 83e3dad..5beabaae 100644
--- a/components/media_router/browser/android/junit/src/org/chromium/components/media_router/caf/CafMessageHandlerTest.java
+++ b/components/media_router/browser/android/junit/src/org/chromium/components/media_router/caf/CafMessageHandlerTest.java
@@ -62,8 +62,6 @@
 @RunWith(BaseRobolectricTestRunner.class)
 @Config(manifest = Config.NONE)
 public class CafMessageHandlerTest {
-    private static final String TAG = "MediaRouter";
-
     private static final String SESSION_ID = "SESSION_ID";
     private static final String INVALID_SESSION_ID = "INVALID_SESSION_ID";
     private static final String CLIENT_ID1 = "client-id-1";
diff --git a/components/media_router/browser/android/junit/src/org/chromium/components/media_router/caf/CastSessionControllerTest.java b/components/media_router/browser/android/junit/src/org/chromium/components/media_router/caf/CastSessionControllerTest.java
index 8dfc11c..47ed3b8c 100644
--- a/components/media_router/browser/android/junit/src/org/chromium/components/media_router/caf/CastSessionControllerTest.java
+++ b/components/media_router/browser/android/junit/src/org/chromium/components/media_router/caf/CastSessionControllerTest.java
@@ -54,11 +54,6 @@
 @RunWith(BaseRobolectricTestRunner.class)
 @Config(manifest = Config.NONE)
 public class CastSessionControllerTest {
-    private static final String PRESENTATION_ID = "presentation-id";
-    private static final String ORIGIN = "https://example.com/";
-    private static final int TAB_ID = 1;
-    private static final String APP_ID = "12345678";
-
     @Mock
     private CastDevice mCastDevice;
     @Mock
diff --git a/components/minidump_uploader/android/java/src/org/chromium/components/minidump_uploader/CrashReportMimeWriter.java b/components/minidump_uploader/android/java/src/org/chromium/components/minidump_uploader/CrashReportMimeWriter.java
index 59a2f0b..03ff70c 100644
--- a/components/minidump_uploader/android/java/src/org/chromium/components/minidump_uploader/CrashReportMimeWriter.java
+++ b/components/minidump_uploader/android/java/src/org/chromium/components/minidump_uploader/CrashReportMimeWriter.java
@@ -17,8 +17,6 @@
  */
 @JNINamespace("minidump_uploader")
 public class CrashReportMimeWriter {
-    private static final String TAG = "CrashReportMimeWriter";
-
     private static final String MINIDUMP_KEY = "upload_file_minidump";
 
     /*
diff --git a/components/offline_items_collection/core/android/java/src/org/chromium/components/offline_items_collection/LegacyHelpers.java b/components/offline_items_collection/core/android/java/src/org/chromium/components/offline_items_collection/LegacyHelpers.java
index cba1798..d1794e19d 100644
--- a/components/offline_items_collection/core/android/java/src/org/chromium/components/offline_items_collection/LegacyHelpers.java
+++ b/components/offline_items_collection/core/android/java/src/org/chromium/components/offline_items_collection/LegacyHelpers.java
@@ -17,7 +17,6 @@
     public static final String LEGACY_CONTENT_INDEX_NAMESPACE = "content_index";
     public static final String LEGACY_DOWNLOAD_NAMESPACE = "LEGACY_DOWNLOAD";
     public static final String LEGACY_ANDROID_DOWNLOAD_NAMESPACE = "LEGACY_ANDROID_DOWNLOAD";
-    private static final String LEGACY_DOWNLOAD_NAMESPACE_PREFIX = "LEGACY_DOWNLOAD";
 
     /**
      * Helper to build a {@link ContentId} based on a single GUID for old offline content sources
diff --git a/components/offline_items_collection/core/android/java/src/org/chromium/components/offline_items_collection/OfflineContentAggregatorBridge.java b/components/offline_items_collection/core/android/java/src/org/chromium/components/offline_items_collection/OfflineContentAggregatorBridge.java
index b30ea8c..5f0578df 100644
--- a/components/offline_items_collection/core/android/java/src/org/chromium/components/offline_items_collection/OfflineContentAggregatorBridge.java
+++ b/components/offline_items_collection/core/android/java/src/org/chromium/components/offline_items_collection/OfflineContentAggregatorBridge.java
@@ -4,8 +4,6 @@
 
 package org.chromium.components.offline_items_collection;
 
-import android.os.Handler;
-
 import org.chromium.base.Callback;
 import org.chromium.base.ObserverList;
 import org.chromium.base.annotations.CalledByNative;
@@ -22,8 +20,6 @@
  */
 @JNINamespace("offline_items_collection::android")
 public class OfflineContentAggregatorBridge implements OfflineContentProvider {
-    private final Handler mHandler = new Handler();
-
     private long mNativeOfflineContentAggregatorBridge;
     private ObserverList<OfflineContentProvider.Observer> mObservers;
 
diff --git a/components/omnibox/browser/android/java/src/org/chromium/components/omnibox/AutocompleteMatchBuilder.java b/components/omnibox/browser/android/java/src/org/chromium/components/omnibox/AutocompleteMatchBuilder.java
index 57fecbb..9481107 100644
--- a/components/omnibox/browser/android/java/src/org/chromium/components/omnibox/AutocompleteMatchBuilder.java
+++ b/components/omnibox/browser/android/java/src/org/chromium/components/omnibox/AutocompleteMatchBuilder.java
@@ -137,6 +137,15 @@
     }
 
     /**
+     * @param text The text to replace the Omnibox content with.
+     * @return Omnibox suggestion builder.
+     */
+    public AutocompleteMatchBuilder setFillIntoEdit(String text) {
+        mFillIntoEdit = text;
+        return this;
+    }
+
+    /**
      * @param id Group Id for newly built suggestion.
      * @return Omnibox suggestion builder.
      */
diff --git a/components/omnibox/browser/omnibox_field_trial.cc b/components/omnibox/browser/omnibox_field_trial.cc
index e41401a..059d8ec 100644
--- a/components/omnibox/browser/omnibox_field_trial.cc
+++ b/components/omnibox/browser/omnibox_field_trial.cc
@@ -687,7 +687,7 @@
 const base::FeatureParam<int> OmniboxFieldTrial::kRichSuggestionVerticalMargin(
     &omnibox::kUniformRowHeight,
     "OmniboxRichSuggestionVerticalMargin",
-    4);
+    6);
 
 bool OmniboxFieldTrial::IsChromeRefreshIconsEnabled() {
   static bool enabled =
@@ -709,6 +709,12 @@
          base::FeatureList::IsEnabled(omnibox::kCr2023ActionChips);
 }
 
+bool OmniboxFieldTrial::IsChromeRefreshSuggestHoverFillShapeEnabled() {
+  return features::GetChromeRefresh2023Level() ==
+             features::ChromeRefresh2023Level::kLevel2 ||
+         base::FeatureList::IsEnabled(omnibox::kSuggestionHoverFillShape);
+}
+
 bool OmniboxFieldTrial::IsGM3TextStyleEnabled() {
   return base::FeatureList::IsEnabled(omnibox::kOmniboxSteadyStateTextStyle);
 }
diff --git a/components/omnibox/browser/omnibox_field_trial.h b/components/omnibox/browser/omnibox_field_trial.h
index 07d93df5..df8b1df 100644
--- a/components/omnibox/browser/omnibox_field_trial.h
+++ b/components/omnibox/browser/omnibox_field_trial.h
@@ -383,6 +383,11 @@
 // Returns true if the feature to enable CR23 action chip icons is enabled.
 bool IsChromeRefreshActionChipIconsEnabled();
 
+// Omnibox CR23 - suggestion hover fill shape.
+// Returns true if the feature to enable CR23 suggestion hover fill shape is
+// enabled.
+bool IsChromeRefreshSuggestHoverFillShapeEnabled();
+
 // Omnibox GM3 - text style.
 // Returns true if the feature to enable GM3 text styling is enabled.
 bool IsGM3TextStyleEnabled();
diff --git a/components/omnibox/common/omnibox_features.cc b/components/omnibox/common/omnibox_features.cc
index d8dcfd7..b70481c 100644
--- a/components/omnibox/common/omnibox_features.cc
+++ b/components/omnibox/common/omnibox_features.cc
@@ -398,6 +398,12 @@
              "OmniboxExpandedLayout",
              base::FEATURE_DISABLED_BY_DEFAULT);
 
+// If enabled, the shape of the "hover fill" that's rendered for Omnibox
+// suggestions is updated to match CR23 guidelines.
+BASE_FEATURE(kSuggestionHoverFillShape,
+             "OmniboxSuggestionHoverFillShape",
+             base::FEATURE_DISABLED_BY_DEFAULT);
+
 // When enabled, use Assistant for omnibox voice query recognition instead of
 // Android's built-in voice recognition service. Only works on Android.
 BASE_FEATURE(kOmniboxAssistantVoiceSearch,
diff --git a/components/omnibox/common/omnibox_features.h b/components/omnibox/common/omnibox_features.h
index 2b27cbe..e18a079 100644
--- a/components/omnibox/common/omnibox_features.h
+++ b/components/omnibox/common/omnibox_features.h
@@ -98,6 +98,7 @@
 BASE_DECLARE_FEATURE(kExpandedStateColors);
 BASE_DECLARE_FEATURE(kExpandedStateSuggestIcons);
 BASE_DECLARE_FEATURE(kExpandedLayout);
+BASE_DECLARE_FEATURE(kSuggestionHoverFillShape);
 
 // Omnibox UI - these affect the UI or function of the location bar (not the
 // popup).
diff --git a/components/page_info/android/java/src/org/chromium/components/page_info/PageInfoCookiesPreference.java b/components/page_info/android/java/src/org/chromium/components/page_info/PageInfoCookiesPreference.java
index d355bb1..1df4766 100644
--- a/components/page_info/android/java/src/org/chromium/components/page_info/PageInfoCookiesPreference.java
+++ b/components/page_info/android/java/src/org/chromium/components/page_info/PageInfoCookiesPreference.java
@@ -31,7 +31,6 @@
     private static final String COOKIE_SWITCH_PREFERENCE = "cookie_switch";
     private static final String COOKIE_IN_USE_PREFERENCE = "cookie_in_use";
     private static final String FPS_IN_USE_PREFERENCE = "fps_in_use";
-    private static final String CLEAR_BUTTON_PREFERENCE = "clear_button";
 
     private ChromeSwitchPreference mCookieSwitch;
     private ChromeImageViewPreference mCookieInUse;
diff --git a/components/paint_preview/player/android/java/src/org/chromium/components/paintpreview/player/frame/PlayerFrameBitmapPainter.java b/components/paint_preview/player/android/java/src/org/chromium/components/paintpreview/player/frame/PlayerFrameBitmapPainter.java
index f442988..66c1ca15 100644
--- a/components/paint_preview/player/android/java/src/org/chromium/components/paintpreview/player/frame/PlayerFrameBitmapPainter.java
+++ b/components/paint_preview/player/android/java/src/org/chromium/components/paintpreview/player/frame/PlayerFrameBitmapPainter.java
@@ -7,7 +7,6 @@
 import android.graphics.Bitmap;
 import android.graphics.Canvas;
 import android.graphics.Rect;
-import android.os.Handler;
 import android.util.Size;
 
 import androidx.annotation.NonNull;
@@ -27,7 +26,6 @@
     private Rect mDrawBitmapDst = new Rect();
     private Runnable mInvalidateCallback;
     private Runnable mFirstPaintListener;
-    private Handler mHandler = new Handler();
     private boolean mDestroyed;
 
     PlayerFrameBitmapPainter(@NonNull Runnable invalidateCallback,
diff --git a/components/policy/resources/templates/policy_definitions/Miscellaneous/OnBulkDataEntryEnterpriseConnector.yaml b/components/policy/resources/templates/policy_definitions/Miscellaneous/OnBulkDataEntryEnterpriseConnector.yaml
index b46a29a..ddbd0e1 100644
--- a/components/policy/resources/templates/policy_definitions/Miscellaneous/OnBulkDataEntryEnterpriseConnector.yaml
+++ b/components/policy/resources/templates/policy_definitions/Miscellaneous/OnBulkDataEntryEnterpriseConnector.yaml
@@ -123,6 +123,7 @@
         - google
         - local_user_agent
         - local_system_agent
+        - brcm_chrm_cas
         type: string
       verification:
         properties:
diff --git a/components/policy/resources/templates/policy_definitions/Miscellaneous/OnFileAttachedEnterpriseConnector.yaml b/components/policy/resources/templates/policy_definitions/Miscellaneous/OnFileAttachedEnterpriseConnector.yaml
index f27ba38..d13d1ab 100644
--- a/components/policy/resources/templates/policy_definitions/Miscellaneous/OnFileAttachedEnterpriseConnector.yaml
+++ b/components/policy/resources/templates/policy_definitions/Miscellaneous/OnFileAttachedEnterpriseConnector.yaml
@@ -127,6 +127,7 @@
         - google
         - local_user_agent
         - local_system_agent
+        - brcm_chrm_cas
         type: string
       verification:
         properties:
diff --git a/components/policy/resources/templates/policy_definitions/Miscellaneous/OnFileDownloadedEnterpriseConnector.yaml b/components/policy/resources/templates/policy_definitions/Miscellaneous/OnFileDownloadedEnterpriseConnector.yaml
index 64578c2e..44182b31 100644
--- a/components/policy/resources/templates/policy_definitions/Miscellaneous/OnFileDownloadedEnterpriseConnector.yaml
+++ b/components/policy/resources/templates/policy_definitions/Miscellaneous/OnFileDownloadedEnterpriseConnector.yaml
@@ -128,6 +128,7 @@
         - google
         - local_user_agent
         - local_system_agent
+        - brcm_chrm_cas
         type: string
       verification:
         properties:
diff --git a/components/policy/resources/templates/policy_definitions/Miscellaneous/OnPrintEnterpriseConnector.yaml b/components/policy/resources/templates/policy_definitions/Miscellaneous/OnPrintEnterpriseConnector.yaml
index f708e97..3caf009 100644
--- a/components/policy/resources/templates/policy_definitions/Miscellaneous/OnPrintEnterpriseConnector.yaml
+++ b/components/policy/resources/templates/policy_definitions/Miscellaneous/OnPrintEnterpriseConnector.yaml
@@ -116,6 +116,7 @@
         - google
         - local_user_agent
         - local_system_agent
+        - brcm_chrm_cas
         type: string
       verification:
         properties:
diff --git a/components/printing/browser/print_to_pdf/pdf_print_utils.cc b/components/printing/browser/print_to_pdf/pdf_print_utils.cc
index 3bcad25..b2df910 100644
--- a/components/printing/browser/print_to_pdf/pdf_print_utils.cc
+++ b/components/printing/browser/print_to_pdf/pdf_print_utils.cc
@@ -154,7 +154,7 @@
   if (paper_height_in_inches <= 0)
     return "paper height is zero or negative";
 
-  gfx::Size paper_size_in_points = gfx::ToRoundedSize(
+  gfx::Size paper_size_in_points = gfx::ToCeiledSize(
       gfx::SizeF(paper_width_in_inches * printing::kPointsPerInch,
                  paper_height_in_inches * printing::kPointsPerInch));
   gfx::Rect printable_area_device_units(paper_size_in_points);
@@ -175,6 +175,17 @@
   print_pages_params->params->prefer_css_page_size =
       prefer_css_page_size.value_or(false);
 
+  CHECK(!print_pages_params->params->page_size.IsEmpty())
+      << print_pages_params->params->page_size.ToString();
+
+  if (print_pages_params->params->printable_area.IsEmpty()) {
+    return "invalid print parameters: printable area is empty";
+  }
+
+  if (print_pages_params->params->content_size.IsEmpty()) {
+    return "invalid print parameters: content area is empty";
+  }
+
   if (!printing::PrintMsgPrintParamsIsValid(*print_pages_params->params)) {
     return "invalid print parameters";
   }
diff --git a/components/privacy_sandbox/privacy_sandbox_attestations/BUILD.gn b/components/privacy_sandbox/privacy_sandbox_attestations/BUILD.gn
index 1184d93..9229730 100644
--- a/components/privacy_sandbox/privacy_sandbox_attestations/BUILD.gn
+++ b/components/privacy_sandbox/privacy_sandbox_attestations/BUILD.gn
@@ -6,12 +6,32 @@
   sources = [
     "privacy_sandbox_attestations.cc",
     "privacy_sandbox_attestations.h",
+    "privacy_sandbox_attestations_parser.cc",
+    "privacy_sandbox_attestations_parser.h",
   ]
 
-  deps = [ "//components/privacy_sandbox:features" ]
+  deps = [
+    "//components/privacy_sandbox:features",
+    "//components/privacy_sandbox/privacy_sandbox_attestations/proto:proto",
+  ]
 
   public_deps = [
     "//base",
     "//net:net",
   ]
 }
+
+source_set("unit_tests") {
+  testonly = true
+  sources = [ "privacy_sandbox_attestations_parser_unittest.cc" ]
+
+  deps = [
+    ":privacy_sandbox_attestations",
+    "//base/test:test_support",
+    "//components/privacy_sandbox/privacy_sandbox_attestations/proto:proto",
+    "//net",
+    "//testing/gtest",
+    "//testing/gtest:gtest",
+    "//url:url",
+  ]
+}
diff --git a/components/privacy_sandbox/privacy_sandbox_attestations/privacy_sandbox_attestations.h b/components/privacy_sandbox/privacy_sandbox_attestations/privacy_sandbox_attestations.h
index 5ecbcf5..d2edb25 100644
--- a/components/privacy_sandbox/privacy_sandbox_attestations/privacy_sandbox_attestations.h
+++ b/components/privacy_sandbox/privacy_sandbox_attestations/privacy_sandbox_attestations.h
@@ -11,6 +11,11 @@
 
 namespace privacy_sandbox {
 
+// When a new enum value is added:
+// 1. Update kMaxValue to match it.
+// 2. Update `PrivacySandboxAttestationsGatedAPIProto` in
+//    `privacy_sandbox_attestations.proto`.
+// 3. Update `AllowAPI` in `privacy_sandbox_attestations_parser.cc`.
 enum class PrivacySandboxAttestationsGatedAPI {
   kTopics,
   kProtectedAudience,
@@ -18,7 +23,6 @@
   kAttributionReporting,
   kSharedStorage,
 
-  // Update this value whenever a new API is added.
   kMaxValue = kSharedStorage,
 };
 
@@ -27,6 +31,8 @@
                   PrivacySandboxAttestationsGatedAPI::kTopics,
                   PrivacySandboxAttestationsGatedAPI::kMaxValue>;
 
+// TODO(crbug.com/1454847): Add a concise representation for "this site is
+// attested for all APIs".
 using PrivacySandboxAttestationsMap =
     base::flat_map<net::SchemefulSite, PrivacySandboxAttestationsGatedAPISet>;
 
diff --git a/components/privacy_sandbox/privacy_sandbox_attestations/privacy_sandbox_attestations_parser.cc b/components/privacy_sandbox/privacy_sandbox_attestations/privacy_sandbox_attestations_parser.cc
new file mode 100644
index 0000000..9bda56e8
--- /dev/null
+++ b/components/privacy_sandbox/privacy_sandbox_attestations/privacy_sandbox_attestations_parser.cc
@@ -0,0 +1,112 @@
+// 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 "components/privacy_sandbox/privacy_sandbox_attestations/privacy_sandbox_attestations_parser.h"
+#include "components/privacy_sandbox/privacy_sandbox_attestations/proto/privacy_sandbox_attestations.pb.h"
+
+#include "base/containers/enum_set.h"
+#include "base/containers/flat_map.h"
+#include "net/base/schemeful_site.h"
+#include "url/gurl.h"
+
+namespace {
+void InsertAPI(
+    privacy_sandbox::PrivacySandboxAttestationsGatedAPISet& allowed_api_set,
+    privacy_sandbox::PrivacySandboxAttestationsGatedAPIProto proto_api) {
+  // If the proto enum matches one understood by the client, add it to the
+  // allowed API set. Otherwise, ignore it.
+  switch (proto_api) {
+    case privacy_sandbox::TOPICS: {
+      allowed_api_set.Put(
+          privacy_sandbox::PrivacySandboxAttestationsGatedAPI::kTopics);
+      return;
+    }
+    case privacy_sandbox::PROTECTED_AUDIENCE: {
+      allowed_api_set.Put(privacy_sandbox::PrivacySandboxAttestationsGatedAPI::
+                              kProtectedAudience);
+      return;
+    }
+    case privacy_sandbox::PRIVATE_AGGREGATION: {
+      allowed_api_set.Put(privacy_sandbox::PrivacySandboxAttestationsGatedAPI::
+                              kPrivateAggregation);
+      return;
+    }
+    case privacy_sandbox::ATTRIBUTION_REPORTING: {
+      allowed_api_set.Put(privacy_sandbox::PrivacySandboxAttestationsGatedAPI::
+                              kAttributionReporting);
+      return;
+    }
+    case privacy_sandbox::SHARED_STORAGE: {
+      allowed_api_set.Put(
+          privacy_sandbox::PrivacySandboxAttestationsGatedAPI::kSharedStorage);
+      return;
+    }
+    case privacy_sandbox::UNKNOWN: {
+      return;
+    }
+    default: {
+      return;
+    }
+  }
+}
+}  // namespace
+
+namespace privacy_sandbox {
+
+absl::optional<PrivacySandboxAttestationsMap> ParseAttestationsFromStream(
+    std::istream& input) {
+  PrivacySandboxAttestationsProto proto;
+
+  // Parse the istream into a proto for the attestations message format.
+  if (!proto.ParseFromIstream(&input)) {
+    // Parsing failed. This should never happen in real use, because the input
+    // comes from Chrome servers.
+    return absl::nullopt;
+  }
+
+  // Convert the parsed proto into a C++ attestations map.
+
+  // Parse the set of "all APIs".
+  PrivacySandboxAttestationsGatedAPISet all_api_set;
+  for (int i = 0; i < proto.all_apis_size(); ++i) {
+    InsertAPI(all_api_set, proto.all_apis(i));
+  }
+
+  // Allocate a vector to store the site attestation pairs from the proto.
+  std::vector<
+      std::pair<net::SchemefulSite, PrivacySandboxAttestationsGatedAPISet>>
+      site_attestations_vector;
+  site_attestations_vector.reserve(proto.sites_attested_for_all_apis_size() +
+                                   proto.site_attestations_size());
+
+  // Store the sites that are attested for all APIs.
+  for (int i = 0; i < proto.sites_attested_for_all_apis_size(); ++i) {
+    site_attestations_vector.emplace_back(
+        net::SchemefulSite(GURL(proto.sites_attested_for_all_apis(i))),
+        all_api_set);
+  }
+
+  // Store the sites that are attested for only some APIs.
+  const auto& site_attestations_map = proto.site_attestations();
+  for (const auto& site_attestation_pair : site_attestations_map) {
+    // Get the site key.
+    auto site = net::SchemefulSite(GURL(site_attestation_pair.first));
+
+    // Collect all of the allowed apis for that site.
+    const auto& attestation = site_attestation_pair.second;
+    PrivacySandboxAttestationsGatedAPISet allowed_api_set;
+    for (int i = 0; i < attestation.attested_apis_size(); ++i) {
+      InsertAPI(allowed_api_set, attestation.attested_apis(i));
+    }
+
+    // Append the site,apis pair to the C++ formatted vector.
+    site_attestations_vector.emplace_back(site, allowed_api_set);
+  }
+
+  // Convert the vector into a flat map (which prefers initialization with the
+  // entire data structure, not incremental inserts) and return.
+  return PrivacySandboxAttestationsMap(site_attestations_vector);
+}
+
+}  // namespace privacy_sandbox
diff --git a/components/privacy_sandbox/privacy_sandbox_attestations/privacy_sandbox_attestations_parser.h b/components/privacy_sandbox/privacy_sandbox_attestations/privacy_sandbox_attestations_parser.h
new file mode 100644
index 0000000..d148c67
--- /dev/null
+++ b/components/privacy_sandbox/privacy_sandbox_attestations/privacy_sandbox_attestations_parser.h
@@ -0,0 +1,27 @@
+// 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 COMPONENTS_PRIVACY_SANDBOX_PRIVACY_SANDBOX_ATTESTATIONS_PRIVACY_SANDBOX_ATTESTATIONS_PARSER_H_
+#define COMPONENTS_PRIVACY_SANDBOX_PRIVACY_SANDBOX_ATTESTATIONS_PRIVACY_SANDBOX_ATTESTATIONS_PARSER_H_
+
+#include "components/privacy_sandbox/privacy_sandbox_attestations/privacy_sandbox_attestations.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
+
+#include <istream>
+
+namespace privacy_sandbox {
+
+// Parse an `input` containing a serialized protobuf attestations list
+// (`PrivacySandboxAttestationsProto`) and convert it into a C++
+// `PrivacySandboxAttestationsMap`.
+// Return absl::nullopt if there is a catastrophic failure with parsing. This
+// should never happen in normal use, but we should gracefully handle failure
+// just in case the list sent from the server (or the file stored on disk) gets
+// corrupted.
+absl::optional<PrivacySandboxAttestationsMap> ParseAttestationsFromStream(
+    std::istream& input);
+
+}  // namespace privacy_sandbox
+
+#endif  // COMPONENTS_PRIVACY_SANDBOX_PRIVACY_SANDBOX_ATTESTATIONS_PRIVACY_SANDBOX_ATTESTATIONS_PARSER_H_
diff --git a/components/privacy_sandbox/privacy_sandbox_attestations/privacy_sandbox_attestations_parser_unittest.cc b/components/privacy_sandbox/privacy_sandbox_attestations/privacy_sandbox_attestations_parser_unittest.cc
new file mode 100644
index 0000000..d8db8f0
--- /dev/null
+++ b/components/privacy_sandbox/privacy_sandbox_attestations/privacy_sandbox_attestations_parser_unittest.cc
@@ -0,0 +1,306 @@
+// 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 "components/privacy_sandbox/privacy_sandbox_attestations/privacy_sandbox_attestations_parser.h"
+#include "components/privacy_sandbox/privacy_sandbox_attestations/proto/privacy_sandbox_attestations.pb.h"
+
+#include "base/containers/enum_set.h"
+#include "base/containers/flat_map.h"
+#include "net/base/schemeful_site.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "url/gurl.h"
+
+#include <sstream>
+#include <string>
+
+namespace privacy_sandbox {
+
+class PrivacySandboxAttestationsParserTest : public testing::Test {};
+
+// A completely empty proto gets parsed as an empty map.
+TEST_F(PrivacySandboxAttestationsParserTest, EmptyProto) {
+  PrivacySandboxAttestationsProto proto;
+  ASSERT_TRUE(proto.site_attestations_size() == 0);
+
+  std::string serialized_proto;
+  proto.SerializeToString(&serialized_proto);
+  std::istringstream iss(serialized_proto);
+
+  absl::optional<PrivacySandboxAttestationsMap> optional_map =
+      ParseAttestationsFromStream(iss);
+  ASSERT_TRUE(optional_map.has_value());
+  ASSERT_TRUE(optional_map->empty());
+}
+
+// A malformed proto returns absl::nullopt to represent an error.
+TEST_F(PrivacySandboxAttestationsParserTest, InvalidProto) {
+  std::string serialized_proto("invalid proto");
+  std::istringstream iss(serialized_proto);
+  absl::optional<PrivacySandboxAttestationsMap> optional_map =
+      ParseAttestationsFromStream(iss);
+  ASSERT_FALSE(optional_map.has_value());
+}
+
+// A map with one API for each site should correctly map the APIs between proto
+// and C++.
+TEST_F(PrivacySandboxAttestationsParserTest, OneSitePerAPIProto) {
+  PrivacySandboxAttestationsProto proto;
+  ASSERT_TRUE(proto.site_attestations_size() == 0);
+
+  std::string site1 = "https://a.com";
+  std::string site2 = "https://b.com";
+  std::string site3 = "https://c.com";
+  std::string site4 = "https://d.com";
+  std::string site5 = "https://e.com";
+
+  PrivacySandboxAttestationsProto::PrivacySandboxAttestedAPIsProto attestation1;
+  attestation1.add_attested_apis(TOPICS);
+
+  PrivacySandboxAttestationsProto::PrivacySandboxAttestedAPIsProto attestation2;
+  attestation2.add_attested_apis(PROTECTED_AUDIENCE);
+
+  PrivacySandboxAttestationsProto::PrivacySandboxAttestedAPIsProto attestation3;
+  attestation3.add_attested_apis(PRIVATE_AGGREGATION);
+
+  PrivacySandboxAttestationsProto::PrivacySandboxAttestedAPIsProto attestation4;
+  attestation4.add_attested_apis(ATTRIBUTION_REPORTING);
+
+  PrivacySandboxAttestationsProto::PrivacySandboxAttestedAPIsProto attestation5;
+  attestation5.add_attested_apis(SHARED_STORAGE);
+
+  (*proto.mutable_site_attestations())[site1] = attestation1;
+  (*proto.mutable_site_attestations())[site2] = attestation2;
+  (*proto.mutable_site_attestations())[site3] = attestation3;
+  (*proto.mutable_site_attestations())[site4] = attestation4;
+  (*proto.mutable_site_attestations())[site5] = attestation5;
+
+  std::string serialized_proto;
+  proto.SerializeToString(&serialized_proto);
+  std::istringstream iss(serialized_proto);
+
+  absl::optional<PrivacySandboxAttestationsMap> optional_map =
+      ParseAttestationsFromStream(iss);
+  ASSERT_TRUE(optional_map.has_value());
+  ASSERT_TRUE(optional_map->size() == 5UL);
+
+  const PrivacySandboxAttestationsGatedAPISet& site1apis =
+      (*optional_map)[net::SchemefulSite(GURL(site1))];
+  ASSERT_TRUE(site1apis.Has(PrivacySandboxAttestationsGatedAPI::kTopics));
+  ASSERT_TRUE(site1apis.Size() == 1UL);
+
+  const PrivacySandboxAttestationsGatedAPISet& site2apis =
+      (*optional_map)[net::SchemefulSite(GURL(site2))];
+  ASSERT_TRUE(
+      site2apis.Has(PrivacySandboxAttestationsGatedAPI::kProtectedAudience));
+  ASSERT_TRUE(site2apis.Size() == 1UL);
+
+  const PrivacySandboxAttestationsGatedAPISet& site3apis =
+      (*optional_map)[net::SchemefulSite(GURL(site3))];
+  ASSERT_TRUE(
+      site3apis.Has(PrivacySandboxAttestationsGatedAPI::kPrivateAggregation));
+  ASSERT_TRUE(site3apis.Size() == 1UL);
+
+  const PrivacySandboxAttestationsGatedAPISet& site4apis =
+      (*optional_map)[net::SchemefulSite(GURL(site4))];
+  ASSERT_TRUE(
+      site4apis.Has(PrivacySandboxAttestationsGatedAPI::kAttributionReporting));
+  ASSERT_TRUE(site4apis.Size() == 1UL);
+
+  const PrivacySandboxAttestationsGatedAPISet& site5apis =
+      (*optional_map)[net::SchemefulSite(GURL(site5))];
+  ASSERT_TRUE(
+      site5apis.Has(PrivacySandboxAttestationsGatedAPI::kSharedStorage));
+  ASSERT_TRUE(site5apis.Size() == 1UL);
+}
+
+// Multiple attested APIs per site should work. Unknown APIs should be ignored.
+TEST_F(PrivacySandboxAttestationsParserTest, MultipleAPIsPerSiteProto) {
+  PrivacySandboxAttestationsProto proto;
+  ASSERT_TRUE(proto.site_attestations_size() == 0);
+
+  std::string site1 = "https://a.com";
+  PrivacySandboxAttestationsProto::PrivacySandboxAttestedAPIsProto attestation1;
+  attestation1.add_attested_apis(TOPICS);
+  // Add the default out of range value.
+  attestation1.add_attested_apis(UNKNOWN);
+  attestation1.add_attested_apis(PROTECTED_AUDIENCE);
+  attestation1.add_attested_apis(PRIVATE_AGGREGATION);
+  // Add an explicitly out of range value. (This static_cast is undefined...)
+  attestation1.add_attested_apis(
+      static_cast<PrivacySandboxAttestationsGatedAPIProto>(192));
+  attestation1.add_attested_apis(ATTRIBUTION_REPORTING);
+  attestation1.add_attested_apis(SHARED_STORAGE);
+
+  (*proto.mutable_site_attestations())[site1] = attestation1;
+
+  std::string serialized_proto;
+  proto.SerializeToString(&serialized_proto);
+  std::istringstream iss(serialized_proto);
+
+  absl::optional<PrivacySandboxAttestationsMap> optional_map =
+      ParseAttestationsFromStream(iss);
+  ASSERT_TRUE(optional_map.has_value());
+  ASSERT_TRUE(optional_map->size() == 1UL);
+
+  const PrivacySandboxAttestationsGatedAPISet& site1apis =
+      (*optional_map)[net::SchemefulSite(GURL(site1))];
+  ASSERT_TRUE(site1apis.Has(PrivacySandboxAttestationsGatedAPI::kTopics));
+  ASSERT_TRUE(
+      site1apis.Has(PrivacySandboxAttestationsGatedAPI::kProtectedAudience));
+  ASSERT_TRUE(
+      site1apis.Has(PrivacySandboxAttestationsGatedAPI::kPrivateAggregation));
+  ASSERT_TRUE(
+      site1apis.Has(PrivacySandboxAttestationsGatedAPI::kAttributionReporting));
+  ASSERT_TRUE(
+      site1apis.Has(PrivacySandboxAttestationsGatedAPI::kSharedStorage));
+  ASSERT_TRUE(site1apis.Size() == 5UL);
+}
+
+// Test basic functionality of `all_apis` and `sites_attested_for_all_apis`.
+// Let "all APIs" be Topics, Protected Audience, and Private Aggregation.
+// Have two sites attested for "all APIs", and one site attested for Private
+// Aggregation and Shared Storage only.
+TEST_F(PrivacySandboxAttestationsParserTest, AllAPIsProto) {
+  PrivacySandboxAttestationsProto proto;
+  ASSERT_TRUE(proto.site_attestations_size() == 0);
+
+  // Pretend that there are 3 APIs in the set of "all APIs".
+  proto.add_all_apis(TOPICS);
+  proto.add_all_apis(PROTECTED_AUDIENCE);
+  proto.add_all_apis(PRIVATE_AGGREGATION);
+
+  std::string site1 = "https://a.com";
+  std::string site2 = "https://b.com";
+  std::string site3 = "https://c.com";
+
+  proto.add_sites_attested_for_all_apis(site1);
+  proto.add_sites_attested_for_all_apis(site2);
+
+  PrivacySandboxAttestationsProto::PrivacySandboxAttestedAPIsProto attestation3;
+  attestation3.add_attested_apis(PRIVATE_AGGREGATION);
+  attestation3.add_attested_apis(SHARED_STORAGE);
+  (*proto.mutable_site_attestations())[site3] = attestation3;
+
+  std::string serialized_proto;
+  proto.SerializeToString(&serialized_proto);
+  std::istringstream iss(serialized_proto);
+
+  absl::optional<PrivacySandboxAttestationsMap> optional_map =
+      ParseAttestationsFromStream(iss);
+  ASSERT_TRUE(optional_map.has_value());
+  ASSERT_TRUE(optional_map->size() == 3UL);
+
+  const PrivacySandboxAttestationsGatedAPISet& site1apis =
+      (*optional_map)[net::SchemefulSite(GURL(site1))];
+  ASSERT_TRUE(site1apis.Has(PrivacySandboxAttestationsGatedAPI::kTopics));
+  ASSERT_TRUE(
+      site1apis.Has(PrivacySandboxAttestationsGatedAPI::kProtectedAudience));
+  ASSERT_TRUE(
+      site1apis.Has(PrivacySandboxAttestationsGatedAPI::kPrivateAggregation));
+  ASSERT_TRUE(site1apis.Size() == 3UL);
+
+  const PrivacySandboxAttestationsGatedAPISet& site2apis =
+      (*optional_map)[net::SchemefulSite(GURL(site2))];
+  ASSERT_TRUE(site2apis.Has(PrivacySandboxAttestationsGatedAPI::kTopics));
+  ASSERT_TRUE(
+      site2apis.Has(PrivacySandboxAttestationsGatedAPI::kProtectedAudience));
+  ASSERT_TRUE(
+      site2apis.Has(PrivacySandboxAttestationsGatedAPI::kPrivateAggregation));
+  ASSERT_TRUE(site2apis.Size() == 3UL);
+
+  const PrivacySandboxAttestationsGatedAPISet& site3apis =
+      (*optional_map)[net::SchemefulSite(GURL(site3))];
+  ASSERT_TRUE(
+      site3apis.Has(PrivacySandboxAttestationsGatedAPI::kPrivateAggregation));
+  ASSERT_TRUE(
+      site3apis.Has(PrivacySandboxAttestationsGatedAPI::kSharedStorage));
+  ASSERT_TRUE(site3apis.Size() == 2UL);
+}
+
+// Test that nothing goes terribly wrong when the proto has multiple mappings
+// for a single site (which should never happen in real use).
+TEST_F(PrivacySandboxAttestationsParserTest, RepeatedSiteProto) {
+  PrivacySandboxAttestationsProto proto;
+  ASSERT_TRUE(proto.site_attestations_size() == 0);
+
+  // Pretend that there are 3 APIs in the set of "all APIs".
+  proto.add_all_apis(TOPICS);
+  proto.add_all_apis(PROTECTED_AUDIENCE);
+  proto.add_all_apis(PRIVATE_AGGREGATION);
+
+  std::string site1 = "https://a.com";
+
+  proto.add_sites_attested_for_all_apis(site1);
+  proto.add_sites_attested_for_all_apis(site1);
+
+  PrivacySandboxAttestationsProto::PrivacySandboxAttestedAPIsProto attestation3;
+  attestation3.add_attested_apis(PRIVATE_AGGREGATION);
+  attestation3.add_attested_apis(SHARED_STORAGE);
+  (*proto.mutable_site_attestations())[site1] = attestation3;
+
+  std::string serialized_proto;
+  proto.SerializeToString(&serialized_proto);
+  std::istringstream iss(serialized_proto);
+
+  absl::optional<PrivacySandboxAttestationsMap> optional_map =
+      ParseAttestationsFromStream(iss);
+  ASSERT_TRUE(optional_map.has_value());
+  // The three mappings for the same site get deduplicated to 1.
+  ASSERT_TRUE(optional_map->size() == 1UL);
+
+  // The first mapping is the one that ends up being used, though this
+  // isn't particularly important.
+  const PrivacySandboxAttestationsGatedAPISet& site1apis =
+      (*optional_map)[net::SchemefulSite(GURL(site1))];
+  ASSERT_TRUE(site1apis.Has(PrivacySandboxAttestationsGatedAPI::kTopics));
+  ASSERT_TRUE(
+      site1apis.Has(PrivacySandboxAttestationsGatedAPI::kProtectedAudience));
+  ASSERT_TRUE(
+      site1apis.Has(PrivacySandboxAttestationsGatedAPI::kPrivateAggregation));
+  ASSERT_TRUE(site1apis.Size() == 3UL);
+}
+
+// Test that invalid API enums in `all_apis` are ignored.
+TEST_F(PrivacySandboxAttestationsParserTest, InvalidAllAPIsProto) {
+  PrivacySandboxAttestationsProto proto;
+  ASSERT_TRUE(proto.site_attestations_size() == 0);
+
+  // Pretend that there are 3 APIs in the set of "all APIs".
+  proto.add_all_apis(TOPICS);
+  // Add the default out of range value.
+  proto.add_all_apis(UNKNOWN);
+  proto.add_all_apis(PROTECTED_AUDIENCE);
+  proto.add_all_apis(PRIVATE_AGGREGATION);
+  // Add an explicitly out of range value. (This static_cast is undefined...)
+  proto.add_all_apis(static_cast<PrivacySandboxAttestationsGatedAPIProto>(192));
+  proto.add_all_apis(ATTRIBUTION_REPORTING);
+  proto.add_all_apis(SHARED_STORAGE);
+
+  std::string site1 = "https://a.com";
+  proto.add_sites_attested_for_all_apis(site1);
+
+  std::string serialized_proto;
+  proto.SerializeToString(&serialized_proto);
+  std::istringstream iss(serialized_proto);
+
+  absl::optional<PrivacySandboxAttestationsMap> optional_map =
+      ParseAttestationsFromStream(iss);
+  ASSERT_TRUE(optional_map.has_value());
+  ASSERT_TRUE(optional_map->size() == 1UL);
+
+  const PrivacySandboxAttestationsGatedAPISet& site1apis =
+      (*optional_map)[net::SchemefulSite(GURL(site1))];
+  ASSERT_TRUE(site1apis.Has(PrivacySandboxAttestationsGatedAPI::kTopics));
+  ASSERT_TRUE(
+      site1apis.Has(PrivacySandboxAttestationsGatedAPI::kProtectedAudience));
+  ASSERT_TRUE(
+      site1apis.Has(PrivacySandboxAttestationsGatedAPI::kPrivateAggregation));
+  ASSERT_TRUE(
+      site1apis.Has(PrivacySandboxAttestationsGatedAPI::kAttributionReporting));
+  ASSERT_TRUE(
+      site1apis.Has(PrivacySandboxAttestationsGatedAPI::kSharedStorage));
+  ASSERT_TRUE(site1apis.Size() == 5UL);
+}
+
+}  // namespace privacy_sandbox
diff --git a/components/privacy_sandbox/privacy_sandbox_attestations/proto/BUILD.gn b/components/privacy_sandbox/privacy_sandbox_attestations/proto/BUILD.gn
new file mode 100644
index 0000000..00f45cb
--- /dev/null
+++ b/components/privacy_sandbox/privacy_sandbox_attestations/proto/BUILD.gn
@@ -0,0 +1,10 @@
+# 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("//third_party/protobuf/proto_library.gni")
+
+proto_library("proto") {
+  sources = [ "privacy_sandbox_attestations.proto" ]
+  cc_generator_options = "lite=true:"
+}
diff --git a/components/privacy_sandbox/privacy_sandbox_attestations/proto/privacy_sandbox_attestations.proto b/components/privacy_sandbox/privacy_sandbox_attestations/proto/privacy_sandbox_attestations.proto
new file mode 100644
index 0000000..6bae63f
--- /dev/null
+++ b/components/privacy_sandbox/privacy_sandbox_attestations/proto/privacy_sandbox_attestations.proto
@@ -0,0 +1,34 @@
+// 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.
+
+syntax = "proto3";
+
+package privacy_sandbox;
+
+// Mirrors the C++ enum `PrivacySandboxAttestationsGatedAPI`.
+// Protobuf enums use the first value as the default for unexpected values,
+// so we use UNKNOWN to handle forward compatibility.
+enum PrivacySandboxAttestationsGatedAPIProto {
+  UNKNOWN = 0;
+  TOPICS = 1;
+  PROTECTED_AUDIENCE = 2;
+  PRIVATE_AGGREGATION = 3;
+  ATTRIBUTION_REPORTING = 4;
+  SHARED_STORAGE = 5;
+}
+
+message PrivacySandboxAttestationsProto {
+  message PrivacySandboxAttestedAPIsProto {
+    repeated PrivacySandboxAttestationsGatedAPIProto attested_apis = 1;
+  }
+  // A definition of what "all APIs" means for the current version.
+  repeated PrivacySandboxAttestationsGatedAPIProto all_apis = 1;
+
+  // A list of sites attested for all APIs.
+  repeated string sites_attested_for_all_apis = 2;
+
+  // A map from sites to lists of attested APIs. (Should only be used for sites
+  // that are not attested for all APIs.)
+  map<string, PrivacySandboxAttestedAPIsProto> site_attestations = 3;
+}
diff --git a/components/signin/public/android/java/src/org/chromium/components/signin/identitymanager/IdentityManager.java b/components/signin/public/android/java/src/org/chromium/components/signin/identitymanager/IdentityManager.java
index 6825dab..b8a3b1cd 100644
--- a/components/signin/public/android/java/src/org/chromium/components/signin/identitymanager/IdentityManager.java
+++ b/components/signin/public/android/java/src/org/chromium/components/signin/identitymanager/IdentityManager.java
@@ -23,8 +23,6 @@
  * IdentityManager provides access to native IdentityManager's public API to java components.
  */
 public class IdentityManager {
-    private static final String TAG = "IdentityManager";
-
     /**
      * IdentityManager.Observer is notified when the available account information are updated. This
      * is a subset of native's IdentityManager::Observer.
diff --git a/components/signin/public/android/java/src/org/chromium/components/signin/identitymanager/IdentityMutator.java b/components/signin/public/android/java/src/org/chromium/components/signin/identitymanager/IdentityMutator.java
index c3a29d3..b0beda7 100644
--- a/components/signin/public/android/java/src/org/chromium/components/signin/identitymanager/IdentityMutator.java
+++ b/components/signin/public/android/java/src/org/chromium/components/signin/identitymanager/IdentityMutator.java
@@ -18,8 +18,6 @@
  * information.
  */
 public class IdentityMutator {
-    private static final String TAG = "IdentityMutator";
-
     // Pointer to native IdentityMutator, not final because of destroy().
     private long mNativeIdentityMutator;
 
diff --git a/components/signin/public/base/signin_metrics.cc b/components/signin/public/base/signin_metrics.cc
index 6e71a0b7..17519b1 100644
--- a/components/signin/public/base/signin_metrics.cc
+++ b/components/signin/public/base/signin_metrics.cc
@@ -312,6 +312,9 @@
     case AccessPoint::ACCESS_POINT_NTP_LINK:
       base::RecordAction(base::UserMetricsAction("Signin_Signin_FromNTP"));
       break;
+    case AccessPoint::ACCESS_POINT_MENU:
+      base::RecordAction(base::UserMetricsAction("Signin_Signin_FromMenu"));
+      break;
     case AccessPoint::ACCESS_POINT_SETTINGS:
       base::RecordAction(base::UserMetricsAction("Signin_Signin_FromSettings"));
       break;
@@ -351,10 +354,6 @@
       base::RecordAction(
           base::UserMetricsAction("Signin_Signin_FromCloudPrint"));
       break;
-    case AccessPoint::ACCESS_POINT_CONTENT_AREA:
-      base::RecordAction(
-          base::UserMetricsAction("Signin_Signin_FromContentArea"));
-      break;
     case AccessPoint::ACCESS_POINT_SIGNIN_PROMO:
       base::RecordAction(
           base::UserMetricsAction("Signin_Signin_FromSigninPromo"));
@@ -490,6 +489,9 @@
     case AccessPoint::ACCESS_POINT_NTP_LINK:
       base::RecordAction(base::UserMetricsAction("Signin_Impression_FromNTP"));
       break;
+    case AccessPoint::ACCESS_POINT_MENU:
+      base::RecordAction(base::UserMetricsAction("Signin_Impression_FromMenu"));
+      break;
     case AccessPoint::ACCESS_POINT_SETTINGS:
       base::RecordAction(
           base::UserMetricsAction("Signin_Impression_FromSettings"));
@@ -595,7 +597,6 @@
           base::UserMetricsAction("Signin_Impression_FromSetUpList"));
       break;
     case AccessPoint::ACCESS_POINT_ENTERPRISE_SIGNOUT_COORDINATOR:
-    case AccessPoint::ACCESS_POINT_CONTENT_AREA:
     case AccessPoint::ACCESS_POINT_EXTENSIONS:
     case AccessPoint::ACCESS_POINT_SUPERVISED_USER:
     case AccessPoint::ACCESS_POINT_UNKNOWN:
@@ -634,6 +635,7 @@
     // But not these access points.
     case AccessPoint::ACCESS_POINT_START_PAGE:
     case AccessPoint::ACCESS_POINT_NTP_LINK:
+    case AccessPoint::ACCESS_POINT_MENU:
     case AccessPoint::ACCESS_POINT_SETTINGS:
     case AccessPoint::ACCESS_POINT_SUPERVISED_USER:
     case AccessPoint::ACCESS_POINT_EXTENSION_INSTALL_BUBBLE:
@@ -644,7 +646,6 @@
     case AccessPoint::ACCESS_POINT_USER_MANAGER:
     case AccessPoint::ACCESS_POINT_DEVICES_PAGE:
     case AccessPoint::ACCESS_POINT_CLOUD_PRINT:
-    case AccessPoint::ACCESS_POINT_CONTENT_AREA:
     case AccessPoint::ACCESS_POINT_SIGNIN_PROMO:
     case AccessPoint::ACCESS_POINT_RECENT_TABS:
     case AccessPoint::ACCESS_POINT_UNKNOWN:
diff --git a/components/signin/public/base/signin_metrics.h b/components/signin/public/base/signin_metrics.h
index f1abf51..69a56cb 100644
--- a/components/signin/public/base/signin_metrics.h
+++ b/components/signin/public/base/signin_metrics.h
@@ -132,7 +132,8 @@
 enum class AccessPoint : int {
   ACCESS_POINT_START_PAGE = 0,
   ACCESS_POINT_NTP_LINK = 1,
-  // ACCESS_POINT_MENU = 2, no longer used.
+  // Access point from the three dot app menu.
+  ACCESS_POINT_MENU = 2,
   ACCESS_POINT_SETTINGS = 3,
   ACCESS_POINT_SUPERVISED_USER = 4,
   ACCESS_POINT_EXTENSION_INSTALL_BUBBLE = 5,
@@ -144,7 +145,7 @@
   ACCESS_POINT_USER_MANAGER = 11,
   ACCESS_POINT_DEVICES_PAGE = 12,
   ACCESS_POINT_CLOUD_PRINT = 13,
-  ACCESS_POINT_CONTENT_AREA = 14,
+  // ACCESS_POINT_CONTENT_AREA = 14, no longer used.
   ACCESS_POINT_SIGNIN_PROMO = 15,
   ACCESS_POINT_RECENT_TABS = 16,
   // This should never have been used to get signin URL.
diff --git a/components/signin/public/base/signin_metrics_unittest.cc b/components/signin/public/base/signin_metrics_unittest.cc
index 174c7d0f..29d82c1f 100644
--- a/components/signin/public/base/signin_metrics_unittest.cc
+++ b/components/signin/public/base/signin_metrics_unittest.cc
@@ -16,6 +16,7 @@
 const AccessPoint kAccessPointsThatSupportUserAction[] = {
     AccessPoint::ACCESS_POINT_START_PAGE,
     AccessPoint::ACCESS_POINT_NTP_LINK,
+    AccessPoint::ACCESS_POINT_MENU,
     AccessPoint::ACCESS_POINT_SETTINGS,
     AccessPoint::ACCESS_POINT_SUPERVISED_USER,
     AccessPoint::ACCESS_POINT_EXTENSION_INSTALL_BUBBLE,
@@ -26,7 +27,6 @@
     AccessPoint::ACCESS_POINT_USER_MANAGER,
     AccessPoint::ACCESS_POINT_DEVICES_PAGE,
     AccessPoint::ACCESS_POINT_CLOUD_PRINT,
-    AccessPoint::ACCESS_POINT_CONTENT_AREA,
     AccessPoint::ACCESS_POINT_SIGNIN_PROMO,
     AccessPoint::ACCESS_POINT_RECENT_TABS,
     AccessPoint::ACCESS_POINT_UNKNOWN,
@@ -49,6 +49,7 @@
 const AccessPoint kAccessPointsThatSupportImpression[] = {
     AccessPoint::ACCESS_POINT_START_PAGE,
     AccessPoint::ACCESS_POINT_NTP_LINK,
+    AccessPoint::ACCESS_POINT_MENU,
     AccessPoint::ACCESS_POINT_SETTINGS,
     AccessPoint::ACCESS_POINT_EXTENSION_INSTALL_BUBBLE,
     AccessPoint::ACCESS_POINT_BOOKMARK_BUBBLE,
@@ -80,6 +81,8 @@
         return "StartPage";
       case AccessPoint::ACCESS_POINT_NTP_LINK:
         return "NTP";
+      case AccessPoint::ACCESS_POINT_MENU:
+        return "Menu";
       case AccessPoint::ACCESS_POINT_SETTINGS:
         return "Settings";
       case AccessPoint::ACCESS_POINT_SUPERVISED_USER:
@@ -100,8 +103,6 @@
         return "DevicesPage";
       case AccessPoint::ACCESS_POINT_CLOUD_PRINT:
         return "CloudPrint";
-      case AccessPoint::ACCESS_POINT_CONTENT_AREA:
-        return "ContentArea";
       case AccessPoint::ACCESS_POINT_SIGNIN_PROMO:
         return "SigninPromo";
       case AccessPoint::ACCESS_POINT_RECENT_TABS:
diff --git a/components/subresource_filter/android/java/src/org/chromium/components/subresource_filter/AdsBlockedDialogTest.java b/components/subresource_filter/android/java/src/org/chromium/components/subresource_filter/AdsBlockedDialogTest.java
index 1d876c68..badc9924 100644
--- a/components/subresource_filter/android/java/src/org/chromium/components/subresource_filter/AdsBlockedDialogTest.java
+++ b/components/subresource_filter/android/java/src/org/chromium/components/subresource_filter/AdsBlockedDialogTest.java
@@ -35,8 +35,6 @@
 /** Tests for ads blocked dialog. */
 @RunWith(BaseRobolectricTestRunner.class)
 public class AdsBlockedDialogTest {
-    private static final long NATIVE_PTR = 1;
-
     @Rule
     public MockitoRule mMockitoRule = MockitoJUnit.rule();
 
diff --git a/components/user_education/views/BUILD.gn b/components/user_education/views/BUILD.gn
index 9a7711a..6c0a9cd 100644
--- a/components/user_education/views/BUILD.gn
+++ b/components/user_education/views/BUILD.gn
@@ -14,6 +14,8 @@
     "help_bubble_view.h",
     "new_badge_label.cc",
     "new_badge_label.h",
+    "toggle_tracked_element_attention_utils.cc",
+    "toggle_tracked_element_attention_utils.h",
   ]
 
   if (is_mac) {
diff --git a/components/user_education/views/help_bubble_factory_views.cc b/components/user_education/views/help_bubble_factory_views.cc
index 19477bc..05de23e 100644
--- a/components/user_education/views/help_bubble_factory_views.cc
+++ b/components/user_education/views/help_bubble_factory_views.cc
@@ -15,6 +15,7 @@
 #include "components/user_education/common/user_education_class_properties.h"
 #include "components/user_education/views/help_bubble_delegate.h"
 #include "components/user_education/views/help_bubble_view.h"
+#include "components/user_education/views/toggle_tracked_element_attention_utils.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
 #include "ui/base/interaction/element_identifier.h"
 #include "ui/base/interaction/element_tracker.h"
@@ -144,6 +145,7 @@
   if (!anchor_view)
     return;
   anchor_view->SetProperty(kHasInProductHelpPromoKey, false);
+  MaybeRemoveAttentionStateFromTrackedElement(anchor_view);
 }
 
 void HelpBubbleViews::CloseBubbleImpl() {
@@ -218,6 +220,9 @@
         accelerator, ui::AcceleratorManager::HandlerPriority::kNormalPriority,
         result.get());
   }
+  if (result) {
+    MaybeApplyAttentionStateToTrackedElement(anchor.view);
+  }
   return result;
 }
 
diff --git a/components/user_education/views/toggle_tracked_element_attention_utils.cc b/components/user_education/views/toggle_tracked_element_attention_utils.cc
new file mode 100644
index 0000000..f22ff35b
--- /dev/null
+++ b/components/user_education/views/toggle_tracked_element_attention_utils.cc
@@ -0,0 +1,31 @@
+// 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 "components/user_education/views/toggle_tracked_element_attention_utils.h"
+#include "ui/views/animation/ink_drop.h"
+#include "ui/views/animation/ink_drop_host.h"
+
+namespace user_education {
+
+void MaybeRemoveAttentionStateFromTrackedElement(views::View* tracked_element) {
+  views::InkDropHost* const ink_drop_host =
+      views::InkDrop::Get(tracked_element);
+  if (!ink_drop_host) {
+    return;
+  }
+
+  ink_drop_host->ToggleAttentionState(false);
+}
+
+void MaybeApplyAttentionStateToTrackedElement(views::View* tracked_element) {
+  views::InkDropHost* const ink_drop_host =
+      views::InkDrop::Get(tracked_element);
+  if (!ink_drop_host) {
+    return;
+  }
+
+  ink_drop_host->ToggleAttentionState(true);
+}
+
+}  // namespace user_education
diff --git a/components/user_education/views/toggle_tracked_element_attention_utils.h b/components/user_education/views/toggle_tracked_element_attention_utils.h
new file mode 100644
index 0000000..25f4622
--- /dev/null
+++ b/components/user_education/views/toggle_tracked_element_attention_utils.h
@@ -0,0 +1,19 @@
+// 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 COMPONENTS_USER_EDUCATION_VIEWS_TOGGLE_TRACKED_ELEMENT_ATTENTION_UTILS_H_
+#define COMPONENTS_USER_EDUCATION_VIEWS_TOGGLE_TRACKED_ELEMENT_ATTENTION_UTILS_H_
+
+namespace views {
+class View;
+}
+
+namespace user_education {
+
+void MaybeRemoveAttentionStateFromTrackedElement(views::View* tracked_element);
+void MaybeApplyAttentionStateToTrackedElement(views::View* tracked_element);
+
+}  // namespace user_education
+
+#endif  // COMPONENTS_USER_EDUCATION_VIEWS_TOGGLE_TRACKED_ELEMENT_ATTENTION_UTILS_H_
diff --git a/components/variations/android/java/src/org/chromium/components/variations/firstrun/VariationsSeedFetcher.java b/components/variations/android/java/src/org/chromium/components/variations/firstrun/VariationsSeedFetcher.java
index aa3e5691..4a2afc8 100644
--- a/components/variations/android/java/src/org/chromium/components/variations/firstrun/VariationsSeedFetcher.java
+++ b/components/variations/android/java/src/org/chromium/components/variations/firstrun/VariationsSeedFetcher.java
@@ -100,8 +100,7 @@
     public static final int SEED_FETCH_RESULT_DELTA_PATCH_EXCEPTION = -6;
     @VisibleForTesting
     public static final int SEED_FETCH_RESULT_INVALID_IM_HEADER = -5;
-
-    private static final int SEED_FETCH_RESULT_INVALID_DATE_HEADER = -4;
+    // private static final int SEED_FETCH_RESULT_INVALID_DATE_HEADER = -4;
     private static final int SEED_FETCH_RESULT_UNKNOWN_HOST_EXCEPTION = -3;
     private static final int SEED_FETCH_RESULT_TIMEOUT = -2;
     @VisibleForTesting
diff --git a/components/vector_icons/BUILD.gn b/components/vector_icons/BUILD.gn
index a594d5b..2d0fc6c 100644
--- a/components/vector_icons/BUILD.gn
+++ b/components/vector_icons/BUILD.gn
@@ -191,6 +191,7 @@
     "sync.icon",
     "sync_chrome_refresh.icon",
     "sync_off_chrome_refresh.icon",
+    "sync_problem_chrome_refresh.icon",
     "tenancy.icon",
     "troubleshoot.icon",
     "usb.icon",
diff --git a/components/vector_icons/sync_problem_chrome_refresh.icon b/components/vector_icons/sync_problem_chrome_refresh.icon
new file mode 100644
index 0000000..3c0fd0f
--- /dev/null
+++ b/components/vector_icons/sync_problem_chrome_refresh.icon
@@ -0,0 +1,57 @@
+// 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.
+
+CANVAS_DIMENSIONS, 16,
+MOVE_TO, 2.67f, 12.99f,
+R_V_LINE_TO, -1.34f,
+R_H_LINE_TO, 0.93f,
+R_LINE_TO, 0.11f, 0.09f,
+R_ARC_TO, 5.83f, 5.83f, 0, 0, 1, -1.23f, -1.68f,
+ARC_TO, 4.67f, 4.67f, 0, 0, 1, 2.02f, 8,
+R_CUBIC_TO, 0, -1.22f, 0.39f, -2.29f, 1.16f, -3.2f,
+R_ARC_TO, 5.07f, 5.07f, 0, 0, 1, 2.91f, -1.73f,
+R_V_LINE_TO, 1.68f,
+R_CUBIC_TO, -0.71f, 0.2f, -1.29f, 0.59f, -1.75f, 1.19f,
+ARC_TO, 3.27f, 3.27f, 0, 0, 0, 3.66f, 8,
+R_CUBIC_TO, 0, 0.5f, 0.1f, 0.96f, 0.31f, 1.38f,
+R_CUBIC_TO, 0.21f, 0.43f, 0.48f, 0.8f, 0.82f, 1.11f,
+R_LINE_TO, -0.07f, -0.07f,
+R_V_LINE_TO, -0.81f,
+R_H_LINE_TO, 1.34f,
+R_V_LINE_TO, 3.39f,
+CLOSE,
+MOVE_TO, 8, 11.31f,
+R_CUBIC_TO, -0.2f, 0, -0.37f, -0.07f, -0.5f, -0.21f,
+R_ARC_TO, 0.68f, 0.68f, 0, 0, 1, -0.21f, -0.5f,
+R_CUBIC_TO, 0, -0.2f, 0.07f, -0.37f, 0.21f, -0.51f,
+ARC_TO, 0.69f, 0.69f, 0, 0, 1, 8, 9.89f,
+R_CUBIC_TO, 0.2f, 0, 0.37f, 0.07f, 0.5f, 0.21f,
+R_CUBIC_TO, 0.14f, 0.14f, 0.21f, 0.31f, 0.21f, 0.5f,
+R_CUBIC_TO, 0, 0.2f, -0.07f, 0.37f, -0.21f, 0.51f,
+R_ARC_TO, 0.69f, 0.69f, 0, 0, 1, -0.5f, 0.21f,
+CLOSE,
+R_MOVE_TO, -0.68f, -2.44f,
+V_LINE_TO, 4.66f,
+R_H_LINE_TO, 1.35f,
+R_V_LINE_TO, 4.22f,
+CLOSE,
+R_MOVE_TO, 2.57f, 4.05f,
+R_V_LINE_TO, -1.68f,
+R_CUBIC_TO, 0.71f, -0.2f, 1.29f, -0.59f, 1.75f, -1.19f,
+ARC_TO, 3.27f, 3.27f, 0, 0, 0, 12.32f, 8,
+R_ARC_TO, 3.17f, 3.17f, 0, 0, 0, -0.3f, -1.38f,
+R_ARC_TO, 3.51f, 3.51f, 0, 0, 0, -0.82f, -1.1f,
+R_LINE_TO, 0.07f, 0.07f,
+R_V_LINE_TO, 0.81f,
+H_LINE_TO, 9.93f,
+V_LINE_TO, 3.01f,
+R_H_LINE_TO, 3.39f,
+R_V_LINE_TO, 1.34f,
+R_H_LINE_TO, -0.93f,
+R_LINE_TO, -0.11f, -0.09f,
+R_CUBIC_TO, 0.52f, 0.47f, 0.93f, 1.02f, 1.23f, 1.66f,
+R_CUBIC_TO, 0.3f, 0.64f, 0.45f, 1.34f, 0.45f, 2.08f,
+R_CUBIC_TO, 0, 1.22f, -0.39f, 2.29f, -1.16f, 3.2f,
+R_ARC_TO, 5.07f, 5.07f, 0, 0, 1, -2.91f, 1.73f,
+CLOSE
diff --git a/components/viz/service/display/surface_aggregator.cc b/components/viz/service/display/surface_aggregator.cc
index ed9c32a4..0f0763f 100644
--- a/components/viz/service/display/surface_aggregator.cc
+++ b/components/viz/service/display/surface_aggregator.cc
@@ -1647,6 +1647,7 @@
   if (render_pass.filters.HasFilterThatMovesPixels() ||
       (parent_pass && parent_pass->aggregation().in_pixel_moving_filter_pass)) {
     resolved_pass.aggregation().in_pixel_moving_filter_pass = true;
+    stats_->has_pixel_moving_filter = true;
   }
 
   const FrameDamageType damage_type = resolved_frame.GetFrameDamageType();
@@ -1971,6 +1972,7 @@
     // pass, but other attributes related to the embedding hierarchy are still
     // important to propagate.
     if (resolved_pass.IsUnembedded()) {
+      stats_->has_unembedded_pass = true;
       PrewalkRenderPass(resolved_frame, resolved_pass,
                         /*damage_from_parent=*/gfx::Rect(),
                         /*target_to_root_transform=*/gfx::Transform(),
@@ -2279,6 +2281,18 @@
       stats_->declare_resources_time, kHistogramMinTime, kHistogramMaxTime,
       kHistogramTimeBuckets);
 
+  UMA_HISTOGRAM_BOOLEAN("Compositing.SurfaceAggregator.HasCopyRequestsPerFrame",
+                        has_copy_requests_);
+  UMA_HISTOGRAM_BOOLEAN(
+      "Compositing.SurfaceAggregator.HasPixelMovingFiltersPerFrame",
+      stats_->has_pixel_moving_filter);
+  UMA_HISTOGRAM_BOOLEAN(
+      "Compositing.SurfaceAggregator.HasPixelMovingBackdropFiltersPerFrame",
+      has_pixel_moving_backdrop_filter_);
+  UMA_HISTOGRAM_BOOLEAN(
+      "Compositing.SurfaceAggregator.HasUnembeddedRenderPassesPerFrame",
+      stats_->has_unembedded_pass);
+
   stats_.reset();
 }
 
diff --git a/components/viz/service/display/surface_aggregator.h b/components/viz/service/display/surface_aggregator.h
index 3de36d0..9bcf34f 100644
--- a/components/viz/service/display/surface_aggregator.h
+++ b/components/viz/service/display/surface_aggregator.h
@@ -123,6 +123,11 @@
   struct AggregateStatistics {
     int prewalked_surface_count = 0;
     int copied_surface_count = 0;
+    // True if the current frame contains a pixel-moving foreground filter
+    // render pass.
+    bool has_pixel_moving_filter = false;
+    // True if the current frame contains a unembedded render pass.
+    bool has_unembedded_pass = false;
 
     base::TimeDelta prewalk_time;
     base::TimeDelta copy_time;
diff --git a/components/webapps/browser/android/java/src/org/chromium/components/webapps/AppBannerManager.java b/components/webapps/browser/android/java/src/org/chromium/components/webapps/AppBannerManager.java
index ec6a798..652e6db 100644
--- a/components/webapps/browser/android/java/src/org/chromium/components/webapps/AppBannerManager.java
+++ b/components/webapps/browser/android/java/src/org/chromium/components/webapps/AppBannerManager.java
@@ -49,8 +49,6 @@
     /** The key to use to store and retrieve (from the menu data) what was shown in the menu. */
     public static final String MENU_TITLE_KEY = "AppMenuTitleShown";
 
-    private static final String TAG = "AppBannerManager";
-
     /** Retrieves information about a given package. */
     private static AppDetailsDelegate sAppDetailsDelegate;
 
diff --git a/components/webauthn/android/java/src/org/chromium/components/webauthn/Fido2ApiCall.java b/components/webauthn/android/java/src/org/chromium/components/webauthn/Fido2ApiCall.java
index 5bbdfb3..e00d1bcf 100644
--- a/components/webauthn/android/java/src/org/chromium/components/webauthn/Fido2ApiCall.java
+++ b/components/webauthn/android/java/src/org/chromium/components/webauthn/Fido2ApiCall.java
@@ -85,8 +85,6 @@
     public static final int TRANSACTION_HYBRID_SIGN = IBinder.FIRST_CALL_TRANSACTION + 4;
     public static final int TRANSACTION_GET_LINK_INFO = IBinder.FIRST_CALL_TRANSACTION + 0;
 
-    private static final String TAG = "Fido2ApiCall";
-
     private static final String BROWSER_DESCRIPTOR =
             "com.google.android.gms.fido.fido2.internal.privileged.IFido2PrivilegedService";
     private static final String BROWSER_START_SERVICE_ACTION =
diff --git a/components/webauthn/android/java/src/org/chromium/components/webauthn/Fido2ApiTestHelper.java b/components/webauthn/android/java/src/org/chromium/components/webauthn/Fido2ApiTestHelper.java
index 9d31d82..f9cdd6c 100644
--- a/components/webauthn/android/java/src/org/chromium/components/webauthn/Fido2ApiTestHelper.java
+++ b/components/webauthn/android/java/src/org/chromium/components/webauthn/Fido2ApiTestHelper.java
@@ -99,7 +99,6 @@
  */
 public class Fido2ApiTestHelper {
     // Test data.
-    private static final String FILLER_ERROR_MSG = "Error Error";
     private static final int OBJECT_MAGIC = 20293;
 
     /**
@@ -348,7 +347,6 @@
     private static final byte[] TEST_CLIENT_DATA_JSON = new byte[] {4, 5, 6};
     private static final byte[] TEST_AUTHENTICATOR_DATA = new byte[] {7, 8, 9};
     private static final byte[] TEST_SIGNATURE = new byte[] {10, 11, 12};
-    private static final long TIMEOUT_SAFETY_MARGIN_MS = scaleTimeout(TimeUnit.SECONDS.toMillis(1));
     private static final long TIMEOUT_MS = scaleTimeout(TimeUnit.SECONDS.toMillis(1));
     private static final int[] TEST_USER_VERIFICATION_METHOD = new int[] {0x00000002, 0x00000200};
     private static final short[] TEST_KEY_PROTECTION_TYPE = new short[] {0x0002, 0x0001};
diff --git a/components/webxr/android/java/src/org/chromium/components/webxr/XrDelegateImpl.java b/components/webxr/android/java/src/org/chromium/components/webxr/XrDelegateImpl.java
index 38ed303..01984788 100644
--- a/components/webxr/android/java/src/org/chromium/components/webxr/XrDelegateImpl.java
+++ b/components/webxr/android/java/src/org/chromium/components/webxr/XrDelegateImpl.java
@@ -17,9 +17,6 @@
  * interfaces.
  */
 public class XrDelegateImpl implements XrDelegate {
-    private static final String TAG = "XrDelegateImpl";
-    private static final boolean DEBUG_LOGS = false;
-
     private @SessionType int mActiveSession = SessionType.NONE;
 
     private ObservableSupplierImpl<Boolean> mHasActiveSessionSupplier =
diff --git a/content/browser/attribution_reporting/attribution_config.cc b/content/browser/attribution_reporting/attribution_config.cc
index f18c7fc..c0ab211a 100644
--- a/content/browser/attribution_reporting/attribution_config.cc
+++ b/content/browser/attribution_reporting/attribution_config.cc
@@ -8,6 +8,7 @@
 
 #include "base/metrics/field_trial_params.h"
 #include "base/time/time.h"
+#include "content/browser/attribution_reporting/destination_throttler.h"
 #include "third_party/blink/public/common/features.h"
 
 namespace content {
@@ -43,6 +44,10 @@
     return false;
   }
 
+  if (!throttler_policy.Validate()) {
+    return false;
+  }
+
   return true;
 }
 
@@ -160,6 +165,15 @@
   return true;
 }
 
+AttributionConfig::AttributionConfig() = default;
+AttributionConfig::AttributionConfig(const AttributionConfig&) = default;
+AttributionConfig::AttributionConfig(AttributionConfig&&) = default;
+AttributionConfig::~AttributionConfig() = default;
+
+AttributionConfig& AttributionConfig::operator=(const AttributionConfig&) =
+    default;
+AttributionConfig& AttributionConfig::operator=(AttributionConfig&&) = default;
+
 AttributionConfig::EventLevelLimit::EventLevelLimit() = default;
 AttributionConfig::EventLevelLimit::EventLevelLimit(const EventLevelLimit&) =
     default;
diff --git a/content/browser/attribution_reporting/attribution_config.h b/content/browser/attribution_reporting/attribution_config.h
index 81ed530..9a65545 100644
--- a/content/browser/attribution_reporting/attribution_config.h
+++ b/content/browser/attribution_reporting/attribution_config.h
@@ -8,6 +8,7 @@
 #include <stdint.h>
 
 #include "base/time/time.h"
+#include "content/browser/attribution_reporting/destination_throttler.h"
 #include "content/common/content_export.h"
 
 namespace content {
@@ -144,6 +145,15 @@
     // should also be updated.
   };
 
+  AttributionConfig();
+
+  AttributionConfig(const AttributionConfig&);
+  AttributionConfig(AttributionConfig&&);
+  ~AttributionConfig();
+
+  AttributionConfig& operator=(const AttributionConfig&);
+  AttributionConfig& operator=(AttributionConfig&&);
+
   // Returns true if this config is valid.
   [[nodiscard]] bool Validate() const;
 
@@ -158,6 +168,7 @@
   RateLimitConfig rate_limit;
   EventLevelLimit event_level_limit;
   AggregateLimit aggregate_limit;
+  DestinationThrottler::Policy throttler_policy;
 
   // When adding new members, the corresponding `Validate()` definition and
   // `operator==()` definition in `attribution_interop_parser_unittest.cc`
diff --git a/content/browser/attribution_reporting/attribution_data_host_manager_impl.cc b/content/browser/attribution_reporting/attribution_data_host_manager_impl.cc
index c202278c..bb02f8e 100644
--- a/content/browser/attribution_reporting/attribution_data_host_manager_impl.cc
+++ b/content/browser/attribution_reporting/attribution_data_host_manager_impl.cc
@@ -119,32 +119,63 @@
         reporting_origin(std::move(reporting_origin)) {}
 };
 
+class RegistrationNavigationContext {
+ public:
+  RegistrationNavigationContext(int64_t navigation_id,
+                                AttributionInputEvent input_event)
+      : navigation_id_(navigation_id), input_event_(std::move(input_event)) {}
+
+  ~RegistrationNavigationContext() = default;
+
+  RegistrationNavigationContext(const RegistrationNavigationContext&) = delete;
+  RegistrationNavigationContext& operator=(
+      const RegistrationNavigationContext&) = delete;
+
+  RegistrationNavigationContext(RegistrationNavigationContext&&) = default;
+  RegistrationNavigationContext& operator=(RegistrationNavigationContext&&) =
+      default;
+
+  int64_t navigation_id() const { return navigation_id_; }
+
+  const AttributionInputEvent& input_event() const { return input_event_; }
+
+ private:
+  // We store the navigation_id on the registration context to support trigger
+  // buffering. Will not change over the course of the redirect chain.
+  // Logically const.
+  int64_t navigation_id_;
+
+  // Input event associated with the navigation for navigation source data
+  // hosts. The underlying Java object will be null for event sources.
+  // Logically const.
+  AttributionInputEvent input_event_;
+};
+
 }  // namespace
 
-class AttributionDataHostManagerImpl::ReceiverContext {
+class AttributionDataHostManagerImpl::RegistrationContext {
  public:
-  ReceiverContext(SuitableOrigin context_origin,
-                  RegistrationType registration_type,
-                  bool is_within_fenced_frame,
-                  AttributionInputEvent input_event,
-                  GlobalRenderFrameHostId render_frame_id,
-                  absl::optional<int64_t> navigation_id)
+  RegistrationContext(SuitableOrigin context_origin,
+                      RegistrationType registration_type,
+                      bool is_within_fenced_frame,
+                      GlobalRenderFrameHostId render_frame_id,
+                      absl::optional<RegistrationNavigationContext> navigation)
       : context_origin_(std::move(context_origin)),
         registration_type_(registration_type),
         is_within_fenced_frame_(is_within_fenced_frame),
-        input_event_(std::move(input_event)),
         render_frame_id_(render_frame_id),
-        navigation_id_(navigation_id) {
-    DCHECK(!navigation_id_ || registration_type_ == RegistrationType::kSource);
+        navigation_(std::move(navigation)) {
+    CHECK(!navigation_.has_value() ||
+          registration_type_ == RegistrationType::kSource);
   }
 
-  ~ReceiverContext() = default;
+  ~RegistrationContext() = default;
 
-  ReceiverContext(const ReceiverContext&) = delete;
-  ReceiverContext& operator=(const ReceiverContext&) = delete;
+  RegistrationContext(const RegistrationContext&) = delete;
+  RegistrationContext& operator=(const RegistrationContext&) = delete;
 
-  ReceiverContext(ReceiverContext&&) = default;
-  ReceiverContext& operator=(ReceiverContext&&) = default;
+  RegistrationContext(RegistrationContext&&) = default;
+  RegistrationContext& operator=(RegistrationContext&&) = default;
 
   const SuitableOrigin& context_origin() const { return context_origin_; }
 
@@ -152,11 +183,11 @@
 
   bool is_within_fenced_frame() const { return is_within_fenced_frame_; }
 
-  absl::optional<int64_t> navigation_id() const { return navigation_id_; }
-
   GlobalRenderFrameHostId render_frame_id() const { return render_frame_id_; }
 
-  const AttributionInputEvent& input_event() const { return input_event_; }
+  const absl::optional<RegistrationNavigationContext>& navigation() const {
+    return navigation_;
+  }
 
  private:
   // Top-level origin the data host was created in.
@@ -170,18 +201,13 @@
   // Logically const.
   bool is_within_fenced_frame_;
 
-  // Input event associated with the navigation for navigation source data
-  // hosts. The underlying Java object will be null for event sources.
-  // Logically const.
-  AttributionInputEvent input_event_;
-
   // The ID of the topmost render frame host.
   // Logically const.
   GlobalRenderFrameHostId render_frame_id_;
 
-  // When the receiver is tied to a navigation, we store the navigation_id
-  // to be able to bind deferred receivers when it disconnects.
-  absl::optional<int64_t> navigation_id_;
+  // When the registration is tied to a navigation, we store additional context
+  // on the navigation.
+  absl::optional<RegistrationNavigationContext> navigation_;
 };
 
 struct AttributionDataHostManagerImpl::DeferredReceiverTimeout {
@@ -195,7 +221,7 @@
 
 struct AttributionDataHostManagerImpl::DeferredReceiver {
   mojo::PendingReceiver<blink::mojom::AttributionDataHost> data_host;
-  ReceiverContext context;
+  RegistrationContext context;
   base::TimeTicks initial_registration_time = base::TimeTicks::Now();
 };
 
@@ -206,30 +232,8 @@
 
 class AttributionDataHostManagerImpl::SourceRegistrations {
  public:
-  struct ForegroundNavigation {
-    blink::AttributionSrcToken attribution_src_token;
-
-    // Will not change over the course of the redirect chain.
-    int64_t navigation_id;
-  };
-
-  struct Beacon {
-    BeaconId id;
-    absl::optional<int64_t> navigation_id;
-  };
-
-  using Data = absl::variant<ForegroundNavigation, Beacon>;
-
-  SourceRegistrations(SuitableOrigin source_origin,
-                      bool is_within_fenced_frame,
-                      AttributionInputEvent input_event,
-                      GlobalRenderFrameHostId render_frame_id,
-                      Data data)
-      : source_origin_(std::move(source_origin)),
-        is_within_fenced_frame_(is_within_fenced_frame),
-        input_event_(std::move(input_event)),
-        render_frame_id_(render_frame_id),
-        data_(data) {}
+  SourceRegistrations(SourceRegistrationsId id, RegistrationContext context)
+      : id_(id), context_(std::move(context)) {}
 
   SourceRegistrations(const SourceRegistrations&) = delete;
   SourceRegistrations& operator=(const SourceRegistrations&) = delete;
@@ -237,7 +241,9 @@
   SourceRegistrations(SourceRegistrations&&) = default;
   SourceRegistrations& operator=(SourceRegistrations&&) = default;
 
-  const SuitableOrigin& source_origin() const { return source_origin_; }
+  const SuitableOrigin& source_origin() const {
+    return context_.context_origin();
+  }
 
   bool has_pending_decodes() const {
     return !pending_os_decodes_.empty() || !pending_web_decodes_.empty();
@@ -245,23 +251,29 @@
 
   bool registrations_complete() const { return registrations_complete_; }
 
-  bool is_within_fenced_frame() const { return is_within_fenced_frame_; }
-
-  absl::optional<int64_t> navigation_id() const {
-    return absl::visit(
-        base::Overloaded{
-            [](const ForegroundNavigation& navigation) {
-              return absl::make_optional(navigation.navigation_id);
-            },
-            [](const Beacon& beacon) { return beacon.navigation_id; }},
-        data_);
+  bool is_within_fenced_frame() const {
+    return context_.is_within_fenced_frame();
   }
 
-  const AttributionInputEvent& input_event() const { return input_event_; }
+  absl::optional<int64_t> navigation_id() const {
+    if (context_.navigation().has_value()) {
+      return context_.navigation()->navigation_id();
+    }
 
-  GlobalRenderFrameHostId render_frame_id() const { return render_frame_id_; }
+    return absl::nullopt;
+  }
 
-  const Data& data() const { return data_; }
+  const AttributionInputEvent* input_event() const {
+    if (context_.navigation().has_value()) {
+      return &context_.navigation()->input_event();
+    }
+
+    return nullptr;
+  }
+
+  GlobalRenderFrameHostId render_frame_id() const {
+    return context_.render_frame_id();
+  }
 
   const base::circular_deque<PendingWebDecode>& pending_web_decodes() const {
     return pending_web_decodes_;
@@ -298,41 +310,19 @@
     return a < b.Id();
   }
 
-  SourceRegistrationsId Id() const {
-    return absl::visit(
-        base::Overloaded{
-            [](const ForegroundNavigation& navigation) {
-              return SourceRegistrationsId(navigation.attribution_src_token);
-            },
-            [](const Beacon& beacon) {
-              return SourceRegistrationsId(beacon.id);
-            },
-        },
-        data_);
-  }
+  SourceRegistrationsId Id() const { return id_; }
 
  private:
-  // Source origin to use for all registrations on a navigation redirect or
-  // beacon chain. Will not change over the course of the chain.
-  SuitableOrigin source_origin_;
-
   // True if navigation or beacon has completed.
   bool registrations_complete_ = false;
 
-  // Whether the registration was initiated within a fenced frame.
-  bool is_within_fenced_frame_;
-
-  // Input event associated with the navigation.
-  // The underlying Java object will be null for event beacons.
-  AttributionInputEvent input_event_;
-
-  GlobalRenderFrameHostId render_frame_id_;
-
-  Data data_;
+  SourceRegistrationsId id_;
 
   base::circular_deque<PendingWebDecode> pending_web_decodes_;
 
   base::circular_deque<std::string> pending_os_decodes_;
+
+  RegistrationContext context_;
 };
 
 struct AttributionDataHostManagerImpl::RegistrarAndHeader {
@@ -399,10 +389,9 @@
     RegistrationType registration_type,
     GlobalRenderFrameHostId render_frame_id,
     int64_t last_navigation_id) {
-  ReceiverContext receiver_context(
+  RegistrationContext receiver_context(
       std::move(context_origin), registration_type, is_within_fenced_frame,
-      /*input_event=*/AttributionInputEvent(), render_frame_id,
-      /*navigation_id=*/absl::nullopt);
+      render_frame_id, /*navigation=*/absl::nullopt);
 
   switch (registration_type) {
     case RegistrationType::kTrigger:
@@ -537,11 +526,13 @@
     DCHECK(inserted);
     MaybeSetupDeferredReceivers(navigation_id);
 
-    receivers_.Add(this, std::move(it->second.data_host),
-                   ReceiverContext(source_origin, RegistrationType::kSource,
-                                   is_within_fenced_frame,
-                                   std::move(it->second.input_event),
-                                   render_frame_id, navigation_id));
+    receivers_.Add(
+        this, std::move(it->second.data_host),
+        RegistrationContext(
+            /*context_origin=*/source_origin, RegistrationType::kSource,
+            is_within_fenced_frame, render_frame_id,
+            RegistrationNavigationContext(navigation_id,
+                                          std::move(it->second.input_event))));
 
     navigation_data_host_map_.erase(it);
     RecordNavigationDataHostStatus(NavigationDataHostStatus::kProcessed);
@@ -566,12 +557,12 @@
                    network::AttributionReportingRuntimeFeature::kCrossAppWeb));
   if (header.has_value()) {
     auto [it, inserted] = registrations_.emplace(
-        source_origin, is_within_fenced_frame, std::move(input_event),
-        render_frame_id,
-        SourceRegistrations::ForegroundNavigation{
-            .attribution_src_token = attribution_src_token,
-            .navigation_id = navigation_id,
-        });
+        SourceRegistrationsId(attribution_src_token),
+        RegistrationContext(/*context_origin=*/source_origin,
+                            RegistrationType::kSource, is_within_fenced_frame,
+                            render_frame_id,
+                            RegistrationNavigationContext(
+                                navigation_id, std::move(input_event))));
     DCHECK(!it->registrations_complete());
 
     // We defer trigger registrations until source parsing completes.
@@ -603,9 +594,9 @@
   return header.has_value();
 }
 
-const AttributionDataHostManagerImpl::ReceiverContext*
-AttributionDataHostManagerImpl::GetReceiverContextForSource() {
-  const ReceiverContext& context = receivers_.current_context();
+const AttributionDataHostManagerImpl::RegistrationContext*
+AttributionDataHostManagerImpl::GetReceiverRegistrationContextForSource() {
+  const RegistrationContext& context = receivers_.current_context();
 
   if (context.registration_type() == RegistrationType::kTrigger) {
     mojo::ReportBadMessage("AttributionDataHost: Not eligible for source.");
@@ -615,9 +606,9 @@
   return &context;
 }
 
-const AttributionDataHostManagerImpl::ReceiverContext*
-AttributionDataHostManagerImpl::GetReceiverContextForTrigger() {
-  const ReceiverContext& context = receivers_.current_context();
+const AttributionDataHostManagerImpl::RegistrationContext*
+AttributionDataHostManagerImpl::GetReceiverRegistrationContextForTrigger() {
+  const RegistrationContext& context = receivers_.current_context();
 
   if (context.registration_type() == RegistrationType::kSource) {
     mojo::ReportBadMessage("AttributionDataHost: Not eligible for trigger.");
@@ -633,13 +624,14 @@
   // This is validated by the Mojo typemapping.
   DCHECK(reporting_origin.IsValid());
 
-  const ReceiverContext* context = GetReceiverContextForSource();
+  const RegistrationContext* context =
+      GetReceiverRegistrationContextForSource();
   if (!context) {
     return;
   }
 
   auto source_type = SourceType::kEvent;
-  if (context->navigation_id().has_value()) {
+  if (context->navigation().has_value()) {
     source_type = SourceType::kNavigation;
   }
 
@@ -657,7 +649,8 @@
   // This is validated by the Mojo typemapping.
   DCHECK(reporting_origin.IsValid());
 
-  const ReceiverContext* context = GetReceiverContextForTrigger();
+  const RegistrationContext* context =
+      GetReceiverRegistrationContextForTrigger();
   if (!context) {
     return;
   }
@@ -672,22 +665,27 @@
 
 void AttributionDataHostManagerImpl::OsSourceDataAvailable(
     std::vector<GURL> registration_urls) {
-  const ReceiverContext* context = GetReceiverContextForSource();
+  const RegistrationContext* context =
+      GetReceiverRegistrationContextForSource();
   if (!context) {
     return;
   }
 
+  AttributionInputEvent input_event;
+  if (context->navigation().has_value()) {
+    input_event = context->navigation()->input_event();
+  }
   for (GURL& url : registration_urls) {
     attribution_manager_->HandleOsRegistration(
-        OsRegistration(std::move(url), context->context_origin(),
-                       context->input_event()),
+        OsRegistration(std::move(url), context->context_origin(), input_event),
         context->render_frame_id());
   }
 }
 
 void AttributionDataHostManagerImpl::OsTriggerDataAvailable(
     std::vector<GURL> registration_urls) {
-  const ReceiverContext* context = GetReceiverContextForTrigger();
+  const RegistrationContext* context =
+      GetReceiverRegistrationContextForTrigger();
   if (!context) {
     return;
   }
@@ -701,14 +699,14 @@
 }
 
 void AttributionDataHostManagerImpl::OnReceiverDisconnected() {
-  const ReceiverContext& context = receivers_.current_context();
+  const RegistrationContext& context = receivers_.current_context();
 
-  if (context.navigation_id().has_value()) {
+  if (context.navigation().has_value()) {
     if (auto it = ongoing_background_registrations_.find(
-            context.navigation_id().value());
+            context.navigation()->navigation_id());
         it != ongoing_background_registrations_.end()) {
       ongoing_background_registrations_.erase(it);
-      MaybeBindDeferredReceivers(context.navigation_id().value(),
+      MaybeBindDeferredReceivers(context.navigation()->navigation_id(),
                                  /*due_to_timeout=*/false);
     }
   }
@@ -721,18 +719,19 @@
     bool is_within_fenced_frame,
     AttributionInputEvent input_event,
     GlobalRenderFrameHostId render_frame_id) {
+  absl::optional<RegistrationNavigationContext> navigation;
   if (navigation_id.has_value()) {
     MaybeSetupDeferredReceivers(navigation_id.value());
+    navigation = RegistrationNavigationContext(navigation_id.value(),
+                                               std::move(input_event));
   }
 
-  auto [it, inserted] =
-      registrations_.emplace(std::move(source_origin), is_within_fenced_frame,
-                             std::move(input_event), render_frame_id,
-                             SourceRegistrations::Beacon{
-                                 .id = beacon_id,
-                                 .navigation_id = navigation_id,
-                             });
-  DCHECK(inserted);
+  auto [it, inserted] = registrations_.emplace(
+      SourceRegistrationsId(beacon_id),
+      RegistrationContext(/*context_origin=*/std::move(source_origin),
+                          RegistrationType::kSource, is_within_fenced_frame,
+                          render_frame_id, std::move(navigation)));
+  CHECK(inserted);
 }
 
 void AttributionDataHostManagerImpl::NotifyFencedFrameReportingBeaconData(
@@ -788,12 +787,9 @@
   {
     const auto& pending_decode = registrations->pending_web_decodes().front();
 
-    auto source_type = SourceType::kNavigation;
-    if (const auto* beacon =
-            absl::get_if<SourceRegistrations::Beacon>(&registrations->data());
-        beacon && !beacon->navigation_id.has_value()) {
-      source_type = SourceType::kEvent;
-    }
+    auto source_type = registrations->navigation_id().has_value()
+                           ? SourceType::kNavigation
+                           : SourceType::kEvent;
 
     auto source =
         [&]() -> base::expected<StorableSource, SourceRegistrationError> {
@@ -846,10 +842,13 @@
       std::vector<GURL> registration_urls =
           attribution_reporting::ParseOsSourceOrTriggerHeader(*result);
 
+      AttributionInputEvent input_event = registrations->input_event()
+                                              ? *registrations->input_event()
+                                              : AttributionInputEvent();
       for (GURL& url : registration_urls) {
         attribution_manager_->HandleOsRegistration(
             OsRegistration(std::move(url), registrations->source_origin(),
-                           registrations->input_event()),
+                           input_event),
             registrations->render_frame_id());
       }
     }
diff --git a/content/browser/attribution_reporting/attribution_data_host_manager_impl.h b/content/browser/attribution_reporting/attribution_data_host_manager_impl.h
index 0f6154d..c4ff3e5 100644
--- a/content/browser/attribution_reporting/attribution_data_host_manager_impl.h
+++ b/content/browser/attribution_reporting/attribution_data_host_manager_impl.h
@@ -110,7 +110,7 @@
       bool is_final_response) override;
 
  private:
-  class ReceiverContext;
+  class RegistrationContext;
 
   struct DeferredReceiverTimeout;
   struct DeferredReceiver;
@@ -134,8 +134,8 @@
   void OsSourceDataAvailable(std::vector<GURL> registration_urls) override;
   void OsTriggerDataAvailable(std::vector<GURL> registration_urls) override;
 
-  const ReceiverContext* GetReceiverContextForSource();
-  const ReceiverContext* GetReceiverContextForTrigger();
+  const RegistrationContext* GetReceiverRegistrationContextForSource();
+  const RegistrationContext* GetReceiverRegistrationContextForTrigger();
 
   void OnReceiverDisconnected();
 
@@ -165,7 +165,7 @@
   // Owns `this`.
   raw_ptr<AttributionManager> attribution_manager_;
 
-  mojo::ReceiverSet<blink::mojom::AttributionDataHost, ReceiverContext>
+  mojo::ReceiverSet<blink::mojom::AttributionDataHost, RegistrationContext>
       receivers_;
 
   // Map which stores pending receivers for data hosts which are going to
diff --git a/content/browser/attribution_reporting/attribution_interop_parser.cc b/content/browser/attribution_reporting/attribution_interop_parser.cc
index 90961dd1..152d0ea1 100644
--- a/content/browser/attribution_reporting/attribution_interop_parser.cc
+++ b/content/browser/attribution_reporting/attribution_interop_parser.cc
@@ -138,6 +138,12 @@
     ParseInt(dict, "max_destinations_per_source_site_reporting_site",
              config.max_destinations_per_source_site_reporting_site, required);
 
+    ParseInt(dict, "max_destinations_per_rate_limit_window_reporting_site",
+             config.throttler_policy.max_per_reporting_site, required);
+
+    ParseInt(dict, "max_destinations_per_rate_limit_window",
+             config.throttler_policy.max_total, required);
+
     int rate_limit_time_window;
     if (ParseInt(dict, "rate_limit_time_window", rate_limit_time_window,
                  required)) {
diff --git a/content/browser/attribution_reporting/attribution_interop_parser_unittest.cc b/content/browser/attribution_reporting/attribution_interop_parser_unittest.cc
index 1d0e9dc..c3449e7 100644
--- a/content/browser/attribution_reporting/attribution_interop_parser_unittest.cc
+++ b/content/browser/attribution_reporting/attribution_interop_parser_unittest.cc
@@ -25,6 +25,7 @@
 #include "content/browser/attribution_reporting/attribution_interop_parser.h"
 #include "content/browser/attribution_reporting/attribution_test_utils.h"
 #include "content/browser/attribution_reporting/common_source_info.h"
+#include "content/browser/attribution_reporting/destination_throttler.h"
 #include "content/browser/attribution_reporting/storable_source.h"
 #include "net/base/schemeful_site.h"
 #include "testing/gmock/include/gmock/gmock.h"
@@ -92,7 +93,8 @@
 bool operator==(const AttributionConfig& a, const AttributionConfig& b) {
   const auto tie = [](const AttributionConfig& config) {
     return std::make_tuple(config.max_sources_per_origin, config.rate_limit,
-                           config.event_level_limit, config.aggregate_limit);
+                           config.event_level_limit, config.aggregate_limit,
+                           config.throttler_policy);
   };
   return tie(a) == tie(b);
 }
@@ -105,13 +107,6 @@
 
 using ::attribution_reporting::SuitableOrigin;
 
-AttributionConfig::EventLevelLimit EventLevelLimitWith(
-    base::FunctionRef<void(AttributionConfig::EventLevelLimit&)> f) {
-  AttributionConfig::EventLevelLimit limit;
-  f(limit);
-  return limit;
-}
-
 // Pick an arbitrary offset time to test correct handling.
 constexpr base::Time kOffsetTime = base::Time::UnixEpoch() + base::Days(5);
 
@@ -592,85 +587,119 @@
   } kTestCases[] = {
       {R"json({})json", false, AttributionConfig()},
       {R"json({"max_sources_per_origin":"100"})json", false,
-       AttributionConfig{.max_sources_per_origin = 100}},
+       AttributionConfigWith(
+           [](AttributionConfig& c) { c.max_sources_per_origin = 100; })},
       {R"json({"max_destinations_per_source_site_reporting_site":"100"})json",
-       false,
-       AttributionConfig{.max_destinations_per_source_site_reporting_site =
-                             100}},
+       false, AttributionConfigWith([](AttributionConfig& c) {
+         c.max_destinations_per_source_site_reporting_site = 100;
+       })},
+      {R"json({"max_destinations_per_rate_limit_window_reporting_site":"100"})json",
+       false, AttributionConfigWith([](AttributionConfig& c) {
+         c.throttler_policy = {.max_per_reporting_site = 100};
+       })},
+      {R"json({"max_destinations_per_rate_limit_window":"100"})json", false,
+       AttributionConfigWith([](AttributionConfig& c) {
+         c.throttler_policy = {.max_total = 100};
+       })},
       {R"json({"rate_limit_time_window":"30"})json", false,
-       AttributionConfig{.rate_limit = RateLimitWith(
-                             [](AttributionConfig::RateLimitConfig& r) {
-                               r.time_window = base::Days(30);
-                             })}},
+       AttributionConfigWith([](AttributionConfig& c) {
+         c.rate_limit =
+             RateLimitWith([](AttributionConfig::RateLimitConfig& r) {
+               r.time_window = base::Days(30);
+             });
+       })},
       {R"json({"rate_limit_max_source_registration_reporting_origins":"10"})json",
-       false,
-       AttributionConfig{.rate_limit = RateLimitWith(
-                             [](AttributionConfig::RateLimitConfig& r) {
-                               r.max_source_registration_reporting_origins = 10;
-                             })}},
+       false, AttributionConfigWith([](AttributionConfig& c) {
+         c.rate_limit =
+             RateLimitWith([](AttributionConfig::RateLimitConfig& r) {
+               r.max_source_registration_reporting_origins = 10;
+             });
+       })},
       {R"json({"rate_limit_max_attribution_reporting_origins":"10"})json",
-       false,
-       AttributionConfig{.rate_limit = RateLimitWith(
-                             [](AttributionConfig::RateLimitConfig& r) {
-                               r.max_attribution_reporting_origins = 10;
-                             })}},
+       false, AttributionConfigWith([](AttributionConfig& c) {
+         c.rate_limit =
+             RateLimitWith([](AttributionConfig::RateLimitConfig& r) {
+               r.max_attribution_reporting_origins = 10;
+             });
+       })},
       {R"json({"rate_limit_max_attributions":"10"})json", false,
-       AttributionConfig{.rate_limit = RateLimitWith(
-                             [](AttributionConfig::RateLimitConfig& r) {
-                               r.max_attributions = 10;
-                             })}},
+       AttributionConfigWith([](AttributionConfig& c) {
+         c.rate_limit =
+             RateLimitWith([](AttributionConfig::RateLimitConfig& r) {
+               r.max_attributions = 10;
+             });
+       })},
       {R"json({"rate_limit_max_reporting_origins_per_source_reporting_site":"2"})json",
-       false,
-       AttributionConfig{
-           .rate_limit =
-               RateLimitWith([](AttributionConfig::RateLimitConfig& r) {
-                 r.max_reporting_origins_per_source_reporting_site = 2;
-               })}},
+       false, AttributionConfigWith([](AttributionConfig& c) {
+         c.rate_limit =
+             RateLimitWith([](AttributionConfig::RateLimitConfig& r) {
+               r.max_reporting_origins_per_source_reporting_site = 2;
+             });
+       })},
       {R"json({"navigation_source_trigger_data_cardinality":"10"})json", false,
-       AttributionConfig{.event_level_limit = EventLevelLimitWith(
-                             [](AttributionConfig::EventLevelLimit& e) {
-                               e.navigation_source_trigger_data_cardinality =
-                                   10;
-                             })}},
+       AttributionConfigWith([](AttributionConfig& c) {
+         c.event_level_limit =
+             EventLevelLimitWith([](AttributionConfig::EventLevelLimit& e) {
+               e.navigation_source_trigger_data_cardinality = 10;
+             });
+       })},
       {R"json({"event_source_trigger_data_cardinality":"10"})json", false,
-       AttributionConfig{.event_level_limit = EventLevelLimitWith(
-                             [](AttributionConfig::EventLevelLimit& e) {
-                               e.event_source_trigger_data_cardinality = 10;
-                             })}},
+       AttributionConfigWith([](AttributionConfig& c) {
+         c.event_level_limit =
+             EventLevelLimitWith([](AttributionConfig::EventLevelLimit& e) {
+               e.event_source_trigger_data_cardinality = 10;
+             });
+       })},
       {R"json({"randomized_response_epsilon":"inf"})json", false,
-       AttributionConfig{.event_level_limit = EventLevelLimitWith(
-                             [](AttributionConfig::EventLevelLimit& e) {
-                               e.randomized_response_epsilon =
-                                   std::numeric_limits<double>::infinity();
-                             })}},
+       AttributionConfigWith([](AttributionConfig& c) {
+         c.event_level_limit =
+             EventLevelLimitWith([](AttributionConfig::EventLevelLimit& e) {
+               e.randomized_response_epsilon =
+                   std::numeric_limits<double>::infinity();
+             });
+       })},
       {R"json({"max_event_level_reports_per_destination":"10"})json", false,
-       AttributionConfig{.event_level_limit = EventLevelLimitWith(
-                             [](AttributionConfig::EventLevelLimit& e) {
-                               e.max_reports_per_destination = 10;
-                             })}},
+       AttributionConfigWith([](AttributionConfig& c) {
+         c.event_level_limit =
+             EventLevelLimitWith([](AttributionConfig::EventLevelLimit& e) {
+               e.max_reports_per_destination = 10;
+             });
+       })},
       {R"json({"max_attributions_per_navigation_source":"10"})json", false,
-       AttributionConfig{.event_level_limit = EventLevelLimitWith(
-                             [](AttributionConfig::EventLevelLimit& e) {
-                               e.max_attributions_per_navigation_source = 10;
-                             })}},
+       AttributionConfigWith([](AttributionConfig& c) {
+         c.event_level_limit =
+             EventLevelLimitWith([](AttributionConfig::EventLevelLimit& e) {
+               e.max_attributions_per_navigation_source = 10;
+             });
+       })},
       {R"json({"max_attributions_per_event_source":"10"})json", false,
-       AttributionConfig{.event_level_limit = EventLevelLimitWith(
-                             [](AttributionConfig::EventLevelLimit& e) {
-                               e.max_attributions_per_event_source = 10;
-                             })}},
+       AttributionConfigWith([](AttributionConfig& c) {
+         c.event_level_limit =
+             EventLevelLimitWith([](AttributionConfig::EventLevelLimit& e) {
+               e.max_attributions_per_event_source = 10;
+             });
+       })},
       {R"json({"max_aggregatable_reports_per_destination":"10"})json", false,
-       AttributionConfig{
-           .aggregate_limit = {.max_reports_per_destination = 10}}},
+       AttributionConfigWith([](AttributionConfig& c) {
+         c.aggregate_limit = {.max_reports_per_destination = 10};
+       })},
       {R"json({"aggregatable_budget_per_source":"100"})json", false,
-       AttributionConfig{
-           .aggregate_limit = {.aggregatable_budget_per_source = 100}}},
+       AttributionConfigWith([](AttributionConfig& c) {
+         c.aggregate_limit = {.aggregatable_budget_per_source = 100};
+       })},
       {R"json({"aggregatable_report_min_delay":"0"})json", false,
-       AttributionConfig{.aggregate_limit = {.min_delay = base::TimeDelta()}}},
+       AttributionConfigWith([](AttributionConfig& c) {
+         c.aggregate_limit = {.min_delay = base::TimeDelta()};
+       })},
       {R"json({"aggregatable_report_delay_span":"0"})json", false,
-       AttributionConfig{.aggregate_limit = {.delay_span = base::TimeDelta()}}},
+       AttributionConfigWith([](AttributionConfig& c) {
+         c.aggregate_limit = {.delay_span = base::TimeDelta()};
+       })},
       {R"json({
         "max_sources_per_origin":"10",
         "max_destinations_per_source_site_reporting_site":"10",
+        "max_destinations_per_rate_limit_window_reporting_site": "1",
+        "max_destinations_per_rate_limit_window": "2",
         "rate_limit_time_window":"10",
         "rate_limit_max_source_registration_reporting_origins":"20",
         "rate_limit_max_attribution_reporting_origins":"15",
@@ -687,32 +716,32 @@
         "aggregatable_report_min_delay":"10",
         "aggregatable_report_delay_span":"20"
       })json",
-       true,
-       AttributionConfig{
-           .max_sources_per_origin = 10,
-           .max_destinations_per_source_site_reporting_site = 10,
-           .rate_limit =
-               RateLimitWith([](AttributionConfig::RateLimitConfig& r) {
-                 r.time_window = base::Days(10);
-                 r.max_source_registration_reporting_origins = 20;
-                 r.max_attribution_reporting_origins = 15;
-                 r.max_attributions = 10;
-                 r.max_reporting_origins_per_source_reporting_site = 5;
-               }),
-           .event_level_limit =
-               EventLevelLimitWith([](AttributionConfig::EventLevelLimit& e) {
-                 e.navigation_source_trigger_data_cardinality = 100;
-                 e.event_source_trigger_data_cardinality = 10;
-                 e.randomized_response_epsilon = 0.2;
-                 e.max_reports_per_destination = 10;
-                 e.max_attributions_per_navigation_source = 5;
-                 e.max_attributions_per_event_source = 1;
-               }),
-           .aggregate_limit = {.max_reports_per_destination = 10,
-                               .aggregatable_budget_per_source = 1000,
-                               .min_delay = base::Minutes(10),
-                               .delay_span = base::Minutes(20)}}},
-  };
+       true, AttributionConfigWith([](AttributionConfig& c) {
+         c.max_sources_per_origin = 10;
+         c.max_destinations_per_source_site_reporting_site = 10;
+         c.rate_limit =
+             RateLimitWith([](AttributionConfig::RateLimitConfig& r) {
+               r.time_window = base::Days(10);
+               r.max_source_registration_reporting_origins = 20;
+               r.max_attribution_reporting_origins = 15;
+               r.max_attributions = 10;
+               r.max_reporting_origins_per_source_reporting_site = 5;
+             });
+         c.event_level_limit =
+             EventLevelLimitWith([](AttributionConfig::EventLevelLimit& e) {
+               e.navigation_source_trigger_data_cardinality = 100;
+               e.event_source_trigger_data_cardinality = 10;
+               e.randomized_response_epsilon = 0.2;
+               e.max_reports_per_destination = 10;
+               e.max_attributions_per_navigation_source = 5;
+               e.max_attributions_per_event_source = 1;
+             }),
+         c.aggregate_limit = {.max_reports_per_destination = 10,
+                              .aggregatable_budget_per_source = 1000,
+                              .min_delay = base::Minutes(10),
+                              .delay_span = base::Minutes(20)},
+         c.throttler_policy = {.max_total = 2, .max_per_reporting_site = 1};
+       })}};
 
   for (const auto& test_case : kTestCases) {
     base::Value::Dict json = base::test::ParseJsonDict(test_case.json);
@@ -732,6 +761,8 @@
   const char* const kFields[] = {
       "max_sources_per_origin",
       "max_destinations_per_source_site_reporting_site",
+      "max_destinations_per_rate_limit_window_reporting_site",
+      "max_destinations_per_rate_limit_window",
       "rate_limit_time_window",
       "rate_limit_max_source_registration_reporting_origins",
       "rate_limit_max_attribution_reporting_origins",
diff --git a/content/browser/attribution_reporting/attribution_manager_impl.cc b/content/browser/attribution_reporting/attribution_manager_impl.cc
index bd30c59..269d31e 100644
--- a/content/browser/attribution_reporting/attribution_manager_impl.cc
+++ b/content/browser/attribution_reporting/attribution_manager_impl.cc
@@ -58,7 +58,6 @@
 #include "content/browser/attribution_reporting/attribution_storage_sql.h"
 #include "content/browser/attribution_reporting/attribution_trigger.h"
 #include "content/browser/attribution_reporting/create_report_result.h"
-#include "content/browser/attribution_reporting/destination_throttler.h"
 #include "content/browser/attribution_reporting/os_registration.h"
 #include "content/browser/attribution_reporting/send_result.h"
 #include "content/browser/attribution_reporting/storable_source.h"
@@ -364,20 +363,6 @@
       AttributionNoiseMode::kDefault, AttributionDelayMode::kDefault);
 }
 
-StorableSource::Result ThrottleResultToStorableSourceResult(
-    DestinationThrottler::Result r) {
-  switch (r) {
-    case DestinationThrottler::Result::kAllowed:
-      return StorableSource::Result::kSuccess;
-    case DestinationThrottler::Result::kHitGlobalLimit:
-      return StorableSource::Result::kDestinationGlobalLimitReached;
-    case DestinationThrottler::Result::kHitReportingLimit:
-      return StorableSource::Result::kDestinationReportingLimitReached;
-    case DestinationThrottler::Result::kHitBothLimits:
-      return StorableSource::Result::kDestinationBothLimitsReached;
-  }
-}
-
 bool IsOperationAllowed(
     StoragePartitionImpl* storage_partition,
     ContentBrowserClient::AttributionReportingOperation operation,
@@ -511,8 +496,7 @@
     std::unique_ptr<AttributionReportSender> report_sender,
     std::unique_ptr<AttributionOsLevelManager> os_level_manager,
     scoped_refptr<base::UpdateableSequencedTaskRunner> storage_task_runner)
-    : throttler_(DestinationThrottler::Policy()),
-      storage_partition_(storage_partition),
+    : storage_partition_(storage_partition),
       max_pending_events_(max_pending_events),
       storage_task_runner_(std::move(storage_task_runner)),
       attribution_storage_(base::SequenceBound<AttributionStorageSql>(
@@ -735,8 +719,7 @@
 
   absl::visit(base::Overloaded{
                   [&](StorableSource& source) {
-                    CheckDestinationThrottlerAndStoreSource(
-                        std::move(source), is_debug_cookie_set);
+                    StoreSource(std::move(source), is_debug_cookie_set);
                   },
                   [&](AttributionTrigger& trigger) {
                     StoreTrigger(std::move(trigger), is_debug_cookie_set);
@@ -747,37 +730,19 @@
   pending_events_.pop_front();
 }
 
-void AttributionManagerImpl::CheckDestinationThrottlerAndStoreSource(
-    StorableSource source,
-    bool is_debug_cookie_set) {
-  DestinationThrottler::Result throttle_result = throttler_.UpdateAndGetResult(
-      source.registration().destination_set,
-      net::SchemefulSite(source.common_info().source_origin()),
-      net::SchemefulSite(source.common_info().reporting_origin()));
-  base::UmaHistogramEnumeration("Conversions.DestinationThrottlerResult",
-                                throttle_result);
-
+void AttributionManagerImpl::StoreSource(StorableSource source,
+                                         bool is_debug_cookie_set) {
   absl::optional<uint64_t> cleared_debug_key;
   if (!is_debug_cookie_set) {
     cleared_debug_key =
         std::exchange(source.registration().debug_key, absl::nullopt);
   }
 
-  if (StorableSource::Result result =
-          ThrottleResultToStorableSourceResult(throttle_result);
-      result == StorableSource::Result::kSuccess) {
-    attribution_storage_.AsyncCall(&AttributionStorage::StoreSource)
-        .WithArgs(source)
-        .Then(base::BindOnce(&AttributionManagerImpl::OnSourceStored,
-                             weak_factory_.GetWeakPtr(), std::move(source),
-                             cleared_debug_key, is_debug_cookie_set));
-  } else {
-    StoreSourceResult store_result(result);
-    store_result.max_destinations_per_rate_limit_window_reporting_origin =
-        throttler_.GetMaxPerReportingSite();
-    OnSourceStored(source, cleared_debug_key, is_debug_cookie_set,
-                   StoreSourceResult(result));
-  }
+  attribution_storage_.AsyncCall(&AttributionStorage::StoreSource)
+      .WithArgs(source)
+      .Then(base::BindOnce(&AttributionManagerImpl::OnSourceStored,
+                           weak_factory_.GetWeakPtr(), std::move(source),
+                           cleared_debug_key, is_debug_cookie_set));
 }
 
 void AttributionManagerImpl::AddPendingAggregatableReportTiming(
diff --git a/content/browser/attribution_reporting/attribution_manager_impl.h b/content/browser/attribution_reporting/attribution_manager_impl.h
index 4846aba..a340d1c2 100644
--- a/content/browser/attribution_reporting/attribution_manager_impl.h
+++ b/content/browser/attribution_reporting/attribution_manager_impl.h
@@ -26,7 +26,6 @@
 #include "content/browser/attribution_reporting/attribution_report.h"
 #include "content/browser/attribution_reporting/attribution_report_sender.h"
 #include "content/browser/attribution_reporting/attribution_reporting.mojom-forward.h"
-#include "content/browser/attribution_reporting/destination_throttler.h"
 #include "content/common/content_export.h"
 #include "content/public/browser/storage_partition.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
@@ -176,6 +175,7 @@
   void MaybeEnqueueEvent(SourceOrTrigger);
   void ProcessEvents();
   void ProcessNextEvent(bool is_debug_cookie_set);
+  void StoreSource(StorableSource source, bool is_debug_cookie_set);
   void StoreTrigger(AttributionTrigger trigger, bool is_debug_cookie_set);
 
   void GetReportsToSend();
@@ -244,11 +244,6 @@
                         const OsRegistration&,
                         bool success);
 
-  void CheckDestinationThrottlerAndStoreSource(StorableSource source,
-                                               bool is_debug_cookie_set);
-
-  DestinationThrottler throttler_;
-
   // Never null.
   const raw_ptr<StoragePartitionImpl> storage_partition_;
 
diff --git a/content/browser/attribution_reporting/attribution_storage_delegate.cc b/content/browser/attribution_reporting/attribution_storage_delegate.cc
index e7d0075..0dcc08c7 100644
--- a/content/browser/attribution_reporting/attribution_storage_delegate.cc
+++ b/content/browser/attribution_reporting/attribution_storage_delegate.cc
@@ -73,6 +73,12 @@
   return config_.aggregate_limit.max_aggregatable_reports_per_source;
 }
 
+DestinationThrottler::Policy
+AttributionStorageDelegate::GetDestinationThrottlerPolicy() const {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  return config_.throttler_policy;
+}
+
 uint64_t AttributionStorageDelegate::SanitizeTriggerData(
     uint64_t trigger_data,
     SourceType source_type) const {
diff --git a/content/browser/attribution_reporting/attribution_storage_delegate.h b/content/browser/attribution_reporting/attribution_storage_delegate.h
index b98043f..09f7b11 100644
--- a/content/browser/attribution_reporting/attribution_storage_delegate.h
+++ b/content/browser/attribution_reporting/attribution_storage_delegate.h
@@ -16,6 +16,7 @@
 #include "components/attribution_reporting/source_type.mojom-forward.h"
 #include "content/browser/attribution_reporting/attribution_config.h"
 #include "content/browser/attribution_reporting/attribution_reporting.mojom-forward.h"
+#include "content/browser/attribution_reporting/destination_throttler.h"
 #include "content/common/content_export.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
 
@@ -163,6 +164,8 @@
 
   int GetMaxAggregatableReportsPerSource() const;
 
+  DestinationThrottler::Policy GetDestinationThrottlerPolicy() const;
+
   // Sanitizes `trigger_data` according to the data limits for `source_type`.
   uint64_t SanitizeTriggerData(uint64_t trigger_data,
                                attribution_reporting::mojom::SourceType) const;
diff --git a/content/browser/attribution_reporting/attribution_storage_sql.cc b/content/browser/attribution_reporting/attribution_storage_sql.cc
index cc9780b..5098af8 100644
--- a/content/browser/attribution_reporting/attribution_storage_sql.cc
+++ b/content/browser/attribution_reporting/attribution_storage_sql.cc
@@ -54,6 +54,7 @@
 #include "content/browser/attribution_reporting/attribution_utils.h"
 #include "content/browser/attribution_reporting/common_source_info.h"
 #include "content/browser/attribution_reporting/create_report_result.h"
+#include "content/browser/attribution_reporting/destination_throttler.h"
 #include "content/browser/attribution_reporting/rate_limit_result.h"
 #include "content/browser/attribution_reporting/sql_queries.h"
 #include "content/browser/attribution_reporting/sql_utils.h"
@@ -614,6 +615,20 @@
   return user_data_directory.Append(kDatabasePath);
 }
 
+StorableSource::Result ThrottleResultToStorableSourceResult(
+    DestinationThrottler::Result r) {
+  switch (r) {
+    case DestinationThrottler::Result::kAllowed:
+      return StorableSource::Result::kSuccess;
+    case DestinationThrottler::Result::kHitGlobalLimit:
+      return StorableSource::Result::kDestinationGlobalLimitReached;
+    case DestinationThrottler::Result::kHitReportingLimit:
+      return StorableSource::Result::kDestinationReportingLimitReached;
+    case DestinationThrottler::Result::kHitBothLimits:
+      return StorableSource::Result::kDestinationBothLimitsReached;
+  }
+}
+
 }  // namespace
 
 AttributionStorageSql::AttributionStorageSql(
@@ -626,7 +641,8 @@
                                .page_size = 4096,
                                .cache_size = 32}),
       delegate_(std::move(delegate)),
-      rate_limit_table_(delegate_.get()) {
+      rate_limit_table_(delegate_.get()),
+      throttler_(delegate_->GetDestinationThrottlerPolicy()) {
   DCHECK(delegate_);
 
   db_.set_histogram_tag("Conversions");
@@ -669,6 +685,14 @@
 StoreSourceResult AttributionStorageSql::StoreSource(
     const StorableSource& source) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  if (StorableSource::Result result = CheckDestinationThrottler(source);
+      result != StorableSource::Result::kSuccess) {
+    StoreSourceResult store_result(result);
+    store_result.max_destinations_per_rate_limit_window_reporting_origin =
+        throttler_.GetMaxPerReportingSite();
+    return store_result;
+  }
+
   // Force the creation of the database if it doesn't exist, as we need to
   // persist the source.
   if (!LazyInit(DbCreationPolicy::kCreateIfAbsent)) {
@@ -899,6 +923,19 @@
       min_fake_report_time);
 }
 
+StorableSource::Result AttributionStorageSql::CheckDestinationThrottler(
+    const StorableSource& source) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  DestinationThrottler::Result throttle_result = throttler_.UpdateAndGetResult(
+      source.registration().destination_set,
+      net::SchemefulSite(source.common_info().source_origin()),
+      net::SchemefulSite(source.common_info().reporting_origin()));
+  base::UmaHistogramEnumeration("Conversions.DestinationThrottlerResult",
+                                throttle_result);
+
+  return ThrottleResultToStorableSourceResult(throttle_result);
+}
+
 // Checks whether a new report is allowed to be stored for the given source
 // based on `GetMaxAttributionsPerSource()`. If there's sufficient capacity,
 // the new report should be stored. Otherwise, if all existing reports were from
diff --git a/content/browser/attribution_reporting/attribution_storage_sql.h b/content/browser/attribution_reporting/attribution_storage_sql.h
index b583149..e1c88b7 100644
--- a/content/browser/attribution_reporting/attribution_storage_sql.h
+++ b/content/browser/attribution_reporting/attribution_storage_sql.h
@@ -18,7 +18,9 @@
 #include "content/browser/attribution_reporting/attribution_report.h"
 #include "content/browser/attribution_reporting/attribution_storage.h"
 #include "content/browser/attribution_reporting/attribution_trigger.h"
+#include "content/browser/attribution_reporting/destination_throttler.h"
 #include "content/browser/attribution_reporting/rate_limit_table.h"
+#include "content/browser/attribution_reporting/store_source_result.mojom-forward.h"
 #include "content/browser/attribution_reporting/stored_source.h"
 #include "content/common/content_export.h"
 #include "content/public/browser/attribution_data_model.h"
@@ -34,6 +36,7 @@
 namespace content {
 
 class AttributionStorageDelegate;
+class StorableSource;
 struct AttributionInfo;
 
 enum class RateLimitResult : int;
@@ -124,6 +127,9 @@
                  StoragePartition::StorageKeyMatcherFunction filter,
                  bool delete_rate_limit_data) override;
 
+  [[nodiscard]] attribution_reporting::mojom::StoreSourceResult
+  CheckDestinationThrottler(const StorableSource& source);
+
   void ClearAllDataAllTime(bool delete_rate_limit_data)
       VALID_CONTEXT_REQUIRED(sequence_checker_);
 
@@ -369,6 +375,8 @@
   base::Time last_deleted_expired_sources_
       GUARDED_BY_CONTEXT(sequence_checker_);
 
+  DestinationThrottler throttler_ GUARDED_BY_CONTEXT(sequence_checker_);
+
   SEQUENCE_CHECKER(sequence_checker_);
 };
 
diff --git a/content/browser/attribution_reporting/attribution_test_utils.cc b/content/browser/attribution_reporting/attribution_test_utils.cc
index 7421804c..4556e10 100644
--- a/content/browser/attribution_reporting/attribution_test_utils.cc
+++ b/content/browser/attribution_reporting/attribution_test_utils.cc
@@ -94,6 +94,20 @@
   return limit;
 }
 
+AttributionConfig::EventLevelLimit EventLevelLimitWith(
+    base::FunctionRef<void(content::AttributionConfig::EventLevelLimit&)> f) {
+  content::AttributionConfig::EventLevelLimit limit;
+  f(limit);
+  return limit;
+}
+
+AttributionConfig AttributionConfigWith(
+    base::FunctionRef<void(AttributionConfig&)> f) {
+  AttributionConfig limit;
+  f(limit);
+  return limit;
+}
+
 // Builds an impression with default values. This is done as a builder because
 // all values needed to be provided at construction time.
 SourceBuilder::SourceBuilder(base::Time time)
diff --git a/content/browser/attribution_reporting/attribution_test_utils.h b/content/browser/attribution_reporting/attribution_test_utils.h
index d8f08a9..7e47d9dc 100644
--- a/content/browser/attribution_reporting/attribution_test_utils.h
+++ b/content/browser/attribution_reporting/attribution_test_utils.h
@@ -71,6 +71,12 @@
 AttributionConfig::RateLimitConfig RateLimitWith(
     base::FunctionRef<void(AttributionConfig::RateLimitConfig&)> f);
 
+AttributionConfig::EventLevelLimit EventLevelLimitWith(
+    base::FunctionRef<void(content::AttributionConfig::EventLevelLimit&)> f);
+
+AttributionConfig AttributionConfigWith(
+    base::FunctionRef<void(AttributionConfig&)> f);
+
 // Helper class to construct a StorableSource for tests using default data.
 // StorableSource members are not mutable after construction requiring a
 // builder pattern.
diff --git a/content/browser/attribution_reporting/destination_throttler.cc b/content/browser/attribution_reporting/destination_throttler.cc
index 512595db..2e3037a 100644
--- a/content/browser/attribution_reporting/destination_throttler.cc
+++ b/content/browser/attribution_reporting/destination_throttler.cc
@@ -135,6 +135,28 @@
   std::map<net::SchemefulSite, DestinationSubset> reporting_destinations_;
 };
 
+bool DestinationThrottler::Policy::Validate() const {
+  if (max_per_reporting_site <= 0) {
+    return false;
+  }
+
+  if (max_total < max_per_reporting_site) {
+    return false;
+  }
+
+  if (!rate_limit_window.is_positive()) {
+    return false;
+  }
+
+  return true;
+}
+
+bool DestinationThrottler::Policy::operator==(const Policy& other) const {
+  return max_total == other.max_total &&
+         max_per_reporting_site == other.max_per_reporting_site &&
+         rate_limit_window == other.rate_limit_window;
+}
+
 DestinationThrottler::DestinationThrottler(Policy policy)
     : policy_(std::move(policy)) {}
 
diff --git a/content/browser/attribution_reporting/destination_throttler.h b/content/browser/attribution_reporting/destination_throttler.h
index 918a54a..dff2904 100644
--- a/content/browser/attribution_reporting/destination_throttler.h
+++ b/content/browser/attribution_reporting/destination_throttler.h
@@ -24,12 +24,14 @@
 // keeping track of unique destinations being registered on source sites.
 class CONTENT_EXPORT DestinationThrottler {
  public:
-  struct Policy {
-    // TODO(tquintanilla): Move these parameters to `AttributionConfig` to align
-    // with other parameters.
+  struct CONTENT_EXPORT Policy {
     int max_total = 200;
     int max_per_reporting_site = 50;
     base::TimeDelta rate_limit_window = base::Minutes(1);
+
+    bool operator==(const Policy&) const;
+
+    [[nodiscard]] bool Validate() const;
   };
   explicit DestinationThrottler(Policy policy);
   ~DestinationThrottler();
diff --git a/content/browser/attribution_reporting/test/configurable_storage_delegate.cc b/content/browser/attribution_reporting/test/configurable_storage_delegate.cc
index d212afc..2b7f2a0a 100644
--- a/content/browser/attribution_reporting/test/configurable_storage_delegate.cc
+++ b/content/browser/attribution_reporting/test/configurable_storage_delegate.cc
@@ -27,57 +27,46 @@
 
 namespace content {
 
-namespace {
-
-AttributionConfig::EventLevelLimit EventLevelLimitWith(
-    base::FunctionRef<void(AttributionConfig::EventLevelLimit&)> f) {
-  content::AttributionConfig::EventLevelLimit limit;
-  f(limit);
-  return limit;
-}
-
-}  // namespace
-
 ConfigurableStorageDelegate::ConfigurableStorageDelegate()
-    : AttributionStorageDelegate(AttributionConfig{
-          .max_sources_per_origin = std::numeric_limits<int>::max(),
-          .max_destinations_per_source_site_reporting_site =
-              std::numeric_limits<int>::max(),
-          .rate_limit =
-              RateLimitWith([](AttributionConfig::RateLimitConfig& r) {
-                r.time_window = base::TimeDelta::Max();
-                r.max_source_registration_reporting_origins =
-                    std::numeric_limits<int64_t>::max();
-                r.max_attribution_reporting_origins =
-                    std::numeric_limits<int64_t>::max();
-                r.max_attributions = std::numeric_limits<int64_t>::max();
-                r.max_reporting_origins_per_source_reporting_site =
-                    std::numeric_limits<int>::max();
-              }),
-          .event_level_limit =
-              EventLevelLimitWith([](AttributionConfig::EventLevelLimit& e) {
-                e.navigation_source_trigger_data_cardinality =
-                    std::numeric_limits<uint64_t>::max();
-                e.event_source_trigger_data_cardinality =
-                    std::numeric_limits<uint64_t>::max();
-                e.randomized_response_epsilon =
-                    std::numeric_limits<double>::infinity();
-                e.max_reports_per_destination = std::numeric_limits<int>::max();
-                e.max_attributions_per_navigation_source =
-                    std::numeric_limits<int>::max();
-                e.max_attributions_per_event_source =
-                    std::numeric_limits<int>::max();
-              }),
-          .aggregate_limit =
-              {
-                  .max_reports_per_destination =
-                      std::numeric_limits<int>::max(),
-                  .aggregatable_budget_per_source =
-                      std::numeric_limits<int64_t>::max(),
-                  .min_delay = base::TimeDelta(),
-                  .delay_span = base::TimeDelta(),
-              },
-      }) {}
+    : AttributionStorageDelegate(
+          AttributionConfigWith([](AttributionConfig& c) {
+            c.max_sources_per_origin = std::numeric_limits<int>::max(),
+            c.max_destinations_per_source_site_reporting_site =
+                std::numeric_limits<int>::max();
+            c.rate_limit =
+                RateLimitWith([](AttributionConfig::RateLimitConfig& r) {
+                  r.time_window = base::TimeDelta::Max();
+                  r.max_source_registration_reporting_origins =
+                      std::numeric_limits<int64_t>::max();
+                  r.max_attribution_reporting_origins =
+                      std::numeric_limits<int64_t>::max();
+                  r.max_attributions = std::numeric_limits<int64_t>::max();
+                  r.max_reporting_origins_per_source_reporting_site =
+                      std::numeric_limits<int>::max();
+                }),
+            c.event_level_limit =
+                EventLevelLimitWith([](AttributionConfig::EventLevelLimit& e) {
+                  e.navigation_source_trigger_data_cardinality =
+                      std::numeric_limits<uint64_t>::max();
+                  e.event_source_trigger_data_cardinality =
+                      std::numeric_limits<uint64_t>::max();
+                  e.randomized_response_epsilon =
+                      std::numeric_limits<double>::infinity();
+                  e.max_reports_per_destination =
+                      std::numeric_limits<int>::max();
+                  e.max_attributions_per_navigation_source =
+                      std::numeric_limits<int>::max();
+                  e.max_attributions_per_event_source =
+                      std::numeric_limits<int>::max();
+                });
+            c.aggregate_limit = {
+                .max_reports_per_destination = std::numeric_limits<int>::max(),
+                .aggregatable_budget_per_source =
+                    std::numeric_limits<int64_t>::max(),
+                .min_delay = base::TimeDelta(),
+                .delay_span = base::TimeDelta(),
+            };
+          })) {}
 
 ConfigurableStorageDelegate::~ConfigurableStorageDelegate() = default;
 
diff --git a/content/browser/browsing_topics/header_util.cc b/content/browser/browsing_topics/header_util.cc
index e8a5a0f..ee370ae2 100644
--- a/content/browser/browsing_topics/header_util.cc
+++ b/content/browser/browsing_topics/header_util.cc
@@ -26,7 +26,7 @@
 // grow as versions require more digits.
 constexpr int kVersionMaxLength = 13;
 
-static_assert(blink::features::kBrowsingTopicsConfigVersionDefault < 10,
+static_assert(browsing_topics::ConfigVersion::kMaxValue < 10,
               "Topics config version should not exceed 1 digit, or "
               "`kVersionMaxLength` should be updated accordingly.");
 
diff --git a/content/browser/browsing_topics/header_util_unittest.cc b/content/browser/browsing_topics/header_util_unittest.cc
index cc9b7fd..7e966d4f 100644
--- a/content/browser/browsing_topics/header_util_unittest.cc
+++ b/content/browser/browsing_topics/header_util_unittest.cc
@@ -341,8 +341,8 @@
 TEST_F(BrowsingTopicsUtilTest,
        DeriveTopicsHeaderValue_LengthExceedsDefaultMax_NoPadding) {
   std::string config_version = base::StrCat(
-      {"chrome.", base::NumberToString(
-                      blink::features::kBrowsingTopicsConfigVersion.Get())});
+      {"chrome.",
+       base::NumberToString(browsing_topics::ConfigVersion::kMaxValue)});
   std::string taxonomy_version = base::NumberToString(
       blink::features::kBrowsingTopicsTaxonomyVersion.Get());
 
diff --git a/content/browser/interest_group/ad_auction_service_impl_unittest.cc b/content/browser/interest_group/ad_auction_service_impl_unittest.cc
index ba28eb9f..5f2d97d 100644
--- a/content/browser/interest_group/ad_auction_service_impl_unittest.cc
+++ b/content/browser/interest_group/ad_auction_service_impl_unittest.cc
@@ -344,8 +344,9 @@
       base::AutoLock auto_lock(lock_);
       DCHECK(!quit_report_wait_loop_callback_);
       EXPECT_LE(report_count_, num_reports);
-      if (report_count_ >= num_reports)
+      if (report_count_ >= num_reports) {
         return;
+      }
       waiting_for_report_count_ = num_reports;
       quit_report_wait_loop_callback_ = run_loop.QuitClosure();
     }
@@ -447,8 +448,9 @@
 
   void OnReportSent() EXCLUSIVE_LOCKS_REQUIRED(lock_) {
     ++report_count_;
-    if (waiting_for_report_count_ == report_count_)
+    if (waiting_for_report_count_ == report_count_) {
       std::move(quit_report_wait_loop_callback_).Run();
+    }
   }
 
   void OnUpdateRequestReceived(URLLoaderInterceptor::RequestParams* params)
@@ -721,15 +723,17 @@
 
   int GetJoinCount(const url::Origin& owner, const std::string& name) {
     auto interest_group = GetInterestGroup(owner, name);
-    if (!interest_group)
+    if (!interest_group) {
       return 0;
+    }
     return interest_group->bidding_browser_signals->join_count;
   }
 
   double GetPriority(const url::Origin& owner, const std::string& name) {
     auto interest_group = GetInterestGroup(owner, name);
-    if (!interest_group)
+    if (!interest_group) {
       return 0;
+    }
     return interest_group->interest_group.priority;
   }
 
@@ -1156,6 +1160,7 @@
 "trustedBiddingSignalsUrl":
   "%s/interest_group/new_trusted_bidding_signals_url.json",
 "trustedBiddingSignalsKeys": ["new_key"],
+"updateURL": "%s/interest_group/new_daily_update_partial.json",
 "ads": [{"renderURL": "%s/new_ad_render_url",
          "sizeGroup": "group_new",
          "metadata": {"new_a": "b"},
@@ -1172,7 +1177,7 @@
 "sizeGroups": {"group_new": ["size_new"]}
 })",
                          kOriginStringA, kOriginStringA, kOriginStringA,
-                         kOriginStringA, kOriginStringA));
+                         kOriginStringA, kOriginStringA, kOriginStringA));
 
   blink::InterestGroup interest_group = CreateInterestGroup();
   interest_group.priority = 2.0;
@@ -1266,6 +1271,11 @@
   ASSERT_TRUE(group.trusted_bidding_signals_keys.has_value());
   EXPECT_EQ(group.trusted_bidding_signals_keys->size(), 1u);
   EXPECT_EQ(group.trusted_bidding_signals_keys.value()[0], "new_key");
+  ASSERT_TRUE(group.update_url.has_value());
+  EXPECT_EQ(
+      group.update_url->spec(),
+      base::StringPrintf("%s/interest_group/new_daily_update_partial.json",
+                         kOriginStringA));
   ASSERT_TRUE(group.ads.has_value());
   ASSERT_EQ(group.ads->size(), 1u);
   EXPECT_EQ(group.ads.value()[0].render_url.spec(),
diff --git a/content/browser/interest_group/interest_group_storage.cc b/content/browser/interest_group/interest_group_storage.cc
index 1388db8..e38b0f5 100644
--- a/content/browser/interest_group/interest_group_storage.cc
+++ b/content/browser/interest_group/interest_group_storage.cc
@@ -1209,6 +1209,10 @@
   // enclose them in a transaction since only one will actually modify the
   // database.
 
+  int64_t join_day = join_time.ToDeltaSinceWindowsEpoch()
+                         .FloorToMultiple(base::Days(1))
+                         .InMicroseconds();
+
   // clang-format off
   sql::Statement insert_join_hist(
       db.GetCachedStatement(SQL_FROM_HERE,
@@ -1221,7 +1225,7 @@
   insert_join_hist.Reset(true);
   insert_join_hist.BindString(0, Serialize(owner));
   insert_join_hist.BindString(1, name);
-  insert_join_hist.BindTime(2, join_time);
+  insert_join_hist.BindInt64(2, join_day);
   if (!insert_join_hist.Run())
     return false;
 
@@ -1242,7 +1246,7 @@
   update_join_hist.Reset(true);
   update_join_hist.BindString(0, Serialize(owner));
   update_join_hist.BindString(1, name);
-  update_join_hist.BindTime(2, join_time);
+  update_join_hist.BindInt64(2, join_day);
 
   return update_join_hist.Run();
 }
@@ -1451,6 +1455,9 @@
     stored_group.all_sellers_capabilities = *update.all_sellers_capabilities;
   if (update.execution_mode)
     stored_group.execution_mode = *update.execution_mode;
+  if (update.daily_update_url) {
+    stored_group.update_url = std::move(update.daily_update_url);
+  }
   if (update.bidding_url)
     stored_group.bidding_url = std::move(update.bidding_url);
   if (update.bidding_wasm_helper_url) {
@@ -1522,6 +1529,10 @@
   // enclose them in a transaction since only one will actually modify the
   // database.
 
+  int64_t bid_day = bid_time.ToDeltaSinceWindowsEpoch()
+                        .FloorToMultiple(base::Days(1))
+                        .InMicroseconds();
+
   // clang-format off
   sql::Statement insert_bid_hist(
       db.GetCachedStatement(SQL_FROM_HERE,
@@ -1534,7 +1545,7 @@
   insert_bid_hist.Reset(true);
   insert_bid_hist.BindString(0, Serialize(group_key.owner));
   insert_bid_hist.BindString(1, group_key.name);
-  insert_bid_hist.BindTime(2, bid_time);
+  insert_bid_hist.BindInt64(2, bid_day);
   if (!insert_bid_hist.Run())
     return false;
 
@@ -1555,7 +1566,7 @@
   update_bid_hist.Reset(true);
   update_bid_hist.BindString(0, Serialize(group_key.owner));
   update_bid_hist.BindString(1, group_key.name);
-  update_bid_hist.BindTime(2, bid_time);
+  update_bid_hist.BindInt64(2, bid_day);
 
   return update_bid_hist.Run();
 }
diff --git a/content/browser/interest_group/interest_group_update_manager.cc b/content/browser/interest_group/interest_group_update_manager.cc
index 97cd6224..c07546561 100644
--- a/content/browser/interest_group/interest_group_update_manager.cc
+++ b/content/browser/interest_group/interest_group_update_manager.cc
@@ -476,6 +476,11 @@
     interest_group_update.bidding_wasm_helper_url =
         GURL(*maybe_bidding_wasm_helper_url);
   }
+  const std::string* maybe_update_url =
+      dict->FindString("updateURL");  // TODO check if we use this or updateURL
+  if (maybe_update_url) {
+    interest_group_update.daily_update_url = GURL(*maybe_update_url);
+  }
   const std::string* maybe_trusted_bidding_signals_url =
       dict->FindString("trustedBiddingSignalsURL");
   const std::string* maybe_trusted_bidding_signals_url_deprecated =
diff --git a/content/browser/preloading/prefetch/prefetch_document_manager.cc b/content/browser/preloading/prefetch/prefetch_document_manager.cc
index b10e350..0fa8d046 100644
--- a/content/browser/preloading/prefetch/prefetch_document_manager.cc
+++ b/content/browser/preloading/prefetch/prefetch_document_manager.cc
@@ -9,6 +9,7 @@
 #include <tuple>
 #include <vector>
 
+#include "base/containers/contains.h"
 #include "base/containers/cxx20_erase.h"
 #include "content/browser/browser_context_impl.h"
 #include "content/browser/preloading/prefetch/prefetch_container.h"
@@ -194,6 +195,30 @@
                          blink::mojom::SpeculationInjectionWorld>>
       prefetches;
 
+  // Evicts an existing prefetch if there is no longer a matching speculation
+  // candidate for it. Note: A matching candidate is not necessarily the
+  // candidate that originally triggered the prefetch, but is any prefetch
+  // candidate that has the same URL.
+  if (PrefetchNewLimitsEnabled()) {
+    std::vector<GURL> urls_from_candidates;
+    urls_from_candidates.reserve(candidates.size());
+    for (const auto& candidate_ptr : candidates) {
+      if (candidate_ptr->action == blink::mojom::SpeculationAction::kPrefetch) {
+        urls_from_candidates.push_back(candidate_ptr->url);
+      }
+    }
+    base::flat_set<GURL> url_set(std::move(urls_from_candidates));
+    std::vector<base::WeakPtr<PrefetchContainer>> prefetches_to_evict;
+    for (const auto& [url, prefetch] : all_prefetches_) {
+      if (prefetch && !base::Contains(url_set, url)) {
+        prefetches_to_evict.push_back(prefetch);
+      }
+    }
+    for (const auto& prefetch : prefetches_to_evict) {
+      EvictPrefetch(prefetch);
+    }
+  }
+
   auto should_process_entry =
       [&](const blink::mojom::SpeculationCandidatePtr& candidate) {
         // This code doesn't not support speculation candidates with the action
@@ -414,13 +439,11 @@
     DCHECK(GetPrefetchService());
     base::WeakPtr<PrefetchContainer> oldest_prefetch =
         completed_non_eager_prefetches_.front();
-    auto oldest_prefetch_key = oldest_prefetch->GetPrefetchContainerKey();
     // TODO(crbug.com/1445086): We should also be checking if the prefetch is
     // currently being used to serve a navigation. In that scenario, evicting
     // doesn't make sense.
-    GetPrefetchService()->EvictPrefetch(oldest_prefetch_key);
+    EvictPrefetch(oldest_prefetch);
     completed_non_eager_prefetches_.pop_front();
-    prefetch_eviction_callback_.Run(oldest_prefetch_key.second);
     return true;
   }
 }
@@ -430,6 +453,20 @@
   prefetch_eviction_callback_ = std::move(callback);
 }
 
+void PrefetchDocumentManager::EvictPrefetch(
+    base::WeakPtr<PrefetchContainer> prefetch) {
+  DCHECK(prefetch);
+  const GURL url = prefetch->GetURL();
+  if (auto it = owned_prefetches_.find(url); it != owned_prefetches_.end()) {
+    owned_prefetches_.erase(it);
+  } else {
+    DCHECK(GetPrefetchService());
+    GetPrefetchService()->EvictPrefetch(prefetch->GetPrefetchContainerKey());
+  }
+  all_prefetches_.erase(url);
+  prefetch_eviction_callback_.Run(url);
+}
+
 DOCUMENT_USER_DATA_KEY_IMPL(PrefetchDocumentManager);
 
 }  // namespace content
diff --git a/content/browser/preloading/prefetch/prefetch_document_manager.h b/content/browser/preloading/prefetch/prefetch_document_manager.h
index 02aab691..558e750 100644
--- a/content/browser/preloading/prefetch/prefetch_document_manager.h
+++ b/content/browser/preloading/prefetch/prefetch_document_manager.h
@@ -120,6 +120,10 @@
   // See documentation for |prefetch_eviction_callback_|.
   void SetPrefetchEvictionCallback(PrefetchEvictionCallback callback);
 
+  // Destroys |prefetch|. |prefetch| could either be owned by |this| or by
+  // PrefetchService.
+  void EvictPrefetch(base::WeakPtr<PrefetchContainer> prefetch);
+
   base::WeakPtr<PrefetchDocumentManager> GetWeakPtr() {
     return weak_method_factory_.GetWeakPtr();
   }
diff --git a/content/browser/preloading/prefetch/prefetch_service.cc b/content/browser/preloading/prefetch/prefetch_service.cc
index 663c3960..47c6573f 100644
--- a/content/browser/preloading/prefetch/prefetch_service.cc
+++ b/content/browser/preloading/prefetch/prefetch_service.cc
@@ -418,7 +418,7 @@
   // If this happens, then we just delete the old prefetch and add the new
   // prefetch to |all_prefetches_|.
   auto prefetch_iter = all_prefetches_.find(prefetch_container_key);
-  if (prefetch_iter != all_prefetches_.end()) {
+  if (prefetch_iter != all_prefetches_.end() && prefetch_iter->second) {
     ResetPrefetch(prefetch_iter->second);
   }
   all_prefetches_[prefetch_container_key] = prefetch_container;
@@ -918,6 +918,10 @@
   DCHECK(prefetch_container);
   prefetch_container->SetPrefetchStatus(PrefetchStatus::kPrefetchEvicted);
   ResetPrefetch(prefetch_container);
+  if (active_prefetches_.size() <
+      PrefetchServiceMaximumNumberOfConcurrentPrefetches()) {
+    Prefetch();
+  }
 }
 
 void PrefetchService::StartSinglePrefetch(
@@ -936,6 +940,10 @@
 
   TakeOwnershipOfPrefetch(prefetch_container);
 
+  // Note: This must be called before CanPrefetchNow() below to prevent
+  // re-entrancy.
+  active_prefetches_.insert(prefetch_container->GetPrefetchContainerKey());
+
   const bool is_above_limit =
       (PrefetchNewLimitsEnabled() &&
        !prefetch_container->GetPrefetchDocumentManager()->CanPrefetchNow(
@@ -966,8 +974,6 @@
 
   MakePrefetchRequest(prefetch_container, prefetch_container->GetURL());
 
-  active_prefetches_.insert(prefetch_container->GetPrefetchContainerKey());
-
   PrefetchDocumentManager* prefetch_document_manager =
       prefetch_container->GetPrefetchDocumentManager();
   if (prefetch_container->GetPrefetchType().IsProxyRequiredWhenCrossOrigin() &&
diff --git a/content/browser/preloading/prefetch/prefetch_service_unittest.cc b/content/browser/preloading/prefetch/prefetch_service_unittest.cc
index 4a3bc172..dfeadd5 100644
--- a/content/browser/preloading/prefetch/prefetch_service_unittest.cc
+++ b/content/browser/preloading/prefetch/prefetch_service_unittest.cc
@@ -5847,6 +5847,12 @@
     MakePrefetchOnMainFrame(
         url, PrefetchType(/*use_prefetch_proxy=*/false, eagerness));
     base::RunLoop().RunUntilIdle();
+    return CompleteExistingPrefetch(url);
+  }
+
+  // Unlike the above method, this expects the prefetch for |url| to have
+  // already been triggered.
+  base::WeakPtr<PrefetchContainer> CompleteExistingPrefetch(GURL url) {
     VerifyCommonRequestState(url,
                              /*use_prefetch_proxy=*/false);
     MakeResponseAndWait(net::HTTP_OK, net::OK, kHTMLMimeType,
@@ -6007,5 +6013,185 @@
   }
 }
 
+TEST_F(PrefetchServiceNewLimitsTest, PrefetchWithNoCandidateIsNotStarted) {
+  const GURL url_1 = GURL("https://example.com/one");
+  const GURL url_2 = GURL("https://example.com/two");
+  const GURL url_3 = GURL("https://example.com/three");
+
+  NavigateAndCommit(GURL("https://example.com"));
+
+  MakePrefetchService(
+      std::make_unique<testing::NiceMock<MockPrefetchServiceDelegate>>(
+          /*num_on_prefetch_likely_calls=*/3));
+
+  auto candidate_1 = blink::mojom::SpeculationCandidate::New();
+  candidate_1->url = url_1;
+  candidate_1->action = blink::mojom::SpeculationAction::kPrefetch;
+  candidate_1->eagerness = blink::mojom::SpeculationEagerness::kEager;
+  candidate_1->referrer = blink::mojom::Referrer::New();
+  auto candidate_2 = candidate_1.Clone();
+  candidate_2->url = url_2;
+  auto candidate_3 = candidate_1.Clone();
+  candidate_3->url = url_3;
+
+  auto* prefetch_document_manager =
+      PrefetchDocumentManager::GetOrCreateForCurrentDocument(main_rfh());
+  ASSERT_TRUE(prefetch_document_manager);
+
+  base::MockRepeatingCallback<void(const GURL& url)> mock_eviction_callback;
+  EXPECT_CALL(mock_eviction_callback, Run(url_2)).Times(1);
+  prefetch_document_manager->SetPrefetchEvictionCallback(
+      mock_eviction_callback.Get());
+
+  // Send 3 candidates to PrefetchDocumentManager.
+  std::vector<blink::mojom::SpeculationCandidatePtr> candidates;
+  candidates.push_back(candidate_1.Clone());
+  candidates.push_back(candidate_2.Clone());
+  candidates.push_back(candidate_3.Clone());
+  prefetch_document_manager->ProcessCandidates(candidates,
+                                               /*devtools_observer=*/nullptr);
+  base::RunLoop().RunUntilIdle();
+  VerifyCommonRequestState(url_1, /*use_prefetch_proxy=*/false);
+
+  // Remove |url_2| from the list of candidates while a prefetch for |url_1| is
+  // in progress.
+  candidates.clear();
+  candidates.push_back(candidate_1.Clone());
+  candidates.push_back(candidate_3.Clone());
+  prefetch_document_manager->ProcessCandidates(candidates,
+                                               /*devtools_observer=*/nullptr);
+
+  // Finish prefetch of |url_1|.
+  MakeResponseAndWait(net::HTTP_OK, net::OK, kHTMLMimeType,
+                      /*use_prefetch_proxy=*/false,
+                      {{"X-Testing", "Hello World"}}, kHTMLBody);
+  // PrefetchService skips |url_2| because its candidate was removed, and starts
+  // prefetching |url_3| instead.
+  VerifyCommonRequestState(url_3, /*use_prefetch_proxy=*/false);
+  // Finish prefetch of |url_2|.
+  MakeResponseAndWait(net::HTTP_OK, net::OK, kHTMLMimeType,
+                      /*use_prefetch_proxy=*/false,
+                      {{"X-Testing", "Hello World"}}, kHTMLBody);
+  // There should be no pending prefetch requests.
+  EXPECT_EQ(RequestCount(), 0);
+}
+
+TEST_F(PrefetchServiceNewLimitsTest,
+       InProgressPrefetchWithNoCandidateIsCancelled) {
+  const GURL url_1 = GURL("https://example.com/one");
+  const GURL url_2 = GURL("https://example.com/two");
+
+  NavigateAndCommit(GURL("https://example.com"));
+
+  MakePrefetchService(
+      std::make_unique<testing::NiceMock<MockPrefetchServiceDelegate>>(
+          /*num_on_prefetch_likely_calls=*/2));
+
+  auto candidate_1 = blink::mojom::SpeculationCandidate::New();
+  candidate_1->url = url_1;
+  candidate_1->action = blink::mojom::SpeculationAction::kPrefetch;
+  candidate_1->eagerness = blink::mojom::SpeculationEagerness::kEager;
+  candidate_1->referrer = blink::mojom::Referrer::New();
+  auto candidate_2 = candidate_1.Clone();
+  candidate_2->url = url_2;
+
+  auto* prefetch_document_manager =
+      PrefetchDocumentManager::GetOrCreateForCurrentDocument(main_rfh());
+  ASSERT_TRUE(prefetch_document_manager);
+
+  base::MockRepeatingCallback<void(const GURL& url)> mock_eviction_callback;
+  EXPECT_CALL(mock_eviction_callback, Run(url_1)).Times(1);
+  prefetch_document_manager->SetPrefetchEvictionCallback(
+      mock_eviction_callback.Get());
+
+  // Send 2 candidates to PrefetchDocumentManager.
+  std::vector<blink::mojom::SpeculationCandidatePtr> candidates;
+  candidates.push_back(candidate_1.Clone());
+  candidates.push_back(candidate_2.Clone());
+  prefetch_document_manager->ProcessCandidates(candidates,
+                                               /*devtools_observer=*/nullptr);
+  base::RunLoop().RunUntilIdle();
+
+  // Prefetch for |url_1| should have started.
+  VerifyCommonRequestState(url_1, /*use_prefetch_proxy=*/false);
+
+  // Remove |candidate_1|.
+  candidates.clear();
+  candidates.push_back(candidate_2.Clone());
+  prefetch_document_manager->ProcessCandidates(candidates,
+                                               /*devtools_observer=*/nullptr);
+  base::RunLoop().RunUntilIdle();
+
+  // The prefetch for |url_1| should be cancelled, and prefetch for |url_2|
+  // should have started.
+
+  EXPECT_EQ(test_url_loader_factory_.pending_requests()->size(), 2u);
+  // The client for the first request should be disconnected.
+  EXPECT_FALSE(
+      test_url_loader_factory_.GetPendingRequest(0)->client.is_connected());
+  // Clears out first request.
+  MakeResponseAndWait(net::HTTP_OK, net::OK, kHTMLMimeType,
+                      /*use_prefetch_proxy=*/false,
+                      {{"X-Testing", "Hello World"}}, kHTMLBody);
+  VerifyCommonRequestState(url_2, /*use_prefetch_proxy=*/false);
+  base::WeakPtr<PrefetchContainer> serveable_prefetch_container =
+      GetPrefetchToServe(url_1);
+  EXPECT_FALSE(serveable_prefetch_container);
+}
+
+TEST_F(PrefetchServiceNewLimitsTest,
+       CompletedPrefetchWithNoCandidateIsEvicted) {
+  const GURL url_1 = GURL("https://example.com/one");
+  const GURL url_2 = GURL("https://example.com/two");
+  const GURL url_3 = GURL("https://example.com/three");
+
+  NavigateAndCommit(GURL("https://example.com"));
+
+  MakePrefetchService(
+      std::make_unique<testing::NiceMock<MockPrefetchServiceDelegate>>(
+          /*num_on_prefetch_likely_calls=*/2));
+
+  auto candidate_1 = blink::mojom::SpeculationCandidate::New();
+  candidate_1->url = url_1;
+  candidate_1->action = blink::mojom::SpeculationAction::kPrefetch;
+  candidate_1->eagerness = blink::mojom::SpeculationEagerness::kEager;
+  candidate_1->referrer = blink::mojom::Referrer::New();
+  auto candidate_2 = candidate_1.Clone();
+  candidate_2->url = url_2;
+
+  auto* prefetch_document_manager =
+      PrefetchDocumentManager::GetOrCreateForCurrentDocument(main_rfh());
+  ASSERT_TRUE(prefetch_document_manager);
+
+  base::MockRepeatingCallback<void(const GURL& url)> mock_eviction_callback;
+  EXPECT_CALL(mock_eviction_callback, Run(url_1)).Times(1);
+  prefetch_document_manager->SetPrefetchEvictionCallback(
+      mock_eviction_callback.Get());
+
+  // Send 2 candidates to PrefetchDocumentManager.
+  std::vector<blink::mojom::SpeculationCandidatePtr> candidates;
+  candidates.push_back(candidate_1.Clone());
+  candidates.push_back(candidate_2.Clone());
+  prefetch_document_manager->ProcessCandidates(candidates,
+                                               /*devtools_observer=*/nullptr);
+  base::RunLoop().RunUntilIdle();
+
+  // Complete prefetches for |url_1| and |url_2|.
+  base::WeakPtr<PrefetchContainer> prefetch_1 = CompleteExistingPrefetch(url_1);
+  ASSERT_TRUE(prefetch_1);
+  base::WeakPtr<PrefetchContainer> prefetch_2 = CompleteExistingPrefetch(url_2);
+  ASSERT_TRUE(prefetch_2);
+
+  // Remove |candidate_1|.
+  candidates.clear();
+  candidates.push_back(candidate_2.Clone());
+  prefetch_document_manager->ProcessCandidates(candidates,
+                                               /*devtools_observer=*/nullptr);
+  base::RunLoop().RunUntilIdle();
+  // |prefetch_1| should have been removed.
+  EXPECT_FALSE(prefetch_1);
+  EXPECT_TRUE(prefetch_2);
+}
+
 }  // namespace
 }  // namespace content
diff --git a/content/browser/preloading/preloading_decider_unittest.cc b/content/browser/preloading/preloading_decider_unittest.cc
index 510f407..cdbeaf08f9 100644
--- a/content/browser/preloading/preloading_decider_unittest.cc
+++ b/content/browser/preloading/preloading_decider_unittest.cc
@@ -628,5 +628,159 @@
   EXPECT_EQ(1u, GetPrefetchService()->prefetches_.size());
 }
 
+// Tests that candidate removal causes a prefetch to be destroyed, and that
+// a reinserted candidate with the same url is re-processed.
+TEST_F(PreloadingDeciderTest, ProcessCandidates_EagerCandidateRemoval) {
+  base::test::ScopedFeatureList scoped_feature_list;
+  scoped_feature_list.InitWithFeatures({features::kPrefetchNewLimits}, {});
+
+  auto* preloading_decider =
+      PreloadingDecider::GetOrCreateForCurrentDocument(&GetPrimaryMainFrame());
+  ASSERT_TRUE(preloading_decider);
+  const GURL url_1 = GetSameOriginUrl("/candidate1.html");
+  const GURL url_2 = GetSameOriginUrl("/candidate2.html");
+
+  auto candidate_1 = blink::mojom::SpeculationCandidate::New();
+  candidate_1->url = url_1;
+  candidate_1->action = blink::mojom::SpeculationAction::kPrefetch;
+  candidate_1->referrer = blink::mojom::Referrer::New();
+  candidate_1->eagerness = blink::mojom::SpeculationEagerness::kEager;
+  candidate_1->requires_anonymous_client_ip_when_cross_origin = false;
+
+  auto candidate_2 = candidate_1.Clone();
+  candidate_2->url = url_2;
+
+  std::vector<blink::mojom::SpeculationCandidatePtr> candidates;
+  candidates.push_back(candidate_1.Clone());
+  candidates.push_back(candidate_2.Clone());
+  preloading_decider->UpdateSpeculationCandidates(candidates);
+
+  const auto& prefetches = GetPrefetchService()->prefetches_;
+  ASSERT_EQ(2u, prefetches.size());
+  EXPECT_EQ(prefetches[0]->GetURL(), url_1);
+  EXPECT_EQ(prefetches[1]->GetURL(), url_2);
+
+  // Remove |candidate_2|.
+  candidates.clear();
+  candidates.push_back(candidate_1.Clone());
+  preloading_decider->UpdateSpeculationCandidates(candidates);
+
+  EXPECT_TRUE(prefetches[0]);
+  EXPECT_FALSE(prefetches[1]);
+
+  // Re-add |candidate_2|.
+  candidates.clear();
+  candidates.push_back(candidate_1.Clone());
+  candidates.push_back(candidate_2.Clone());
+  preloading_decider->UpdateSpeculationCandidates(candidates);
+
+  ASSERT_EQ(3u, prefetches.size());
+  EXPECT_TRUE(prefetches[0]);
+  EXPECT_FALSE(prefetches[1]);
+  EXPECT_EQ(prefetches[2]->GetURL(), url_2);
+}
+
+// Tests that candidate removal works correctly for non-eager candidates, and
+// that a non-eager candidate is reprocessed correctly after re-insertion.
+TEST_F(PreloadingDeciderTest, ProcessCandidates_NonEagerCandidateRemoval) {
+  base::test::ScopedFeatureList scoped_feature_list;
+  scoped_feature_list.InitWithFeatures({features::kPrefetchNewLimits}, {});
+
+  auto* preloading_decider =
+      PreloadingDecider::GetOrCreateForCurrentDocument(&GetPrimaryMainFrame());
+  ASSERT_TRUE(preloading_decider);
+  const GURL url_1 = GetSameOriginUrl("/candidate1.html");
+  const GURL url_2 = GetSameOriginUrl("/candidate2.html");
+
+  auto candidate_1 = blink::mojom::SpeculationCandidate::New();
+  candidate_1->url = url_1;
+  candidate_1->action = blink::mojom::SpeculationAction::kPrefetch;
+  candidate_1->referrer = blink::mojom::Referrer::New();
+  candidate_1->eagerness = blink::mojom::SpeculationEagerness::kEager;
+
+  auto candidate_2 = candidate_1.Clone();
+  candidate_2->url = url_2;
+  candidate_2->eagerness = blink::mojom::SpeculationEagerness::kConservative;
+
+  std::vector<blink::mojom::SpeculationCandidatePtr> candidates;
+  candidates.push_back(candidate_1.Clone());
+  candidates.push_back(candidate_2.Clone());
+  preloading_decider->UpdateSpeculationCandidates(candidates);
+
+  const auto& prefetches = GetPrefetchService()->prefetches_;
+  ASSERT_EQ(1u, prefetches.size());
+  EXPECT_EQ(prefetches[0]->GetURL(), url_1);
+
+  preloading_decider->OnPointerDown(url_2);
+
+  ASSERT_EQ(2u, prefetches.size());
+  EXPECT_TRUE(prefetches[0]);
+  EXPECT_EQ(prefetches[1]->GetURL(), url_2);
+
+  // Remove |candidate_2|.
+  candidates.clear();
+  candidates.push_back(candidate_1.Clone());
+  preloading_decider->UpdateSpeculationCandidates(candidates);
+
+  ASSERT_EQ(2u, prefetches.size());
+  EXPECT_TRUE(prefetches[0]);
+  EXPECT_FALSE(prefetches[1]);
+
+  // Re-add |candidate_2|, remove |candidate_1|.
+  candidates.clear();
+  candidates.push_back(candidate_2.Clone());
+  preloading_decider->UpdateSpeculationCandidates(candidates);
+
+  ASSERT_EQ(2u, prefetches.size());
+  EXPECT_FALSE(prefetches[0]);
+
+  preloading_decider->OnPointerDown(url_2);
+
+  ASSERT_EQ(3u, prefetches.size());
+  EXPECT_TRUE(prefetches[2]);
+  EXPECT_EQ(prefetches[2]->GetURL(), url_2);
+}
+
+// Test to demonstrate current behaviour where a prefetch is still considered
+// to have a speculation candidate even if its original triggering speculation
+// candidate was removed; so long as there exists a candidate with the same
+// URL.
+TEST_F(PreloadingDeciderTest,
+       ProcessCandidates_SecondCandidateWithSameUrlKeepsPrefetchAlive) {
+  base::test::ScopedFeatureList scoped_feature_list;
+  scoped_feature_list.InitWithFeatures({features::kPrefetchNewLimits}, {});
+
+  auto* preloading_decider =
+      PreloadingDecider::GetOrCreateForCurrentDocument(&GetPrimaryMainFrame());
+  ASSERT_TRUE(preloading_decider);
+  const GURL url = GetSameOriginUrl("/candidate.html");
+
+  auto candidate_1 = blink::mojom::SpeculationCandidate::New();
+  candidate_1->url = url;
+  candidate_1->action = blink::mojom::SpeculationAction::kPrefetch;
+  candidate_1->referrer = blink::mojom::Referrer::New();
+  candidate_1->eagerness = blink::mojom::SpeculationEagerness::kEager;
+
+  auto candidate_2 = candidate_1.Clone();
+  candidate_2->eagerness = blink::mojom::SpeculationEagerness::kConservative;
+
+  std::vector<blink::mojom::SpeculationCandidatePtr> candidates;
+  candidates.push_back(candidate_1.Clone());
+  candidates.push_back(candidate_2.Clone());
+  preloading_decider->UpdateSpeculationCandidates(candidates);
+
+  const auto& prefetches = GetPrefetchService()->prefetches_;
+  ASSERT_EQ(prefetches.size(), 1u);
+  EXPECT_EQ(prefetches[0]->GetURL(), url);
+
+  // Remove |candidate_1|.
+  candidates.clear();
+  candidates.push_back(candidate_2.Clone());
+  preloading_decider->UpdateSpeculationCandidates(candidates);
+
+  EXPECT_EQ(prefetches.size(), 1u);
+  EXPECT_TRUE(prefetches[0]);
+}
+
 }  // namespace
 }  // namespace content
diff --git a/content/browser/renderer_host/render_frame_host_impl.cc b/content/browser/renderer_host/render_frame_host_impl.cc
index 04d9b108..bef2d08 100644
--- a/content/browser/renderer_host/render_frame_host_impl.cc
+++ b/content/browser/renderer_host/render_frame_host_impl.cc
@@ -263,6 +263,7 @@
 #include "ui/accessibility/ax_action_handler_registry.h"
 #include "ui/accessibility/ax_common.h"
 #include "ui/accessibility/ax_tree_update.h"
+#include "ui/base/ime/text_input_client.h"
 #include "ui/display/screen.h"
 #include "ui/events/event_constants.h"
 #include "url/gurl.h"
@@ -4505,6 +4506,20 @@
 
   DCHECK(owner_);  // See `owner_` invariants about `IsActive()`.
   owner_->SetFocusedFrame(GetSiteInstance()->group());
+
+#if BUILDFLAG(IS_WIN)
+  // If the frame has a url, notify the view to allow it to supply the Url to
+  // any interested IME (e.g. Windows 11's TSF uses this information).
+  if (!last_committed_url_.is_empty()) {
+    RenderWidgetHostView* view = render_view_host_->GetWidget()->GetView();
+    if (view) {
+      ui::TextInputClient* input_client = view->GetTextInputClient();
+      if (input_client) {
+        input_client->OnFrameFocusChanged();
+      }
+    }
+  }
+#endif  // BUILDFLAG(IS_WIN)
 }
 
 void RenderFrameHostImpl::DidCallFocus() {
diff --git a/content/browser/renderer_host/render_widget_host_view_aura.cc b/content/browser/renderer_host/render_widget_host_view_aura.cc
index 0e521106..c01cc787 100644
--- a/content/browser/renderer_host/render_widget_host_view_aura.cc
+++ b/content/browser/renderer_host/render_widget_host_view_aura.cc
@@ -1842,6 +1842,12 @@
     editing_context.page_url = frame->GetLastCommittedURL();
   return editing_context;
 }
+
+void RenderWidgetHostViewAura::OnFrameFocusChanged() {
+  if (GetInputMethod()) {
+    GetInputMethod()->OnUrlChanged();
+  }
+}
 #endif
 
 ////////////////////////////////////////////////////////////////////////////////
diff --git a/content/browser/renderer_host/render_widget_host_view_aura.h b/content/browser/renderer_host/render_widget_host_view_aura.h
index cf4d910..7631ad8f 100644
--- a/content/browser/renderer_host/render_widget_host_view_aura.h
+++ b/content/browser/renderer_host/render_widget_host_view_aura.h
@@ -292,6 +292,8 @@
   // content.
   // https://docs.microsoft.com/en-us/windows/win32/tsf/predefined-properties
   ui::TextInputClient::EditingContext GetTextEditingContext() override;
+
+  void OnFrameFocusChanged() override;
 #endif
 
   // Overridden from display::DisplayObserver:
diff --git a/content/browser/renderer_host/render_widget_host_view_base.cc b/content/browser/renderer_host/render_widget_host_view_base.cc
index ed1488e..f522bf1 100644
--- a/content/browser/renderer_host/render_widget_host_view_base.cc
+++ b/content/browser/renderer_host/render_widget_host_view_base.cc
@@ -174,7 +174,6 @@
 }
 
 ui::TextInputClient* RenderWidgetHostViewBase::GetTextInputClient() {
-  NOTREACHED();
   return nullptr;
 }
 
diff --git a/content/test/content_test_bundle_data.filelist b/content/test/content_test_bundle_data.filelist
index 46ce19d..4e268ba 100644
--- a/content/test/content_test_bundle_data.filelist
+++ b/content/test/content_test_bundle_data.filelist
@@ -5135,6 +5135,7 @@
 data/attribution_reporting/interop/dedup_key.json
 data/attribution_reporting/interop/default_config.json
 data/attribution_reporting/interop/destination_limit.json
+data/attribution_reporting/interop/destination_throttle_limit.json
 data/attribution_reporting/interop/event_level_report_time.json
 data/attribution_reporting/interop/event_level_storage_limit.json
 data/attribution_reporting/interop/event_level_trigger_filter_data.json
diff --git a/content/test/data/attribution_reporting/interop/README.md b/content/test/data/attribution_reporting/interop/README.md
index cfb5762..1c4eb7b9 100644
--- a/content/test/data/attribution_reporting/interop/README.md
+++ b/content/test/data/attribution_reporting/interop/README.md
@@ -35,6 +35,18 @@
   // Formatted as a base-10 string.
   "max_destinations_per_source_site_reporting_site": "100",
 
+  // Positive integer that controls the maximum number of distinct destinations
+  // covered by sources for a given (source site, reporting site) over
+  // a rate limiting window.
+  // Formatted as a base-10 string.
+  "max_destinations_per_rate_limit_window": "200",
+
+  // Positive integer that controls the total maximum number of distinct
+  // destinations covered by sources for a given source site over
+  // a rate limiting window.
+  // Formatted as a base-10 string.
+  "max_destinations_per_rate_limit_window_reporting_site": "50",
+
   // Positive integer that controls the rate-limiting time window in days for
   // attribution. Formatted as a base-10 string.
   "rate_limit_time_window": "30",
diff --git a/content/test/data/attribution_reporting/interop/default_config.json b/content/test/data/attribution_reporting/interop/default_config.json
index 8acf539..4f64ffd 100644
--- a/content/test/data/attribution_reporting/interop/default_config.json
+++ b/content/test/data/attribution_reporting/interop/default_config.json
@@ -1,6 +1,8 @@
 {
   "max_sources_per_origin": "1024",
   "max_destinations_per_source_site_reporting_site": "100",
+  "max_destinations_per_rate_limit_window": "200",
+  "max_destinations_per_rate_limit_window_reporting_site": "50",
   "rate_limit_time_window": "30",
   "rate_limit_max_source_registration_reporting_origins": "100",
   "rate_limit_max_attribution_reporting_origins": "10",
diff --git a/content/test/data/attribution_reporting/interop/destination_throttle_limit.json b/content/test/data/attribution_reporting/interop/destination_throttle_limit.json
new file mode 100644
index 0000000..ec82421
--- /dev/null
+++ b/content/test/data/attribution_reporting/interop/destination_throttle_limit.json
@@ -0,0 +1,385 @@
+{
+  "description": "Unique destination rate limit within rate limit window",
+  "api_config": {
+    "max_destinations_per_rate_limit_window": "2",
+    "max_destinations_per_rate_limit_window_reporting_site": "1"
+  },
+  "input": {
+    "sources": [
+      // Should be dropped due to destination reporting site rate limit.
+      {
+        "timestamp": "1643235572000",
+        "registration_request": {
+          "source_origin": "https://source1.test",
+          "attribution_src_url": "https://reporter1.test/register-source",
+          "source_type": "navigation"
+        },
+        "responses": [{
+          "url": "https://reporter1.test/register-source",
+          "response": {
+            "Attribution-Reporting-Register-Source": {
+              "debug_reporting": true,
+              "destination": [
+                "https://example.destination1.test",
+                "https://destination2.test"
+              ],
+              "source_event_id": "987"
+            }
+          }
+        }]
+      },
+      {
+        "timestamp": "1643235573000",
+        "registration_request": {
+          "source_origin": "https://source1.test",
+          "attribution_src_url": "https://reporter1.test/register-source",
+          "source_type": "navigation"
+        },
+        "responses": [{
+          "url": "https://reporter1.test/register-source",
+          "response": {
+            "Attribution-Reporting-Register-Source": {
+              "debug_reporting": true,
+              "destination": "https://example.destination3.test",
+              "source_event_id": "111"
+            }
+          }
+        }]
+      },
+      // Should be dropped due to destination reporting site rate limit.
+      {
+        "timestamp": "1643235574000",
+        "registration_request": {
+          "source_origin": "https://source1.test",
+          "attribution_src_url": "https://a.reporter1.test/register-source",
+          "source_type": "navigation"
+        },
+        "responses": [{
+          "url": "https://a.reporter1.test/register-source",
+          "response": {
+            "Attribution-Reporting-Register-Source": {
+              "debug_reporting": true,
+              "destination": "https://destination4.test",
+              "source_event_id": "222"
+            }
+          }
+        }]
+      },
+      {
+        "timestamp": "1643235575000",
+        "registration_request": {
+          "source_origin": "https://source1.test",
+          "attribution_src_url": "https://reporter2.test/register-source",
+          "source_type": "navigation"
+        },
+        "responses": [{
+          "url": "https://reporter2.test/register-source",
+          "response": {
+            "Attribution-Reporting-Register-Source": {
+              "debug_reporting": true,
+              "destination": "https://destination5.test",
+              "source_event_id": "123"
+            }
+          }
+        }]
+      },
+      // Should be dropped due to max destination limit.
+      {
+        "timestamp": "1643235576000",
+        "registration_request": {
+          "source_origin": "https://source1.test",
+          "attribution_src_url": "https://reporter3.test/register-source",
+          "source_type": "navigation"
+        },
+        "responses": [{
+          "url": "https://reporter3.test/register-source",
+          "debug_permission": true,
+          "response": {
+            "Attribution-Reporting-Register-Source": {
+              "debug_reporting": true,
+              "destination": "https://destination6.test",
+              "source_event_id": "567"
+            }
+          }
+        }]
+      },
+      // Should be dropped due to both max destination and reporting site
+      // limit.
+      {
+        "timestamp": "1643235577000",
+        "registration_request": {
+          "source_origin": "https://source1.test",
+          "attribution_src_url": "https://reporter3.test/register-source",
+          "source_type": "navigation"
+        },
+        "responses": [{
+          "url": "https://reporter3.test/register-source",
+          "response": {
+            "Attribution-Reporting-Register-Source": {
+              "debug_reporting": true,
+              "destination": [
+                "https://destination7.test",
+                "https://destination8.test"
+              ],
+              "source_event_id": "765"
+            }
+          }
+        }]
+      },
+      // Should be processed as the rate limit window is 1 minute.
+      {
+        "timestamp": "1643235634000",
+        "registration_request": {
+          "source_origin": "https://source1.test",
+          "attribution_src_url": "https://reporter1.test/register-source",
+          "source_type": "navigation"
+        },
+        "responses": [{
+          "url": "https://reporter1.test/register-source",
+          "response": {
+            "Attribution-Reporting-Register-Source": {
+              "debug_reporting": true,
+              "destination": "https://destination8.test",
+              "source_event_id": "444"
+            }
+          }
+        }]
+      },
+    ],
+    "triggers": [
+      // Should be dropped as there is no matching source.
+      {
+        "timestamp": "1643235634001",
+        "registration_request": {
+          "attribution_src_url": "https://reporter1.test/register-trigger",
+          "destination_origin": "https://destination1.test"
+        },
+        "responses": [{
+          "url": "https://reporter1.test/register-trigger",
+          "response": {
+            "Attribution-Reporting-Register-Trigger": {
+              "event_trigger_data": [
+                {
+                  "trigger_data": "1"
+                }
+              ]
+            }
+          }
+        }]
+      },
+      // Should result in an event-level report.
+      {
+        "timestamp": "1643235635000",
+        "registration_request": {
+          "attribution_src_url": "https://reporter1.test/register-trigger",
+          "destination_origin": "https://destination3.test"
+        },
+        "responses": [{
+          "url": "https://reporter1.test/register-trigger",
+          "response": {
+            "Attribution-Reporting-Register-Trigger": {
+              "event_trigger_data": [
+                {
+                  "trigger_data": "2"
+                }
+              ]
+            }
+          }
+        }]
+      },
+      // Should be dropped as there is no matching source.
+      {
+        "timestamp": "1643235636000",
+        "registration_request": {
+          "attribution_src_url": "https://reporter1.test/register-trigger",
+          "destination_origin": "https://destination4.test"
+        },
+        "responses": [{
+          "url": "https://reporter1.test/register-trigger",
+          "response": {
+            "Attribution-Reporting-Register-Trigger": {
+              "event_trigger_data": [
+                {
+                  "trigger_data": "3"
+                }
+              ]
+            }
+          }
+        }]
+      },
+      // Should result in an event-level report.
+      {
+        "timestamp": "1643235637000",
+        "registration_request": {
+          "attribution_src_url": "https://reporter2.test/register-trigger",
+          "destination_origin": "https://destination5.test"
+        },
+        "responses": [{
+          "url": "https://reporter2.test/register-trigger",
+          "response": {
+            "Attribution-Reporting-Register-Trigger": {
+              "event_trigger_data": [
+                {
+                  "trigger_data": "4"
+                }
+              ]
+            }
+          }
+        }]
+      },
+      // Should be dropped as there is no matching source.
+      {
+        "timestamp": "1643235638000",
+        "registration_request": {
+          "attribution_src_url": "https://reporter3.test/register-trigger",
+          "destination_origin": "https://destination6.test"
+        },
+        "responses": [{
+          "url": "https://reporter3.test/register-trigger",
+          "response": {
+            "Attribution-Reporting-Register-Trigger": {
+              "event_trigger_data": [
+                {
+                  "trigger_data": "5"
+                }
+              ]
+            }
+          }
+        }]
+      },
+      // Should be dropped as there is no matching source.
+      {
+        "timestamp": "1643235639000",
+        "registration_request": {
+          "attribution_src_url": "https://reporter3.test/register-trigger",
+          "destination_origin": "https://destination7.test"
+        },
+        "responses": [{
+          "url": "https://reporter3.test/register-trigger",
+          "response": {
+            "Attribution-Reporting-Register-Trigger": {
+              "event_trigger_data": [
+                {
+                  "trigger_data": "5"
+                }
+              ]
+            }
+          }
+        }]
+      },
+      // Should result in an event-level report.
+      {
+        "timestamp": "1643235640000",
+        "registration_request": {
+          "attribution_src_url": "https://reporter1.test/register-trigger",
+          "destination_origin": "https://destination8.test"
+        },
+        "responses": [{
+          "url": "https://reporter1.test/register-trigger",
+          "response": {
+            "Attribution-Reporting-Register-Trigger": {
+              "event_trigger_data": [
+                {
+                  "trigger_data": "6"
+                }
+              ]
+            }
+          }
+        }]
+      }
+    ]
+  },
+  "output": {
+    "event_level_results": [
+      {
+        "payload": {
+          "attribution_destination": "https://destination3.test",
+          "randomized_trigger_rate": 0.0024263,
+          "scheduled_report_time": "1643411973",
+          "source_event_id": "111",
+          "source_type": "navigation",
+          "trigger_data": "2"
+        },
+        "report_url": "https://reporter1.test/.well-known/attribution-reporting/report-event-attribution",
+        "report_time": "1643411973000"
+      },
+      {
+        "payload": {
+          "attribution_destination": "https://destination5.test",
+          "randomized_trigger_rate": 0.0024263,
+          "scheduled_report_time": "1643411975",
+          "source_event_id": "123",
+          "source_type": "navigation",
+          "trigger_data": "4"
+        },
+        "report_url": "https://reporter2.test/.well-known/attribution-reporting/report-event-attribution",
+        "report_time": "1643411975000"
+      },
+      {
+        "payload": {
+          "attribution_destination": "https://destination8.test",
+          "randomized_trigger_rate": 0.0024263,
+          "scheduled_report_time": "1643412034",
+          "source_event_id": "444",
+          "source_type": "navigation",
+          "trigger_data": "6"
+        },
+        "report_time": "1643412034000",
+        "report_url": "https://reporter1.test/.well-known/attribution-reporting/report-event-attribution"
+      }
+    ],
+    "verbose_debug_reports": [
+      {
+        "payload": [ {
+          "body": {
+             "attribution_destination": [ "https://destination1.test", "https://destination2.test" ],
+             "limit": "1",
+             "source_event_id": "987",
+             "source_site": "https://source1.test"
+          },
+          "type": "source-destination-rate-limit"
+       } ],
+       "report_time": "1643235572000",
+       "report_url": "https://reporter1.test/.well-known/attribution-reporting/debug/verbose"
+      },
+      {
+        "payload": [{
+          "body": {
+            "attribution_destination": "https://destination4.test",
+            "limit": "1",
+            "source_event_id": "222",
+            "source_site": "https://source1.test"
+           },
+           "type": "source-destination-rate-limit"
+        }],
+        "report_time": "1643235574000",
+        "report_url": "https://a.reporter1.test/.well-known/attribution-reporting/debug/verbose"
+      },
+      {
+        "payload": [ {
+          "body": {
+             "attribution_destination": "https://destination6.test",
+             "source_event_id": "567",
+             "source_site": "https://source1.test"
+          },
+          "type": "source-success"
+       } ],
+       "report_time": "1643235576000",
+       "report_url": "https://reporter3.test/.well-known/attribution-reporting/debug/verbose"
+      },
+      {
+        "payload": [ {
+          "body": {
+             "attribution_destination": [ "https://destination7.test", "https://destination8.test" ],
+             "limit": "1",
+             "source_event_id": "765",
+             "source_site": "https://source1.test"
+          },
+          "type": "source-destination-rate-limit"
+       } ],
+       "report_time": "1643235577000",
+       "report_url": "https://reporter3.test/.well-known/attribution-reporting/debug/verbose"
+      }
+    ]
+  }
+}
diff --git a/content/test/gpu/gpu_tests/util/websocket_server.py b/content/test/gpu/gpu_tests/util/websocket_server.py
index 5cf4e9b..b2145e0 100644
--- a/content/test/gpu/gpu_tests/util/websocket_server.py
+++ b/content/test/gpu/gpu_tests/util/websocket_server.py
@@ -3,39 +3,38 @@
 # found in the LICENSE file.
 """Code to allow tests to communicate via a websocket server."""
 
-import asyncio
 import logging
-import sys
 import threading
 from typing import Optional
 
 import websockets  # pylint: disable=import-error
-import websockets.server as ws_server  # pylint: disable=import-error
+import websockets.sync.server as sync_server  # pylint: disable=import-error
 
 WEBSOCKET_PORT_TIMEOUT_SECONDS = 10
 WEBSOCKET_SETUP_TIMEOUT_SECONDS = 5
+WEBSOCKET_CLOSE_TIMEOUT_SECONDS = 2
+SERVER_SHUTDOWN_TIMEOUT_SECONDS = 5
 
 # The client (Chrome) should never be closing the connection. If it does, it's
 # indicative of something going wrong like a renderer crash.
 ClientClosedConnectionError = websockets.exceptions.ConnectionClosedOK
 
-# Alias for readability and so that users don't have to import asyncio.
-WebsocketReceiveMessageTimeoutError = asyncio.TimeoutError
+# Alias for readability.
+WebsocketReceiveMessageTimeoutError = TimeoutError
 
 
 class WebsocketServer():
   def __init__(self):
-    """Server that abstracts the asyncio calls used under the hood.
+    """Server that abstracts the websocket library under the hood.
 
     Only supports one active connection at a time.
     """
     self.server_port = None
-    self.server_stopper = None
-    self.connection_stopper = None
     self.websocket = None
+    self.connection_stopper_event = None
+    self.connection_closed_event = None
     self.port_set_event = threading.Event()
     self.connection_received_event = threading.Event()
-    self.event_loop = None
     self._server_thread = None
 
   def StartServer(self) -> None:
@@ -54,13 +53,14 @@
     # works.
 
   def ClearCurrentConnection(self) -> None:
-    if self.connection_stopper:
-      self.connection_stopper.cancel()
-      try:
-        self.connection_stopper.exception()
-      except asyncio.CancelledError:
-        pass
-    self.connection_stopper = None
+    if self.connection_stopper_event:
+      self.connection_stopper_event.set()
+      closed = self.connection_closed_event.wait(
+          WEBSOCKET_CLOSE_TIMEOUT_SECONDS)
+      if not closed:
+        raise RuntimeError('Websocket connection did not close')
+    self.connection_stopper_event = None
+    self.connection_closed_event = None
     self.websocket = None
     self.connection_received_event.clear()
 
@@ -74,31 +74,20 @@
 
   def StopServer(self) -> None:
     self.ClearCurrentConnection()
-    if self.server_stopper:
-      self.server_stopper.cancel()
-      try:
-        self.server_stopper.exception()
-      except asyncio.CancelledError:
-        pass
-    self.server_stopper = None
-    self.server_port = None
-
-    self._server_thread.join(5)
+    self._server_thread.shutdown()
+    self._server_thread.join(SERVER_SHUTDOWN_TIMEOUT_SECONDS)
     if self._server_thread.is_alive():
       logging.error(
           'Websocket server did not shut down properly - this might be '
           'indicative of an issue in the test harness')
 
   def Send(self, message: str) -> None:
-    asyncio.run_coroutine_threadsafe(self.websocket.send(message),
-                                     self.event_loop)
+    self.websocket.send(message)
 
   def Receive(self, timeout: int) -> str:
-    future = asyncio.run_coroutine_threadsafe(
-        asyncio.wait_for(self.websocket.recv(), timeout), self.event_loop)
     try:
-      return future.result()
-    except asyncio.exceptions.TimeoutError as e:
+      return self.websocket.recv(timeout)
+    except TimeoutError as e:
       raise WebsocketReceiveMessageTimeoutError(
           'Timed out after %d seconds waiting for websocket message' %
           timeout) from e
@@ -108,39 +97,37 @@
   def __init__(self, server_instance: WebsocketServer, *args, **kwargs):
     super().__init__(*args, **kwargs)
     self._server_instance = server_instance
+    self.websocket_server = None
 
   def run(self) -> None:
-    try:
-      asyncio.run(StartWebsocketServer(self._server_instance))
-    except asyncio.CancelledError:
-      pass
-    except Exception as e:  # pylint: disable=broad-except
-      sys.stdout.write('Server thread had an exception: %s\n' % e)
+    StartWebsocketServer(self, self._server_instance)
+
+  def shutdown(self) -> None:
+    self.websocket_server.shutdown()
 
 
-async def StartWebsocketServer(server_instance: WebsocketServer) -> None:
-  async def HandleWebsocketConnection(
-      websocket: ws_server.WebSocketServerProtocol) -> None:
+def StartWebsocketServer(server_thread: _ServerThread,
+                         server_instance: WebsocketServer) -> None:
+  def HandleWebsocketConnection(
+      websocket: sync_server.ServerConnection) -> None:
     # We only allow one active connection - if there are multiple, something is
     # wrong.
-    assert server_instance.connection_stopper is None
+    assert server_instance.connection_stopper_event is None
+    assert server_instance.connection_closed_event is None
     assert server_instance.websocket is None
-    server_instance.connection_stopper = asyncio.Future()
+    server_instance.connection_stopper_event = threading.Event()
+    server_instance.connection_closed_event = threading.Event()
     # Keep our own reference in case the server clears its reference before the
     # await finishes.
-    connection_stopper = server_instance.connection_stopper
+    connection_stopper_event = server_instance.connection_stopper_event
+    connection_closed_event = server_instance.connection_closed_event
     server_instance.websocket = websocket
     server_instance.connection_received_event.set()
-    await connection_stopper
+    connection_stopper_event.wait()
+    connection_closed_event.set()
 
-  async with websockets.serve(HandleWebsocketConnection,
-                              '127.0.0.1',
-                              0,
-                              ping_interval=None,
-                              ping_timeout=None) as server:
-    server_instance.event_loop = asyncio.get_running_loop()
-    server_instance.server_port = server.sockets[0].getsockname()[1]
+  with sync_server.serve(HandleWebsocketConnection, '127.0.0.1', 0) as server:
+    server_thread.websocket_server = server
+    server_instance.server_port = server.socket.getsockname()[1]
     server_instance.port_set_event.set()
-    server_instance.server_stopper = asyncio.Future()
-    server_stopper = server_instance.server_stopper
-    await server_stopper
+    server.serve_forever()
diff --git a/gpu/command_buffer/service/shared_image/d3d_image_backing.cc b/gpu/command_buffer/service/shared_image/d3d_image_backing.cc
index 3bb031a5..4e238aea 100644
--- a/gpu/command_buffer/service/shared_image/d3d_image_backing.cc
+++ b/gpu/command_buffer/service/shared_image/d3d_image_backing.cc
@@ -1082,9 +1082,13 @@
     SharedImageManager* manager,
     MemoryTypeTracker* tracker,
     scoped_refptr<SharedContextState> context_state) {
-  return SkiaGLImageRepresentation::Create(
-      ProduceGLTexturePassthrough(manager, tracker), std::move(context_state),
-      manager, this, tracker);
+  auto gl_representation = ProduceGLTexturePassthrough(manager, tracker);
+  if (!gl_representation) {
+    return nullptr;
+  }
+  return SkiaGLImageRepresentation::Create(std::move(gl_representation),
+                                           std::move(context_state), manager,
+                                           this, tracker);
 }
 
 std::unique_ptr<SkiaGraphiteImageRepresentation>
diff --git a/headless/test/headless_printtopdf_browsertest.cc b/headless/test/headless_printtopdf_browsertest.cc
index b12b06e..1efa4e9b 100644
--- a/headless/test/headless_printtopdf_browsertest.cc
+++ b/headless/test/headless_printtopdf_browsertest.cc
@@ -376,14 +376,63 @@
 
 HEADLESS_DEVTOOLED_TEST_F(HeadlessPDFOOPIFBrowserTest);
 
-class HeadlessPDFTinyPageBrowserTest : public HeadlessPDFBrowserTestBase {
+class HeadlessPDFTinyPageBrowserTest
+    : public HeadlessPDFBrowserTestBase,
+      public testing::WithParamInterface<gfx::SizeF> {
  public:
   const char* GetUrl() override { return "/hello.html"; }
 
   base::Value::Dict GetPrintToPDFParams() override {
+    // This tests that we can print into tiny pages as some WPT
+    // tests expect that.
+    base::Value::Dict params;
+    params.Set("paperHeight", paper_height());
+    params.Set("paperWidth", paper_width());
+    params.Set("marginTop", 0);
+    params.Set("marginBottom", 0);
+    params.Set("marginLeft", 0);
+    params.Set("marginRight", 0);
+
+    return params;
+  }
+
+  void OnPDFReady(base::span<const uint8_t> pdf_span, int num_pages) override {
+    EXPECT_GT(num_pages, 0);
+  }
+
+  void OnPDFFailure(int code, const std::string& message) override {
+    ADD_FAILURE() << "code=" << code << " message: " << message
+                  << " paper size: " << paper_size().ToString();
+  }
+
+  gfx::SizeF paper_size() const { return GetParam(); }
+  float paper_height() const { return paper_size().height(); }
+  float paper_width() const { return paper_size().width(); }
+};
+
+INSTANTIATE_TEST_SUITE_P(All,
+                         HeadlessPDFTinyPageBrowserTest,
+                         testing::Values(gfx::SizeF(0.1, 0.1),
+                                         gfx::SizeF(0.01, 0.01),
+                                         gfx::SizeF(0.001, 0.001)));
+
+HEADLESS_DEVTOOLED_TEST_P(HeadlessPDFTinyPageBrowserTest);
+
+class HeadlessPDFOversizeMarginsBrowserTest
+    : public HeadlessPDFBrowserTestBase {
+ public:
+  const char* GetUrl() override { return "/hello.html"; }
+
+  base::Value::Dict GetPrintToPDFParams() override {
+    // Set paper size to be smaller than the margins and expect content size
+    // error.
     base::Value::Dict params;
     params.Set("paperHeight", 0.1);
     params.Set("paperWidth", 0.1);
+    params.Set("marginTop", 0.2);
+    params.Set("marginBottom", 0.2);
+    params.Set("marginLeft", 0.2);
+    params.Set("marginRight", 0.2);
 
     return params;
   }
@@ -396,11 +445,12 @@
     EXPECT_THAT(
         code,
         testing::Eq(static_cast<int>(crdtp::DispatchCode::INVALID_PARAMS)));
-    EXPECT_THAT(message, testing::Eq("invalid print parameters"));
+    EXPECT_THAT(message,
+                testing::Eq("invalid print parameters: content area is empty"));
   }
 };
 
-HEADLESS_DEVTOOLED_TEST_F(HeadlessPDFTinyPageBrowserTest);
+HEADLESS_DEVTOOLED_TEST_F(HeadlessPDFOversizeMarginsBrowserTest);
 
 class HeadlessPDFDisableLazyLoading : public HeadlessPDFBrowserTestBase {
  public:
diff --git a/infra/config/generated/luci/cr-buildbucket.cfg b/infra/config/generated/luci/cr-buildbucket.cfg
index 3f5ff42..543b941 100644
--- a/infra/config/generated/luci/cr-buildbucket.cfg
+++ b/infra/config/generated/luci/cr-buildbucket.cfg
@@ -11505,7 +11505,7 @@
       name: "Libfuzzer Upload Mac ASan"
       swarming_host: "chromium-swarm.appspot.com"
       dimensions: "builder:Libfuzzer Upload Mac ASan"
-      dimensions: "cores:24"
+      dimensions: "cores:12"
       dimensions: "cpu:x86-64"
       dimensions: "os:Mac-13"
       dimensions: "pool:luci.chromium.ci"
@@ -51714,6 +51714,56 @@
         enable: true
       }
     }
+    builders {
+      name: "test-single-revision"
+      swarming_host: "chromium-swarm.appspot.com"
+      dimensions: "builderless:1"
+      dimensions: "pool:luci.chromium.findit"
+      dimensions: "ssd:1"
+      exe {
+        cipd_package: "infra/chromium/bootstrapper/${platform}"
+        cipd_version: "latest"
+        cmd: "bootstrapper"
+        cmd: "-polymorphic"
+        cmd: "-properties-optional"
+      }
+      properties:
+        '{'
+        '  "$bootstrap/exe": {'
+        '    "exe": {'
+        '      "cipd_package": "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build",'
+        '      "cipd_version": "refs/heads/main",'
+        '      "cmd": ['
+        '        "luciexe"'
+        '      ]'
+        '    }'
+        '  },'
+        '  "$build/reclient": {'
+        '    "instance": "rbe-chromium-trusted",'
+        '    "jobs": 250,'
+        '    "metrics_project": "chromium-reclient-metrics"'
+        '  },'
+        '  "$recipe_engine/resultdb/test_presentation": {'
+        '    "column_keys": [],'
+        '    "grouping_keys": ['
+        '      "status",'
+        '      "v.test_suite"'
+        '    ]'
+        '  },'
+        '  "led_builder_is_bootstrapped": true,'
+        '  "recipe": "gofindit/chromium/test_single_revision"'
+        '}'
+      execution_timeout_secs: 28800
+      build_numbers: YES
+      service_account: "findit-builder@chops-service-accounts.iam.gserviceaccount.com"
+      experiments {
+        key: "luci.recipes.use_python3"
+        value: 100
+      }
+      resultdb {
+        enable: true
+      }
+    }
   }
 }
 buckets {
diff --git a/infra/config/generated/luci/luci-milo.cfg b/infra/config/generated/luci/luci-milo.cfg
index ad84c58..789d2ee 100644
--- a/infra/config/generated/luci/luci-milo.cfg
+++ b/infra/config/generated/luci/luci-milo.cfg
@@ -16793,6 +16793,9 @@
   builders {
     name: "buildbucket/luci.chromium.findit/gofindit-culprit-verification"
   }
+  builders {
+    name: "buildbucket/luci.chromium.findit/test-single-revision"
+  }
   builder_view_only: true
 }
 consoles {
diff --git a/infra/config/recipes.star b/infra/config/recipes.star
index 27df64d1..ba004ff3 100644
--- a/infra/config/recipes.star
+++ b/infra/config/recipes.star
@@ -236,6 +236,11 @@
 )
 
 build_recipe(
+    name = "recipe:gofindit/chromium/test_single_revision",
+    bootstrappable = POLYMORPHIC,
+)
+
+build_recipe(
     name = "recipe:perf/crossbench",
 )
 
diff --git a/infra/config/subprojects/chromium/ci/chromium.fuzz.star b/infra/config/subprojects/chromium/ci/chromium.fuzz.star
index 29a8b719..5e18e0a4 100644
--- a/infra/config/subprojects/chromium/ci/chromium.fuzz.star
+++ b/infra/config/subprojects/chromium/ci/chromium.fuzz.star
@@ -736,7 +736,7 @@
 ci.builder(
     name = "Libfuzzer Upload Mac ASan",
     executable = "recipe:chromium/fuzz",
-    cores = 24,
+    cores = 12,
     os = os.MAC_DEFAULT,
     console_view_entry = consoles.console_view_entry(
         category = "libfuzz",
diff --git a/infra/config/subprojects/findit/OWNERS b/infra/config/subprojects/findit/OWNERS
index a14a581..523cdbb6 100644
--- a/infra/config/subprojects/findit/OWNERS
+++ b/infra/config/subprojects/findit/OWNERS
@@ -1,4 +1,4 @@
-aredulla@google.com
+beining@google.com
 mdraz@google.com
 meiring@google.com
 mwarton@google.com
diff --git a/infra/config/subprojects/findit/findit.star b/infra/config/subprojects/findit/findit.star
index b729a21f..7e2d6bf8 100644
--- a/infra/config/subprojects/findit/findit.star
+++ b/infra/config/subprojects/findit/findit.star
@@ -48,10 +48,18 @@
 
 # Builders are defined in lexicographic order by name
 
-# GoFindit builder to verify a culprit (go/gofindit-design-doc)
+# LUCI Bisection builder to verify a culprit (go/luci-bisection-design-doc).
 builder(
     name = "gofindit-culprit-verification",
     executable = "recipe:gofindit/chromium/single_revision",
     reclient_instance = reclient.instance.DEFAULT_TRUSTED,
     reclient_jobs = reclient.jobs.DEFAULT,
 )
+
+# Builder to run a test for a single revision.
+builder(
+    name = "test-single-revision",
+    executable = "recipe:gofindit/chromium/test_single_revision",
+    reclient_instance = reclient.instance.DEFAULT_TRUSTED,
+    reclient_jobs = reclient.jobs.DEFAULT,
+)
diff --git a/ios/chrome/browser/ui/authentication/signin_promo_view_mediator.mm b/ios/chrome/browser/ui/authentication/signin_promo_view_mediator.mm
index 617dd8c0..827d8f60 100644
--- a/ios/chrome/browser/ui/authentication/signin_promo_view_mediator.mm
+++ b/ios/chrome/browser/ui/authentication/signin_promo_view_mediator.mm
@@ -69,6 +69,7 @@
       return true;
     case signin_metrics::AccessPoint::ACCESS_POINT_START_PAGE:
     case signin_metrics::AccessPoint::ACCESS_POINT_NTP_LINK:
+    case signin_metrics::AccessPoint::ACCESS_POINT_MENU:
     case signin_metrics::AccessPoint::ACCESS_POINT_SUPERVISED_USER:
     case signin_metrics::AccessPoint::ACCESS_POINT_EXTENSION_INSTALL_BUBBLE:
     case signin_metrics::AccessPoint::ACCESS_POINT_EXTENSIONS:
@@ -77,7 +78,6 @@
     case signin_metrics::AccessPoint::ACCESS_POINT_USER_MANAGER:
     case signin_metrics::AccessPoint::ACCESS_POINT_DEVICES_PAGE:
     case signin_metrics::AccessPoint::ACCESS_POINT_CLOUD_PRINT:
-    case signin_metrics::AccessPoint::ACCESS_POINT_CONTENT_AREA:
     case signin_metrics::AccessPoint::ACCESS_POINT_SIGNIN_PROMO:
     case signin_metrics::AccessPoint::ACCESS_POINT_UNKNOWN:
     case signin_metrics::AccessPoint::ACCESS_POINT_PASSWORD_BUBBLE:
@@ -145,6 +145,7 @@
     case signin_metrics::AccessPoint::ACCESS_POINT_TAB_SWITCHER:
     case signin_metrics::AccessPoint::ACCESS_POINT_START_PAGE:
     case signin_metrics::AccessPoint::ACCESS_POINT_NTP_LINK:
+    case signin_metrics::AccessPoint::ACCESS_POINT_MENU:
     case signin_metrics::AccessPoint::ACCESS_POINT_SUPERVISED_USER:
     case signin_metrics::AccessPoint::ACCESS_POINT_EXTENSION_INSTALL_BUBBLE:
     case signin_metrics::AccessPoint::ACCESS_POINT_EXTENSIONS:
@@ -153,7 +154,6 @@
     case signin_metrics::AccessPoint::ACCESS_POINT_USER_MANAGER:
     case signin_metrics::AccessPoint::ACCESS_POINT_DEVICES_PAGE:
     case signin_metrics::AccessPoint::ACCESS_POINT_CLOUD_PRINT:
-    case signin_metrics::AccessPoint::ACCESS_POINT_CONTENT_AREA:
     case signin_metrics::AccessPoint::ACCESS_POINT_SIGNIN_PROMO:
     case signin_metrics::AccessPoint::ACCESS_POINT_UNKNOWN:
     case signin_metrics::AccessPoint::ACCESS_POINT_PASSWORD_BUBBLE:
@@ -225,6 +225,7 @@
     case signin_metrics::AccessPoint::ACCESS_POINT_TAB_SWITCHER:
     case signin_metrics::AccessPoint::ACCESS_POINT_START_PAGE:
     case signin_metrics::AccessPoint::ACCESS_POINT_NTP_LINK:
+    case signin_metrics::AccessPoint::ACCESS_POINT_MENU:
     case signin_metrics::AccessPoint::ACCESS_POINT_SUPERVISED_USER:
     case signin_metrics::AccessPoint::ACCESS_POINT_EXTENSION_INSTALL_BUBBLE:
     case signin_metrics::AccessPoint::ACCESS_POINT_EXTENSIONS:
@@ -233,7 +234,6 @@
     case signin_metrics::AccessPoint::ACCESS_POINT_USER_MANAGER:
     case signin_metrics::AccessPoint::ACCESS_POINT_DEVICES_PAGE:
     case signin_metrics::AccessPoint::ACCESS_POINT_CLOUD_PRINT:
-    case signin_metrics::AccessPoint::ACCESS_POINT_CONTENT_AREA:
     case signin_metrics::AccessPoint::ACCESS_POINT_SIGNIN_PROMO:
     case signin_metrics::AccessPoint::ACCESS_POINT_UNKNOWN:
     case signin_metrics::AccessPoint::ACCESS_POINT_PASSWORD_BUBBLE:
@@ -305,6 +305,7 @@
     case signin_metrics::AccessPoint::ACCESS_POINT_TAB_SWITCHER:
     case signin_metrics::AccessPoint::ACCESS_POINT_START_PAGE:
     case signin_metrics::AccessPoint::ACCESS_POINT_NTP_LINK:
+    case signin_metrics::AccessPoint::ACCESS_POINT_MENU:
     case signin_metrics::AccessPoint::ACCESS_POINT_SUPERVISED_USER:
     case signin_metrics::AccessPoint::ACCESS_POINT_EXTENSION_INSTALL_BUBBLE:
     case signin_metrics::AccessPoint::ACCESS_POINT_EXTENSIONS:
@@ -313,7 +314,6 @@
     case signin_metrics::AccessPoint::ACCESS_POINT_USER_MANAGER:
     case signin_metrics::AccessPoint::ACCESS_POINT_DEVICES_PAGE:
     case signin_metrics::AccessPoint::ACCESS_POINT_CLOUD_PRINT:
-    case signin_metrics::AccessPoint::ACCESS_POINT_CONTENT_AREA:
     case signin_metrics::AccessPoint::ACCESS_POINT_SIGNIN_PROMO:
     case signin_metrics::AccessPoint::ACCESS_POINT_UNKNOWN:
     case signin_metrics::AccessPoint::ACCESS_POINT_PASSWORD_BUBBLE:
@@ -369,6 +369,7 @@
     case signin_metrics::AccessPoint::ACCESS_POINT_TAB_SWITCHER:
     case signin_metrics::AccessPoint::ACCESS_POINT_START_PAGE:
     case signin_metrics::AccessPoint::ACCESS_POINT_NTP_LINK:
+    case signin_metrics::AccessPoint::ACCESS_POINT_MENU:
     case signin_metrics::AccessPoint::ACCESS_POINT_SUPERVISED_USER:
     case signin_metrics::AccessPoint::ACCESS_POINT_EXTENSION_INSTALL_BUBBLE:
     case signin_metrics::AccessPoint::ACCESS_POINT_EXTENSIONS:
@@ -377,7 +378,6 @@
     case signin_metrics::AccessPoint::ACCESS_POINT_USER_MANAGER:
     case signin_metrics::AccessPoint::ACCESS_POINT_DEVICES_PAGE:
     case signin_metrics::AccessPoint::ACCESS_POINT_CLOUD_PRINT:
-    case signin_metrics::AccessPoint::ACCESS_POINT_CONTENT_AREA:
     case signin_metrics::AccessPoint::ACCESS_POINT_SIGNIN_PROMO:
     case signin_metrics::AccessPoint::ACCESS_POINT_UNKNOWN:
     case signin_metrics::AccessPoint::ACCESS_POINT_PASSWORD_BUBBLE:
@@ -433,6 +433,7 @@
     case signin_metrics::AccessPoint::ACCESS_POINT_TAB_SWITCHER:
     case signin_metrics::AccessPoint::ACCESS_POINT_START_PAGE:
     case signin_metrics::AccessPoint::ACCESS_POINT_NTP_LINK:
+    case signin_metrics::AccessPoint::ACCESS_POINT_MENU:
     case signin_metrics::AccessPoint::ACCESS_POINT_SUPERVISED_USER:
     case signin_metrics::AccessPoint::ACCESS_POINT_EXTENSION_INSTALL_BUBBLE:
     case signin_metrics::AccessPoint::ACCESS_POINT_EXTENSIONS:
@@ -441,7 +442,6 @@
     case signin_metrics::AccessPoint::ACCESS_POINT_USER_MANAGER:
     case signin_metrics::AccessPoint::ACCESS_POINT_DEVICES_PAGE:
     case signin_metrics::AccessPoint::ACCESS_POINT_CLOUD_PRINT:
-    case signin_metrics::AccessPoint::ACCESS_POINT_CONTENT_AREA:
     case signin_metrics::AccessPoint::ACCESS_POINT_SIGNIN_PROMO:
     case signin_metrics::AccessPoint::ACCESS_POINT_UNKNOWN:
     case signin_metrics::AccessPoint::ACCESS_POINT_PASSWORD_BUBBLE:
diff --git a/ios/chrome/browser/ui/first_run/tangible_sync/tangible_sync_screen_coordinator.mm b/ios/chrome/browser/ui/first_run/tangible_sync/tangible_sync_screen_coordinator.mm
index 0844526f..c9333e9c 100644
--- a/ios/chrome/browser/ui/first_run/tangible_sync/tangible_sync_screen_coordinator.mm
+++ b/ios/chrome/browser/ui/first_run/tangible_sync/tangible_sync_screen_coordinator.mm
@@ -86,6 +86,11 @@
   _tangibleSyncCoordinator.coordinatorCompleted = nil;
   _tangibleSyncCoordinator = nil;
   _baseNavigationController = nil;
+  _delegate = nil;
+}
+
+- (void)dealloc {
+  CHECK(!_tangibleSyncCoordinator);
 }
 
 #pragma mark - Private
diff --git a/ios/chrome/browser/ui/ntp/new_tab_page_coordinator.mm b/ios/chrome/browser/ui/ntp/new_tab_page_coordinator.mm
index 748ae661..794f3cfe 100644
--- a/ios/chrome/browser/ui/ntp/new_tab_page_coordinator.mm
+++ b/ios/chrome/browser/ui/ntp/new_tab_page_coordinator.mm
@@ -509,11 +509,13 @@
   CHECK(self.started);
   self.webState = webState;
   [self updateNTPIsVisible:YES];
+  [self updateStartForVisibilityChange:YES];
 }
 
 - (void)didNavigateAwayFromNTP {
   [self cancelOmniboxEdit];
   [self updateNTPIsVisible:NO];
+  [self updateStartForVisibilityChange:NO];
   self.webState = nullptr;
 }
 
@@ -1659,7 +1661,6 @@
         self.didAppearTime = base::TimeTicks();
       }
     }
-    [self updateStartForVisibilityChange:visible];
     // Check if feed is visible before reporting NTP visibility as the feed
     // needs to be visible in order to use for metrics.
     // TODO(crbug.com/1373650) Move isFeedVisible check to the metrics recorder
diff --git a/ios/chrome/widget_kit_extension/strings/ios_widget_kit_extension_strings.grd b/ios/chrome/widget_kit_extension/strings/ios_widget_kit_extension_strings.grd
index 86d574a..2934a90e 100644
--- a/ios/chrome/widget_kit_extension/strings/ios_widget_kit_extension_strings.grd
+++ b/ios/chrome/widget_kit_extension/strings/ios_widget_kit_extension_strings.grd
@@ -291,7 +291,7 @@
             Open WEBSITE_PLACEHOLDER
       </message>
       <message name="IDS_IOS_WIDGET_KIT_EXTENSION_SHORTCUTS_NO_SHORTCUTS_AVAILABLE_LABEL" desc="No shortcuts available label for the shortcuts widget [CHAR_LIMIT=40]" meaning="No shortcuts available label for the shortcuts widget [CHAR_LIMIT=40][iOS only]">
-          No shortcuts available
+          Your most visited sites will show up here.
       </message>
     </messages>
   </release>
diff --git a/ios/chrome/widget_kit_extension/strings/ios_widget_kit_extension_strings_grd/IDS_IOS_WIDGET_KIT_EXTENSION_SHORTCUTS_NO_SHORTCUTS_AVAILABLE_LABEL.png.sha1 b/ios/chrome/widget_kit_extension/strings/ios_widget_kit_extension_strings_grd/IDS_IOS_WIDGET_KIT_EXTENSION_SHORTCUTS_NO_SHORTCUTS_AVAILABLE_LABEL.png.sha1
index af3d08b..3a2cfd8 100644
--- a/ios/chrome/widget_kit_extension/strings/ios_widget_kit_extension_strings_grd/IDS_IOS_WIDGET_KIT_EXTENSION_SHORTCUTS_NO_SHORTCUTS_AVAILABLE_LABEL.png.sha1
+++ b/ios/chrome/widget_kit_extension/strings/ios_widget_kit_extension_strings_grd/IDS_IOS_WIDGET_KIT_EXTENSION_SHORTCUTS_NO_SHORTCUTS_AVAILABLE_LABEL.png.sha1
@@ -1 +1 @@
-60e99030c1ce54334bc4f57a1eba52eea23089b0
\ No newline at end of file
+46393e8f2806e2733292102db73d67775ca3f01a
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/ChromeInternal.framework.dSYM.ios.zip.sha1 b/ios/google_internal/frameworks/ChromeInternal.framework.dSYM.ios.zip.sha1
index ba0acc2..c8bdebe5 100644
--- a/ios/google_internal/frameworks/ChromeInternal.framework.dSYM.ios.zip.sha1
+++ b/ios/google_internal/frameworks/ChromeInternal.framework.dSYM.ios.zip.sha1
@@ -1 +1 @@
-d88964a6cc3bf26f20785ef6aa865d722419771c
\ No newline at end of file
+1b1ea699d8572e182d6c4c9679bdff9d58506884
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/ChromeSSOInternal.framework.dSYM.ios.zip.sha1 b/ios/google_internal/frameworks/ChromeSSOInternal.framework.dSYM.ios.zip.sha1
index ba43c0f..2e9b670 100644
--- a/ios/google_internal/frameworks/ChromeSSOInternal.framework.dSYM.ios.zip.sha1
+++ b/ios/google_internal/frameworks/ChromeSSOInternal.framework.dSYM.ios.zip.sha1
@@ -1 +1 @@
-70d7450b2abd5fabff60ef537f42eb2abb702b43
\ No newline at end of file
+acf06a456f090a2122a0b21331c091ae52912d87
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/chrome_internal_dynamic_framework.ios.zip.sha1 b/ios/google_internal/frameworks/chrome_internal_dynamic_framework.ios.zip.sha1
index 49ec35d6..4f6f5c3c 100644
--- a/ios/google_internal/frameworks/chrome_internal_dynamic_framework.ios.zip.sha1
+++ b/ios/google_internal/frameworks/chrome_internal_dynamic_framework.ios.zip.sha1
@@ -1 +1 @@
-8bf545bcdd5e66d690758b46f0565917fc24beb7
\ No newline at end of file
+870580c5f0c01924b1d457cd1ab7aad5033fc01e
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/chrome_internal_dynamic_framework.iossimulator.zip.sha1 b/ios/google_internal/frameworks/chrome_internal_dynamic_framework.iossimulator.zip.sha1
index 8b974c9..e4267b34 100644
--- a/ios/google_internal/frameworks/chrome_internal_dynamic_framework.iossimulator.zip.sha1
+++ b/ios/google_internal/frameworks/chrome_internal_dynamic_framework.iossimulator.zip.sha1
@@ -1 +1 @@
-0bad4e02bbdf39b233b5cb03bba2faee5391212f
\ No newline at end of file
+1b698de35d5a32911a94762e5126293acd9e030a
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/chrome_sso_internal_dynamic_framework.ios.zip.sha1 b/ios/google_internal/frameworks/chrome_sso_internal_dynamic_framework.ios.zip.sha1
index a6f0555..1518b19 100644
--- a/ios/google_internal/frameworks/chrome_sso_internal_dynamic_framework.ios.zip.sha1
+++ b/ios/google_internal/frameworks/chrome_sso_internal_dynamic_framework.ios.zip.sha1
@@ -1 +1 @@
-56581686c33be778aad730ff8af400c96c2adc61
\ No newline at end of file
+68913da8f54b180d64b22745154562db059eafbf
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/chrome_sso_internal_dynamic_framework.iossimulator.zip.sha1 b/ios/google_internal/frameworks/chrome_sso_internal_dynamic_framework.iossimulator.zip.sha1
index b0e7395..b0fc7981 100644
--- a/ios/google_internal/frameworks/chrome_sso_internal_dynamic_framework.iossimulator.zip.sha1
+++ b/ios/google_internal/frameworks/chrome_sso_internal_dynamic_framework.iossimulator.zip.sha1
@@ -1 +1 @@
-2ed3871b623980a39a195a5c0c3bf6e36ceb8073
\ No newline at end of file
+5f45e2486401c5f72835ba37598e76aa3a326055
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/chrome_test_internal_dynamic_framework.ios.zip.sha1 b/ios/google_internal/frameworks/chrome_test_internal_dynamic_framework.ios.zip.sha1
index d855384..73092f7 100644
--- a/ios/google_internal/frameworks/chrome_test_internal_dynamic_framework.ios.zip.sha1
+++ b/ios/google_internal/frameworks/chrome_test_internal_dynamic_framework.ios.zip.sha1
@@ -1 +1 @@
-3079020c1a1f25a5a3b55761db1a59f4306c2678
\ No newline at end of file
+269e2fbf26c92b57210a6e3c4a7208c8a3fb1189
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/chrome_test_internal_dynamic_framework.iossimulator.zip.sha1 b/ios/google_internal/frameworks/chrome_test_internal_dynamic_framework.iossimulator.zip.sha1
index 856f2f1..ad943e1b 100644
--- a/ios/google_internal/frameworks/chrome_test_internal_dynamic_framework.iossimulator.zip.sha1
+++ b/ios/google_internal/frameworks/chrome_test_internal_dynamic_framework.iossimulator.zip.sha1
@@ -1 +1 @@
-e09a86fd6d29df282f66d11bd1619bbcbadcfd63
\ No newline at end of file
+c4067dfffe969b9593b923fcbbe60b86149fe57d
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/remoting_internal_dynamic_framework.ios.zip.sha1 b/ios/google_internal/frameworks/remoting_internal_dynamic_framework.ios.zip.sha1
index bdeeae7d..b1b5aed 100644
--- a/ios/google_internal/frameworks/remoting_internal_dynamic_framework.ios.zip.sha1
+++ b/ios/google_internal/frameworks/remoting_internal_dynamic_framework.ios.zip.sha1
@@ -1 +1 @@
-c68e394e064af5886a84ba188255812a1f634e86
\ No newline at end of file
+88d584f45850bff30373c42fcf535b16fa3e0d2b
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/remoting_internal_dynamic_framework.iossimulator.zip.sha1 b/ios/google_internal/frameworks/remoting_internal_dynamic_framework.iossimulator.zip.sha1
index fd9ab849..acc16da4 100644
--- a/ios/google_internal/frameworks/remoting_internal_dynamic_framework.iossimulator.zip.sha1
+++ b/ios/google_internal/frameworks/remoting_internal_dynamic_framework.iossimulator.zip.sha1
@@ -1 +1 @@
-70a75b6c6838276aaa9389011e993dbdd529d395
\ No newline at end of file
+e9031bf7aa7a5373603cf70d10210438170a8762
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/web_view_shell_internal_dynamic_framework.ios.zip.sha1 b/ios/google_internal/frameworks/web_view_shell_internal_dynamic_framework.ios.zip.sha1
index 4f434c2..1807c13 100644
--- a/ios/google_internal/frameworks/web_view_shell_internal_dynamic_framework.ios.zip.sha1
+++ b/ios/google_internal/frameworks/web_view_shell_internal_dynamic_framework.ios.zip.sha1
@@ -1 +1 @@
-fba9f9d916b743e8eb92e246df1cce16d9043be4
\ No newline at end of file
+36ae6aae0d0bc6386fb3b47a6d6e8c70e6472a53
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/web_view_shell_internal_dynamic_framework.iossimulator.zip.sha1 b/ios/google_internal/frameworks/web_view_shell_internal_dynamic_framework.iossimulator.zip.sha1
index ac12d46..5bbb39b 100644
--- a/ios/google_internal/frameworks/web_view_shell_internal_dynamic_framework.iossimulator.zip.sha1
+++ b/ios/google_internal/frameworks/web_view_shell_internal_dynamic_framework.iossimulator.zip.sha1
@@ -1 +1 @@
-3add8f4602123f5fe712a54f4a25f536d3355d59
\ No newline at end of file
+a7d84ea2060db0466675ee557ad414415cc1a70b
\ No newline at end of file
diff --git a/ios/web_view/BUILD.gn b/ios/web_view/BUILD.gn
index a20c0996..0195c92 100644
--- a/ios/web_view/BUILD.gn
+++ b/ios/web_view/BUILD.gn
@@ -189,6 +189,10 @@
   "internal/passwords/cwv_weak_check_utils_internal.h",
   "internal/passwords/web_view_account_password_store_factory.h",
   "internal/passwords/web_view_account_password_store_factory.mm",
+  "internal/passwords/web_view_affiliation_service_factory.h",
+  "internal/passwords/web_view_affiliation_service_factory.mm",
+  "internal/passwords/web_view_affiliations_prefetcher_factory.h",
+  "internal/passwords/web_view_affiliations_prefetcher_factory.mm",
   "internal/passwords/web_view_bulk_leak_check_service_factory.h",
   "internal/passwords/web_view_bulk_leak_check_service_factory.mm",
   "internal/passwords/web_view_password_change_success_tracker_factory.h",
diff --git a/ios/web_view/internal/passwords/web_view_account_password_store_factory.mm b/ios/web_view/internal/passwords/web_view_account_password_store_factory.mm
index 7e30aa3b..46cf82b1 100644
--- a/ios/web_view/internal/passwords/web_view_account_password_store_factory.mm
+++ b/ios/web_view/internal/passwords/web_view_account_password_store_factory.mm
@@ -4,20 +4,23 @@
 
 #import "ios/web_view/internal/passwords/web_view_account_password_store_factory.h"
 
-#include <memory>
-#include <utility>
+#import <memory>
+#import <utility>
 
-#include "base/functional/bind.h"
-#include "base/functional/callback_helpers.h"
-#include "base/no_destructor.h"
-#include "components/keyed_service/ios/browser_state_dependency_manager.h"
-#include "components/password_manager/core/browser/login_database.h"
-#include "components/password_manager/core/browser/password_manager_constants.h"
-#include "components/password_manager/core/browser/password_manager_util.h"
-#include "components/password_manager/core/browser/password_store_built_in_backend.h"
-#include "components/password_manager/core/browser/password_store_factory_util.h"
-#include "components/password_manager/core/common/password_manager_features.h"
-#include "components/prefs/pref_service.h"
+#import "base/functional/bind.h"
+#import "base/functional/callback_helpers.h"
+#import "base/no_destructor.h"
+#import "components/keyed_service/ios/browser_state_dependency_manager.h"
+#import "components/password_manager/core/browser/affiliation/affiliations_prefetcher.h"
+#import "components/password_manager/core/browser/login_database.h"
+#import "components/password_manager/core/browser/password_manager_constants.h"
+#import "components/password_manager/core/browser/password_manager_util.h"
+#import "components/password_manager/core/browser/password_store_built_in_backend.h"
+#import "components/password_manager/core/browser/password_store_factory_util.h"
+#import "components/password_manager/core/common/password_manager_features.h"
+#import "components/prefs/pref_service.h"
+#import "ios/web_view/internal/passwords/web_view_affiliation_service_factory.h"
+#import "ios/web_view/internal/passwords/web_view_affiliations_prefetcher_factory.h"
 
 #if !defined(__has_feature) || !__has_feature(objc_arc)
 #error "This file requires ARC support."
@@ -57,7 +60,10 @@
 WebViewAccountPasswordStoreFactory::WebViewAccountPasswordStoreFactory()
     : RefcountedBrowserStateKeyedServiceFactory(
           "AccountPasswordStore",
-          BrowserStateDependencyManager::GetInstance()) {}
+          BrowserStateDependencyManager::GetInstance()) {
+  DependsOn(WebViewAffiliationServiceFactory::GetInstance());
+  DependsOn(WebViewAffiliationsPrefetcherFactory::GetInstance());
+}
 
 WebViewAccountPasswordStoreFactory::~WebViewAccountPasswordStoreFactory() {}
 
@@ -78,7 +84,18 @@
       new password_manager::PasswordStore(
           std::make_unique<password_manager::PasswordStoreBuiltInBackend>(
               std::move(login_db)));
-  ps->Init(browser_state->GetPrefs(), /*affiliated_match_helper=*/nullptr);
+
+  password_manager::AffiliationService* affiliation_service =
+      WebViewAffiliationServiceFactory::GetForBrowserState(context);
+  std::unique_ptr<password_manager::AffiliatedMatchHelper>
+      affiliated_match_helper =
+          std::make_unique<password_manager::AffiliatedMatchHelper>(
+              affiliation_service);
+
+  ps->Init(browser_state->GetPrefs(), std::move(affiliated_match_helper));
+
+  WebViewAffiliationsPrefetcherFactory::GetForBrowserState(context)
+      ->RegisterPasswordStore(ps.get());
 
   return ps;
 }
diff --git a/ios/web_view/internal/passwords/web_view_affiliation_service_factory.h b/ios/web_view/internal/passwords/web_view_affiliation_service_factory.h
new file mode 100644
index 0000000..83e47ba
--- /dev/null
+++ b/ios/web_view/internal/passwords/web_view_affiliation_service_factory.h
@@ -0,0 +1,35 @@
+// 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 IOS_WEB_VIEW_INTERNAL_PASSWORDS_WEB_VIEW_AFFILIATION_SERVICE_FACTORY_H_
+#define IOS_WEB_VIEW_INTERNAL_PASSWORDS_WEB_VIEW_AFFILIATION_SERVICE_FACTORY_H_
+
+#import "base/no_destructor.h"
+#import "components/keyed_service/ios/browser_state_keyed_service_factory.h"
+
+namespace password_manager {
+class AffiliationService;
+}
+
+namespace ios_web_view {
+
+class WebViewAffiliationServiceFactory
+    : public BrowserStateKeyedServiceFactory {
+ public:
+  static WebViewAffiliationServiceFactory* GetInstance();
+  static password_manager::AffiliationService* GetForBrowserState(
+      web::BrowserState* browser_state);
+
+ private:
+  friend class base::NoDestructor<WebViewAffiliationServiceFactory>;
+
+  WebViewAffiliationServiceFactory();
+  ~WebViewAffiliationServiceFactory() override;
+
+  std::unique_ptr<KeyedService> BuildServiceInstanceFor(
+      web::BrowserState* context) const override;
+};
+}  // namespace ios_web_view
+
+#endif  // IOS_WEB_VIEW_INTERNAL_PASSWORDS_WEB_VIEW_AFFILIATION_SERVICE_FACTORY_H_
diff --git a/ios/web_view/internal/passwords/web_view_affiliation_service_factory.mm b/ios/web_view/internal/passwords/web_view_affiliation_service_factory.mm
new file mode 100644
index 0000000..22e0227
--- /dev/null
+++ b/ios/web_view/internal/passwords/web_view_affiliation_service_factory.mm
@@ -0,0 +1,74 @@
+// 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 "ios/web_view/internal/passwords/web_view_affiliation_service_factory.h"
+
+#import <memory>
+#import <utility>
+
+#import "base/no_destructor.h"
+#import "base/task/sequenced_task_runner.h"
+#import "base/task/thread_pool.h"
+#import "components/keyed_service/ios/browser_state_dependency_manager.h"
+#import "components/keyed_service/ios/browser_state_keyed_service_factory.h"
+#import "components/password_manager/core/browser/affiliation/affiliation_service_impl.h"
+#import "components/password_manager/core/browser/bulk_leak_check_service.h"
+#import "components/password_manager/core/browser/bulk_leak_check_service_interface.h"
+#import "components/password_manager/core/browser/password_manager_constants.h"
+#import "ios/web_view/internal/app/application_context.h"
+#import "ios/web_view/internal/signin/web_view_identity_manager_factory.h"
+#import "ios/web_view/internal/web_view_browser_state.h"
+#import "mojo/public/cpp/bindings/pending_receiver.h"
+#import "services/network/public/cpp/shared_url_loader_factory.h"
+#import "services/network/public/mojom/proxy_resolving_socket.mojom.h"
+
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
+namespace ios_web_view {
+
+// static
+WebViewAffiliationServiceFactory*
+WebViewAffiliationServiceFactory::GetInstance() {
+  static base::NoDestructor<WebViewAffiliationServiceFactory> instance;
+  return instance.get();
+}
+
+// static
+password_manager::AffiliationService*
+WebViewAffiliationServiceFactory::GetForBrowserState(
+    web::BrowserState* browser_state) {
+  return static_cast<password_manager::AffiliationService*>(
+      GetInstance()->GetServiceForBrowserState(browser_state, true));
+}
+
+WebViewAffiliationServiceFactory::WebViewAffiliationServiceFactory()
+    : BrowserStateKeyedServiceFactory(
+          "AffiliationService",
+          BrowserStateDependencyManager::GetInstance()) {}
+
+WebViewAffiliationServiceFactory::~WebViewAffiliationServiceFactory() = default;
+
+std::unique_ptr<KeyedService>
+WebViewAffiliationServiceFactory::BuildServiceInstanceFor(
+    web::BrowserState* context) const {
+  scoped_refptr<base::SequencedTaskRunner> backend_task_runner =
+      base::ThreadPool::CreateSequencedTaskRunner(
+          {base::MayBlock(), base::TaskPriority::USER_VISIBLE});
+  auto affiliation_service =
+      std::make_unique<password_manager::AffiliationServiceImpl>(
+          context->GetSharedURLLoaderFactory(), backend_task_runner,
+          WebViewBrowserState::FromBrowserState(context)->GetPrefs());
+
+  base::FilePath database_path = context->GetStatePath().Append(
+      password_manager::kAffiliationDatabaseFileName);
+  affiliation_service->Init(
+      ApplicationContext::GetInstance()->GetNetworkConnectionTracker(),
+      database_path);
+
+  return affiliation_service;
+}
+
+}  // namespace ios_web_view
diff --git a/ios/web_view/internal/passwords/web_view_affiliations_prefetcher_factory.h b/ios/web_view/internal/passwords/web_view_affiliations_prefetcher_factory.h
new file mode 100644
index 0000000..26643f4
--- /dev/null
+++ b/ios/web_view/internal/passwords/web_view_affiliations_prefetcher_factory.h
@@ -0,0 +1,34 @@
+// 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 IOS_WEB_VIEW_INTERNAL_PASSWORDS_WEB_VIEW_AFFILIATIONS_PREFETCHER_FACTORY_H_
+#define IOS_WEB_VIEW_INTERNAL_PASSWORDS_WEB_VIEW_AFFILIATIONS_PREFETCHER_FACTORY_H_
+
+#import "base/no_destructor.h"
+#import "components/keyed_service/ios/browser_state_keyed_service_factory.h"
+#import "ios/web_view/internal/web_view_browser_state.h"
+
+namespace password_manager {
+class AffiliationsPrefetcher;
+}  // namespace password_manager
+
+// Creates instances of AffiliationsPrefetcher per BrowserState.
+class WebViewAffiliationsPrefetcherFactory
+    : public BrowserStateKeyedServiceFactory {
+ public:
+  static WebViewAffiliationsPrefetcherFactory* GetInstance();
+  static password_manager::AffiliationsPrefetcher* GetForBrowserState(
+      web::BrowserState* browser_state);
+
+ private:
+  friend class base::NoDestructor<WebViewAffiliationsPrefetcherFactory>;
+
+  WebViewAffiliationsPrefetcherFactory();
+  ~WebViewAffiliationsPrefetcherFactory() override;
+
+  std::unique_ptr<KeyedService> BuildServiceInstanceFor(
+      web::BrowserState* browser_state) const override;
+};
+
+#endif  // IOS_WEB_VIEW_INTERNAL_PASSWORDS_WEB_VIEW_AFFILIATIONS_PREFETCHER_FACTORY_H_
diff --git a/ios/web_view/internal/passwords/web_view_affiliations_prefetcher_factory.mm b/ios/web_view/internal/passwords/web_view_affiliations_prefetcher_factory.mm
new file mode 100644
index 0000000..617b5b1
--- /dev/null
+++ b/ios/web_view/internal/passwords/web_view_affiliations_prefetcher_factory.mm
@@ -0,0 +1,48 @@
+
+// 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 "ios/web_view/internal/passwords/web_view_affiliations_prefetcher_factory.h"
+#import "base/no_destructor.h"
+#import "components/keyed_service/ios/browser_state_dependency_manager.h"
+#import "components/password_manager/core/browser/affiliation/affiliations_prefetcher.h"
+#import "ios/web/public/browser_state.h"
+#import "ios/web_view/internal/passwords/web_view_affiliation_service_factory.h"
+
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
+WebViewAffiliationsPrefetcherFactory*
+WebViewAffiliationsPrefetcherFactory::GetInstance() {
+  static base::NoDestructor<WebViewAffiliationsPrefetcherFactory> instance;
+  return instance.get();
+}
+
+password_manager::AffiliationsPrefetcher*
+WebViewAffiliationsPrefetcherFactory::GetForBrowserState(
+    web::BrowserState* browser_state) {
+  return static_cast<password_manager::AffiliationsPrefetcher*>(
+      GetInstance()->GetServiceForBrowserState(browser_state, /*create=*/true));
+}
+
+WebViewAffiliationsPrefetcherFactory::WebViewAffiliationsPrefetcherFactory()
+    : BrowserStateKeyedServiceFactory(
+          "AffiliationsPrefetcher",
+          BrowserStateDependencyManager::GetInstance()) {
+  DependsOn(ios_web_view::WebViewAffiliationServiceFactory::GetInstance());
+}
+
+WebViewAffiliationsPrefetcherFactory::~WebViewAffiliationsPrefetcherFactory() =
+    default;
+
+std::unique_ptr<KeyedService>
+WebViewAffiliationsPrefetcherFactory::BuildServiceInstanceFor(
+    web::BrowserState* browser_state) const {
+  password_manager::AffiliationService* affiliation_service =
+      ios_web_view::WebViewAffiliationServiceFactory::GetForBrowserState(
+          browser_state);
+  return std::make_unique<password_manager::AffiliationsPrefetcher>(
+      affiliation_service);
+}
diff --git a/media/gpu/v4l2/legacy/v4l2_slice_video_decode_accelerator.cc b/media/gpu/v4l2/legacy/v4l2_slice_video_decode_accelerator.cc
index 36fc4ef..8b054db 100644
--- a/media/gpu/v4l2/legacy/v4l2_slice_video_decode_accelerator.cc
+++ b/media/gpu/v4l2/legacy/v4l2_slice_video_decode_accelerator.cc
@@ -544,8 +544,8 @@
     auto fourcc = Fourcc::FromV4L2PixFmt(fmtdesc.pixelformat);
     if (fourcc && device_->CanCreateEGLImageFrom(*fourcc)) {
       output_format_fourcc_ = fourcc;
-      output_planes_count_ = V4L2Device::GetNumPlanesOfV4L2PixFmt(
-        output_format_fourcc_->ToV4L2PixFmt());
+      output_planes_count_ =
+          GetNumPlanesOfV4L2PixFmt(output_format_fourcc_->ToV4L2PixFmt());
       break;
     }
     ++fmtdesc.index;
@@ -569,8 +569,8 @@
       VLOGF(1) << "Can't find a usable input format from image processor";
       return false;
     }
-    output_planes_count_ = V4L2Device::GetNumPlanesOfV4L2PixFmt(
-        output_format_fourcc_->ToV4L2PixFmt());
+    output_planes_count_ =
+        GetNumPlanesOfV4L2PixFmt(output_format_fourcc_->ToV4L2PixFmt());
 
     gl_image_format_fourcc_ = v4l2_vda_helpers::FindImageProcessorOutputFormat(
         image_processor_device_.get());
@@ -578,8 +578,8 @@
       VLOGF(1) << "Can't find a usable output format from image processor";
       return false;
     }
-    gl_image_planes_count_ = V4L2Device::GetNumPlanesOfV4L2PixFmt(
-        gl_image_format_fourcc_->ToV4L2PixFmt());
+    gl_image_planes_count_ =
+        GetNumPlanesOfV4L2PixFmt(gl_image_format_fourcc_->ToV4L2PixFmt());
   } else {
     gl_image_format_fourcc_ = output_format_fourcc_;
     gl_image_planes_count_ = output_planes_count_;
@@ -590,8 +590,8 @@
   memset(&format, 0, sizeof(format));
   format.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
   format.fmt.pix_mp.pixelformat = output_format_fourcc_->ToV4L2PixFmt();
-  format.fmt.pix_mp.num_planes = V4L2Device::GetNumPlanesOfV4L2PixFmt(
-      output_format_fourcc_->ToV4L2PixFmt());
+  format.fmt.pix_mp.num_planes =
+      GetNumPlanesOfV4L2PixFmt(output_format_fourcc_->ToV4L2PixFmt());
   IOCTL_OR_ERROR_RETURN_FALSE(VIDIOC_S_FMT, &format);
   DCHECK_EQ(format.fmt.pix_mp.pixelformat,
             output_format_fourcc_->ToV4L2PixFmt());
diff --git a/media/gpu/v4l2/v4l2_device.cc b/media/gpu/v4l2/v4l2_device.cc
index 60e7240..e7b8adc 100644
--- a/media/gpu/v4l2/v4l2_device.cc
+++ b/media/gpu/v4l2/v4l2_device.cc
@@ -484,15 +484,6 @@
 }
 
 // static
-size_t V4L2Device::GetNumPlanesOfV4L2PixFmt(uint32_t pix_fmt) {
-  absl::optional<Fourcc> fourcc = Fourcc::FromV4L2PixFmt(pix_fmt);
-  if (fourcc && fourcc->IsMultiPlanar()) {
-    return VideoFrame::NumPlanes(fourcc->ToVideoPixelFormat());
-  }
-  return 1u;
-}
-
-// static
 bool V4L2Device::UseLibV4L2() {
   static const bool use_libv4l2 = LibV4L2Exists();
   return use_libv4l2;
diff --git a/media/gpu/v4l2/v4l2_device.h b/media/gpu/v4l2/v4l2_device.h
index b029645..d6519779 100644
--- a/media/gpu/v4l2/v4l2_device.h
+++ b/media/gpu/v4l2/v4l2_device.h
@@ -123,9 +123,6 @@
   static absl::optional<VideoFrameLayout> V4L2FormatToVideoFrameLayout(
       const struct v4l2_format& format);
 
-  // Returns number of planes of |pix_fmt|.
-  static size_t GetNumPlanesOfV4L2PixFmt(uint32_t pix_fmt);
-
   enum class Type {
     kDecoder,
     kEncoder,
diff --git a/media/gpu/v4l2/v4l2_device_unittest.cc b/media/gpu/v4l2/v4l2_device_unittest.cc
index 2fd2b3c2..c5880f58 100644
--- a/media/gpu/v4l2/v4l2_device_unittest.cc
+++ b/media/gpu/v4l2/v4l2_device_unittest.cc
@@ -9,6 +9,7 @@
 #include <vector>
 
 #include "media/base/color_plane_layout.h"
+#include "media/gpu/v4l2/v4l2_utils.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "ui/gfx/native_pixmap_handle.h"
 
@@ -188,17 +189,17 @@
 
 // Test GetNumPlanesOfV4L2PixFmt.
 TEST(V4L2DeviceTest, GetNumPlanesOfV4L2PixFmt) {
-  EXPECT_EQ(1u, V4L2Device::GetNumPlanesOfV4L2PixFmt(V4L2_PIX_FMT_NV12));
-  EXPECT_EQ(1u, V4L2Device::GetNumPlanesOfV4L2PixFmt(V4L2_PIX_FMT_YUV420));
-  EXPECT_EQ(1u, V4L2Device::GetNumPlanesOfV4L2PixFmt(V4L2_PIX_FMT_YVU420));
-  EXPECT_EQ(1u, V4L2Device::GetNumPlanesOfV4L2PixFmt(V4L2_PIX_FMT_RGB32));
+  EXPECT_EQ(1u, GetNumPlanesOfV4L2PixFmt(V4L2_PIX_FMT_NV12));
+  EXPECT_EQ(1u, GetNumPlanesOfV4L2PixFmt(V4L2_PIX_FMT_YUV420));
+  EXPECT_EQ(1u, GetNumPlanesOfV4L2PixFmt(V4L2_PIX_FMT_YVU420));
+  EXPECT_EQ(1u, GetNumPlanesOfV4L2PixFmt(V4L2_PIX_FMT_RGB32));
 
-  EXPECT_EQ(2u, V4L2Device::GetNumPlanesOfV4L2PixFmt(V4L2_PIX_FMT_NV12M));
-  EXPECT_EQ(2u, V4L2Device::GetNumPlanesOfV4L2PixFmt(V4L2_PIX_FMT_MT21C));
+  EXPECT_EQ(2u, GetNumPlanesOfV4L2PixFmt(V4L2_PIX_FMT_NV12M));
+  EXPECT_EQ(2u, GetNumPlanesOfV4L2PixFmt(V4L2_PIX_FMT_MT21C));
 
-  EXPECT_EQ(3u, V4L2Device::GetNumPlanesOfV4L2PixFmt(V4L2_PIX_FMT_YUV420M));
-  EXPECT_EQ(3u, V4L2Device::GetNumPlanesOfV4L2PixFmt(V4L2_PIX_FMT_YVU420M));
-  EXPECT_EQ(3u, V4L2Device::GetNumPlanesOfV4L2PixFmt(V4L2_PIX_FMT_YUV422M));
+  EXPECT_EQ(3u, GetNumPlanesOfV4L2PixFmt(V4L2_PIX_FMT_YUV420M));
+  EXPECT_EQ(3u, GetNumPlanesOfV4L2PixFmt(V4L2_PIX_FMT_YVU420M));
+  EXPECT_EQ(3u, GetNumPlanesOfV4L2PixFmt(V4L2_PIX_FMT_YUV422M));
 }
 
 }  // namespace media
diff --git a/media/gpu/v4l2/v4l2_image_processor_backend.cc b/media/gpu/v4l2/v4l2_image_processor_backend.cc
index 6ce2f40..a955b0a 100644
--- a/media/gpu/v4l2/v4l2_image_processor_backend.cc
+++ b/media/gpu/v4l2/v4l2_image_processor_backend.cc
@@ -54,8 +54,7 @@
     const gfx::GpuMemoryBufferHandle& gmb_handle,
     V4L2WritableBufferRef* buffer) {
   DCHECK_EQ(buffer->Memory(), V4L2_MEMORY_DMABUF);
-  const size_t num_planes =
-      V4L2Device::GetNumPlanesOfV4L2PixFmt(fourcc.ToV4L2PixFmt());
+  const size_t num_planes = GetNumPlanesOfV4L2PixFmt(fourcc.ToV4L2PixFmt());
   const std::vector<gfx::NativePixmapPlane>& planes =
       gmb_handle.native_pixmap_handle.planes;
 
@@ -942,8 +941,8 @@
 
   switch (input_memory_type_) {
     case V4L2_MEMORY_USERPTR: {
-      const size_t num_planes = V4L2Device::GetNumPlanesOfV4L2PixFmt(
-          input_config_.fourcc.ToV4L2PixFmt());
+      const size_t num_planes =
+          GetNumPlanesOfV4L2PixFmt(input_config_.fourcc.ToV4L2PixFmt());
       std::vector<void*> user_ptrs(num_planes);
       for (size_t i = 0; i < num_planes; ++i) {
         int bytes_used =
diff --git a/media/gpu/v4l2/v4l2_queue.cc b/media/gpu/v4l2/v4l2_queue.cc
index 5645fa6..b5abbce 100644
--- a/media/gpu/v4l2/v4l2_queue.cc
+++ b/media/gpu/v4l2/v4l2_queue.cc
@@ -40,7 +40,7 @@
   format.fmt.pix_mp.pixelformat = fourcc;
   format.fmt.pix_mp.width = size.width();
   format.fmt.pix_mp.height = size.height();
-  format.fmt.pix_mp.num_planes = V4L2Device::GetNumPlanesOfV4L2PixFmt(fourcc);
+  format.fmt.pix_mp.num_planes = GetNumPlanesOfV4L2PixFmt(fourcc);
   format.fmt.pix_mp.plane_fmt[0].sizeimage = buffer_size;
 
   return format;
diff --git a/media/gpu/v4l2/v4l2_utils.cc b/media/gpu/v4l2/v4l2_utils.cc
index 9c784f17..fb7dc1b 100644
--- a/media/gpu/v4l2/v4l2_utils.cc
+++ b/media/gpu/v4l2/v4l2_utils.cc
@@ -17,8 +17,11 @@
 #include "base/ranges/algorithm.h"
 #include "build/build_config.h"
 #include "media/base/video_codecs.h"
+#include "media/base/video_frame.h"
 #include "media/base/video_types.h"
+#include "media/gpu/chromeos/fourcc.h"
 #include "media/gpu/macros.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
 #include "ui/gfx/geometry/size.h"
 
 // TODO(b/255770680): Remove this once V4L2 header is updated.
@@ -188,6 +191,14 @@
   return VIDEO_CODEC_PROFILE_UNKNOWN;
 }
 
+size_t GetNumPlanesOfV4L2PixFmt(uint32_t pix_fmt) {
+  absl::optional<Fourcc> fourcc = Fourcc::FromV4L2PixFmt(pix_fmt);
+  if (fourcc && fourcc->IsMultiPlanar()) {
+    return VideoFrame::NumPlanes(fourcc->ToVideoPixelFormat());
+  }
+  return 1u;
+}
+
 namespace {
 using v4l2_enum_type = decltype(V4L2_PIX_FMT_H264);
 // Correspondence from V4L2 codec described as a pixel format to a Control ID.
diff --git a/media/gpu/v4l2/v4l2_utils.h b/media/gpu/v4l2/v4l2_utils.h
index f6b56e4..2ed5fb3 100644
--- a/media/gpu/v4l2/v4l2_utils.h
+++ b/media/gpu/v4l2/v4l2_utils.h
@@ -43,6 +43,9 @@
 VideoCodecProfile V4L2ProfileToVideoCodecProfile(uint32_t v4l2_codec,
                                                  uint32_t v4l2_profile);
 
+// Returns number of planes of |pix_fmt|, or 1, if this is unknown.
+size_t GetNumPlanesOfV4L2PixFmt(uint32_t pix_fmt);
+
 // Enumerates the supported VideoCodecProfiles for a given device (accessed via
 // |ioctl_cb|) and for |codec_as_pix_fmt| (e.g. V4L2_PIX_FMT_VP9). Returns an
 // empty vector if |codec_as_pix_fmt| is not supported by Chrome, or the
diff --git a/media/gpu/v4l2/v4l2_video_encode_accelerator.cc b/media/gpu/v4l2/v4l2_video_encode_accelerator.cc
index b8486cf5..66b5863 100644
--- a/media/gpu/v4l2/v4l2_video_encode_accelerator.cc
+++ b/media/gpu/v4l2/v4l2_video_encode_accelerator.cc
@@ -1347,7 +1347,7 @@
   input_buf.SetTimeStamp(timestamp);
 
   DCHECK_EQ(device_input_layout_->format(), frame->format());
-  size_t num_planes = V4L2Device::GetNumPlanesOfV4L2PixFmt(
+  size_t num_planes = GetNumPlanesOfV4L2PixFmt(
       Fourcc::FromVideoPixelFormat(device_input_layout_->format(),
                                    !device_input_layout_->is_multi_planar())
           ->ToV4L2PixFmt());
diff --git a/sandbox/policy/mac/gpu.sb b/sandbox/policy/mac/gpu.sb
index 74187e3..692c261 100644
--- a/sandbox/policy/mac/gpu.sb
+++ b/sandbox/policy/mac/gpu.sb
@@ -132,6 +132,19 @@
   (subpath (param darwin-user-temp-dir))
 )
 
+; Metal shader compilation may be offloaded to a helper process that needs
+; access to the user cache directory in order to cache its work to disk.
+; crbug.com/1453813
+(let ((helper-bundle-cache-dir
+       (string-append (param darwin-user-cache-dir)
+                      "/" (param bundle-id) ".helper")))
+  (allow file-issue-extension
+    (require-all
+      (extension-class "com.apple.app-sandbox.read-write")
+      (require-any
+        (subpath (string-append helper-bundle-cache-dir "/com.apple.metalfe"))
+        (subpath (string-append helper-bundle-cache-dir "/com.apple.gpuarchiver"))))))
+
 (if (param-true? filter-syscalls-debug)
   (when (defined? 'syscall-unix)
     (deny syscall-unix (with send-signal SIGSYS))
diff --git a/skia/config/SkUserConfig.h b/skia/config/SkUserConfig.h
index a2dc017..33ec917 100644
--- a/skia/config/SkUserConfig.h
+++ b/skia/config/SkUserConfig.h
@@ -235,8 +235,6 @@
 
 #define SK_USE_LEGACY_DROPSHADOW_IMAGEFILTER
 
-#define SK_USE_LEGACY_DISPLACEMENT_MAP_IMAGEFILTER
-
 #define SK_USE_LEGACY_MORPHOLOGY_IMAGEFILTER
 
 // Use the original std::vector based serializer
diff --git a/testing/buildbot/chromium.chromiumos.json b/testing/buildbot/chromium.chromiumos.json
index 10cc6ca..bb9185f6 100644
--- a/testing/buildbot/chromium.chromiumos.json
+++ b/testing/buildbot/chromium.chromiumos.json
@@ -5669,9 +5669,9 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.interactive_ui_tests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v116.0.5831.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v116.0.5832.0/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 116.0.5831.0",
+        "description": "Run with ash-chrome version 116.0.5832.0",
         "isolate_profile_data": true,
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -5682,8 +5682,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v116.0.5831.0",
-              "revision": "version:116.0.5831.0"
+              "location": "lacros_version_skew_tests_v116.0.5832.0",
+              "revision": "version:116.0.5832.0"
             }
           ],
           "dimension_sets": [
@@ -5834,9 +5834,9 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v116.0.5831.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v116.0.5832.0/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 116.0.5831.0",
+        "description": "Run with ash-chrome version 116.0.5832.0",
         "isolate_profile_data": true,
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -5847,8 +5847,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v116.0.5831.0",
-              "revision": "version:116.0.5831.0"
+              "location": "lacros_version_skew_tests_v116.0.5832.0",
+              "revision": "version:116.0.5832.0"
             }
           ],
           "dimension_sets": [
@@ -5981,9 +5981,9 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v116.0.5831.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v116.0.5832.0/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 116.0.5831.0",
+        "description": "Run with ash-chrome version 116.0.5832.0",
         "isolate_profile_data": true,
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -5994,8 +5994,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v116.0.5831.0",
-              "revision": "version:116.0.5831.0"
+              "location": "lacros_version_skew_tests_v116.0.5832.0",
+              "revision": "version:116.0.5832.0"
             }
           ],
           "dimension_sets": [
diff --git a/testing/buildbot/chromium.coverage.json b/testing/buildbot/chromium.coverage.json
index 67dd49f..6fc4bf4 100644
--- a/testing/buildbot/chromium.coverage.json
+++ b/testing/buildbot/chromium.coverage.json
@@ -25493,9 +25493,9 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.interactive_ui_tests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v116.0.5831.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v116.0.5832.0/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 116.0.5831.0",
+        "description": "Run with ash-chrome version 116.0.5832.0",
         "isolate_profile_data": true,
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -25506,8 +25506,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v116.0.5831.0",
-              "revision": "version:116.0.5831.0"
+              "location": "lacros_version_skew_tests_v116.0.5832.0",
+              "revision": "version:116.0.5832.0"
             }
           ],
           "dimension_sets": [
@@ -25658,9 +25658,9 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v116.0.5831.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v116.0.5832.0/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 116.0.5831.0",
+        "description": "Run with ash-chrome version 116.0.5832.0",
         "isolate_profile_data": true,
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -25671,8 +25671,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v116.0.5831.0",
-              "revision": "version:116.0.5831.0"
+              "location": "lacros_version_skew_tests_v116.0.5832.0",
+              "revision": "version:116.0.5832.0"
             }
           ],
           "dimension_sets": [
@@ -25805,9 +25805,9 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v116.0.5831.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v116.0.5832.0/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 116.0.5831.0",
+        "description": "Run with ash-chrome version 116.0.5832.0",
         "isolate_profile_data": true,
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -25818,8 +25818,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v116.0.5831.0",
-              "revision": "version:116.0.5831.0"
+              "location": "lacros_version_skew_tests_v116.0.5832.0",
+              "revision": "version:116.0.5832.0"
             }
           ],
           "dimension_sets": [
diff --git a/testing/buildbot/chromium.fyi.json b/testing/buildbot/chromium.fyi.json
index ef3299e..1fbed80c 100644
--- a/testing/buildbot/chromium.fyi.json
+++ b/testing/buildbot/chromium.fyi.json
@@ -38489,9 +38489,9 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.interactive_ui_tests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v116.0.5831.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v116.0.5832.0/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 116.0.5831.0",
+        "description": "Run with ash-chrome version 116.0.5832.0",
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
         },
@@ -38501,8 +38501,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v116.0.5831.0",
-              "revision": "version:116.0.5831.0"
+              "location": "lacros_version_skew_tests_v116.0.5832.0",
+              "revision": "version:116.0.5832.0"
             }
           ],
           "dimension_sets": [
@@ -38654,9 +38654,9 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v116.0.5831.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v116.0.5832.0/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 116.0.5831.0",
+        "description": "Run with ash-chrome version 116.0.5832.0",
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
         },
@@ -38666,8 +38666,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v116.0.5831.0",
-              "revision": "version:116.0.5831.0"
+              "location": "lacros_version_skew_tests_v116.0.5832.0",
+              "revision": "version:116.0.5832.0"
             }
           ],
           "dimension_sets": [
@@ -38801,9 +38801,9 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v116.0.5831.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v116.0.5832.0/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 116.0.5831.0",
+        "description": "Run with ash-chrome version 116.0.5832.0",
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
         },
@@ -38813,8 +38813,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v116.0.5831.0",
-              "revision": "version:116.0.5831.0"
+              "location": "lacros_version_skew_tests_v116.0.5832.0",
+              "revision": "version:116.0.5832.0"
             }
           ],
           "dimension_sets": [
@@ -40278,9 +40278,9 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.interactive_ui_tests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v116.0.5831.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v116.0.5832.0/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 116.0.5831.0",
+        "description": "Run with ash-chrome version 116.0.5832.0",
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
         },
@@ -40290,8 +40290,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v116.0.5831.0",
-              "revision": "version:116.0.5831.0"
+              "location": "lacros_version_skew_tests_v116.0.5832.0",
+              "revision": "version:116.0.5832.0"
             }
           ],
           "dimension_sets": [
@@ -40443,9 +40443,9 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v116.0.5831.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v116.0.5832.0/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 116.0.5831.0",
+        "description": "Run with ash-chrome version 116.0.5832.0",
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
         },
@@ -40455,8 +40455,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v116.0.5831.0",
-              "revision": "version:116.0.5831.0"
+              "location": "lacros_version_skew_tests_v116.0.5832.0",
+              "revision": "version:116.0.5832.0"
             }
           ],
           "dimension_sets": [
@@ -40590,9 +40590,9 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v116.0.5831.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v116.0.5832.0/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 116.0.5831.0",
+        "description": "Run with ash-chrome version 116.0.5832.0",
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
         },
@@ -40602,8 +40602,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v116.0.5831.0",
-              "revision": "version:116.0.5831.0"
+              "location": "lacros_version_skew_tests_v116.0.5832.0",
+              "revision": "version:116.0.5832.0"
             }
           ],
           "dimension_sets": [
@@ -41338,9 +41338,9 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.interactive_ui_tests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v116.0.5831.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v116.0.5832.0/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 116.0.5831.0",
+        "description": "Run with ash-chrome version 116.0.5832.0",
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
         },
@@ -41350,8 +41350,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v116.0.5831.0",
-              "revision": "version:116.0.5831.0"
+              "location": "lacros_version_skew_tests_v116.0.5832.0",
+              "revision": "version:116.0.5832.0"
             }
           ],
           "dimension_sets": [
diff --git a/testing/buildbot/chromium.mac.json b/testing/buildbot/chromium.mac.json
index 9b0f7dd..dba2208 100644
--- a/testing/buildbot/chromium.mac.json
+++ b/testing/buildbot/chromium.mac.json
@@ -26302,7 +26302,7 @@
         "test_id_prefix": "ninja://components:components_unittests/"
       },
       {
-        "ci_only": false,
+        "ci_only": true,
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
         },
@@ -26315,7 +26315,7 @@
             }
           ],
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com",
-          "shards": 12
+          "shards": 8
         },
         "test": "content_browsertests",
         "test_id_prefix": "ninja://content/test:content_browsertests/"
@@ -26681,7 +26681,7 @@
         "test_id_prefix": "ninja://headless:headless_unittests/"
       },
       {
-        "ci_only": true,
+        "ci_only": false,
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
         },
@@ -26694,7 +26694,7 @@
             }
           ],
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com",
-          "shards": 3
+          "shards": 6
         },
         "test": "interactive_ui_tests",
         "test_id_prefix": "ninja://chrome/test:interactive_ui_tests/"
diff --git a/testing/buildbot/chromium.memory.json b/testing/buildbot/chromium.memory.json
index a4f54d8..22aa17c 100644
--- a/testing/buildbot/chromium.memory.json
+++ b/testing/buildbot/chromium.memory.json
@@ -18080,12 +18080,12 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.interactive_ui_tests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v116.0.5831.0/test_ash_chrome",
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v116.0.5832.0/test_ash_chrome",
           "--test-launcher-print-test-stdio=always",
           "--combine-ash-logs-on-bots",
           "--asan-symbolize-output"
         ],
-        "description": "Run with ash-chrome version 116.0.5831.0",
+        "description": "Run with ash-chrome version 116.0.5832.0",
         "isolate_profile_data": true,
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -18096,8 +18096,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v116.0.5831.0",
-              "revision": "version:116.0.5831.0"
+              "location": "lacros_version_skew_tests_v116.0.5832.0",
+              "revision": "version:116.0.5832.0"
             }
           ],
           "dimension_sets": [
@@ -18265,12 +18265,12 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v116.0.5831.0/test_ash_chrome",
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v116.0.5832.0/test_ash_chrome",
           "--test-launcher-print-test-stdio=always",
           "--combine-ash-logs-on-bots",
           "--asan-symbolize-output"
         ],
-        "description": "Run with ash-chrome version 116.0.5831.0",
+        "description": "Run with ash-chrome version 116.0.5832.0",
         "isolate_profile_data": true,
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -18281,8 +18281,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v116.0.5831.0",
-              "revision": "version:116.0.5831.0"
+              "location": "lacros_version_skew_tests_v116.0.5832.0",
+              "revision": "version:116.0.5832.0"
             }
           ],
           "dimension_sets": [
@@ -18427,12 +18427,12 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v116.0.5831.0/test_ash_chrome",
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v116.0.5832.0/test_ash_chrome",
           "--test-launcher-print-test-stdio=always",
           "--combine-ash-logs-on-bots",
           "--asan-symbolize-output"
         ],
-        "description": "Run with ash-chrome version 116.0.5831.0",
+        "description": "Run with ash-chrome version 116.0.5832.0",
         "isolate_profile_data": true,
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -18443,8 +18443,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v116.0.5831.0",
-              "revision": "version:116.0.5831.0"
+              "location": "lacros_version_skew_tests_v116.0.5832.0",
+              "revision": "version:116.0.5832.0"
             }
           ],
           "dimension_sets": [
diff --git a/testing/buildbot/filters/android.emulator_11.chrome_public_test_apk.filter b/testing/buildbot/filters/android.emulator_11.chrome_public_test_apk.filter
index e1691cd0..3f2b9e1 100644
--- a/testing/buildbot/filters/android.emulator_11.chrome_public_test_apk.filter
+++ b/testing/buildbot/filters/android.emulator_11.chrome_public_test_apk.filter
@@ -12,9 +12,6 @@
 -org.chromium.chrome.browser.download.DownloadTest.testHttpPostDownload__UseDownloadOfflineContentProviderEnabled
 -org.chromium.chrome.browser.download.DownloadTest.testUrlEscaping__UseDownloadOfflineContentProviderDisabled
 -org.chromium.chrome.browser.download.DownloadTest.testUrlEscaping__UseDownloadOfflineContentProviderEnabled
--org.chromium.chrome.browser.contextmenu.RevampedContextMenuTest.testSaveDataUrl
--org.chromium.chrome.browser.contextmenu.RevampedContextMenuTest.testSaveImage
--org.chromium.chrome.browser.contextmenu.RevampedContextMenuTest.testSaveVideo
 
 # crbug.com/1036571
 -org.chromium.chrome.browser.shape_detection.ShapeDetectionTest.testTextDetection
diff --git a/testing/buildbot/filters/fuchsia.browser_tests.filter b/testing/buildbot/filters/fuchsia.browser_tests.filter
index a1ef938a..91db99e 100644
--- a/testing/buildbot/filters/fuchsia.browser_tests.filter
+++ b/testing/buildbot/filters/fuchsia.browser_tests.filter
@@ -10,15 +10,6 @@
 -AllForms/FormStructureBrowserTest.DataDrivenHeuristics/103
 -AllForms/FormStructureBrowserTest.DataDrivenHeuristics/153
 
-# TODO(crbug.com/1326648):
-# ../../chrome/browser/ui/views/frame/browser_view_browsertest.cc:343: Failure
-# Expected equality of these values:
-#   browser_view()->GetAccessibleWindowTitle()
-#     Which is: u"about:blank - Chromium"
-#   delegate_observer->GetTitle()
-#     Which is: u"Dialog Title"
--BrowserViewTest.GetAccessibleTabModalDialogTitle
-
 # TODO(crbug.com/1325504): [html_media_element.cc(4705)] SetError: {code=4,
 # message="MEDIA_ELEMENT_ERROR: Media load rejected by URL safety check"}
 -All/DeclarativeNetRequestBrowserTest.PacRequestsBypassRules/0
diff --git a/testing/buildbot/filters/linux-lacros.browser_tests.filter b/testing/buildbot/filters/linux-lacros.browser_tests.filter
index a3159307..f0a18ee 100644
--- a/testing/buildbot/filters/linux-lacros.browser_tests.filter
+++ b/testing/buildbot/filters/linux-lacros.browser_tests.filter
@@ -1,12 +1,10 @@
 # TODO(crbug.com/1111979) Enable all tests on lacros.
 -All/WebRtcScreenCaptureBrowserTestWithPicker.ScreenCaptureVideo/*
 -All/WebRtcScreenCaptureBrowserTestWithPicker.ScreenCaptureVideoAndAudio/*
--BrowserViewTest.GetAccessibleTabModalDialogTitle
 -BrowsingDataRemoverBrowserTest.StorageRemovedFromDisk
 -DeclarativeContentApiTest.RulesPersistence
 -ExternalProtocolDialogBrowserTest.TestFocus
 -FolderUploadConfirmationViewTest.InitiallyFocusesCancel
--KeepAliveDevToolsTest.KeepsAliveUntilBrowserClose
 -ProfileListDesktopBrowserTest.SwitchToProfile
 # crbug.com/1121486
 # Following tests were flaky. We disable them first until we have time to investigate.
diff --git a/testing/buildbot/test_suite_exceptions.pyl b/testing/buildbot/test_suite_exceptions.pyl
index 57f4ace..3c06ad4 100644
--- a/testing/buildbot/test_suite_exceptions.pyl
+++ b/testing/buildbot/test_suite_exceptions.pyl
@@ -2138,12 +2138,6 @@
           ],
         },
       },
-      'mac13-arm64-rel-tests': {
-        'ci_only': False,
-        'swarming': {
-          'shards': 12,
-        },
-      },
       'win-asan': {
          # Tests shows tests run faster with fewer retries by using fewer jobs crbug.com/1411912
         'args': [
@@ -3011,6 +3005,12 @@
           'shards': 6,
         },
       },
+      'mac13-arm64-rel-tests': {
+        'ci_only': False,
+        'swarming': {
+          'shards': 6,
+        },
+      },
       'win-arm64-rel': {
         # Surface Pros have touch turned on by default, which makes some interactive-ui-tests fail.
         'args': [
diff --git a/testing/buildbot/variants.pyl b/testing/buildbot/variants.pyl
index be5f820..1775910 100644
--- a/testing/buildbot/variants.pyl
+++ b/testing/buildbot/variants.pyl
@@ -22,16 +22,16 @@
   },
   'LACROS_VERSION_SKEW_CANARY': {
     'args': [
-      '--ash-chrome-path-override=../../lacros_version_skew_tests_v116.0.5831.0/test_ash_chrome',
+      '--ash-chrome-path-override=../../lacros_version_skew_tests_v116.0.5832.0/test_ash_chrome',
     ],
-    'description': 'Run with ash-chrome version 116.0.5831.0',
+    'description': 'Run with ash-chrome version 116.0.5832.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_v116.0.5831.0',
-          'revision': 'version:116.0.5831.0',
+          'location': 'lacros_version_skew_tests_v116.0.5832.0',
+          'revision': 'version:116.0.5832.0',
         },
       ],
     },
diff --git a/third_party/blink/common/features.cc b/third_party/blink/common/features.cc
index 7fecceed..9c5146e 100644
--- a/third_party/blink/common/features.cc
+++ b/third_party/blink/common/features.cc
@@ -1166,13 +1166,6 @@
     kBrowsingTopicsMaxNumberOfApiUsageContextDomainsToStorePerPageLoad{
         &kBrowsingTopicsParameters,
         "max_number_of_api_usage_context_domains_to_store_per_page_load", 30};
-// Encodes the configuration parameters above. A version number can be used for
-// multiple configurations as long as they are compatible (from both Chrome's
-// and users/websites' perspective). For a configuration that's incompatible
-// with previous ones, a new dedicated version number should be used.
-const base::FeatureParam<int> kBrowsingTopicsConfigVersion{
-    &kBrowsingTopicsParameters, "config_version",
-    kBrowsingTopicsConfigVersionDefault};
 // The taxonomy version. This only affects the topics classification that occurs
 // during this browser session, and doesn't affect the pre-existing epochs.
 const base::FeatureParam<int> kBrowsingTopicsTaxonomyVersion{
diff --git a/third_party/blink/public/common/features.h b/third_party/blink/public/common/features.h
index 2ff2da5..6447e19d 100644
--- a/third_party/blink/public/common/features.h
+++ b/third_party/blink/public/common/features.h
@@ -20,8 +20,6 @@
 namespace blink {
 namespace features {
 
-constexpr int kBrowsingTopicsConfigVersionDefault = 1;
-
 constexpr int kBrowsingTopicsTaxonomyVersionDefault = 1;
 
 BLINK_COMMON_EXPORT
@@ -507,8 +505,6 @@
 BLINK_COMMON_EXPORT extern const base::FeatureParam<int>
     kBrowsingTopicsMaxNumberOfApiUsageContextDomainsToStorePerPageLoad;
 BLINK_COMMON_EXPORT extern const base::FeatureParam<int>
-    kBrowsingTopicsConfigVersion;
-BLINK_COMMON_EXPORT extern const base::FeatureParam<int>
     kBrowsingTopicsTaxonomyVersion;
 BLINK_COMMON_EXPORT extern const base::FeatureParam<std::string>
     kBrowsingTopicsDisabledTopicsList;
diff --git a/third_party/blink/renderer/core/layout/ng/inline/ng_inline_items_builder.cc b/third_party/blink/renderer/core/layout/ng/inline/ng_inline_items_builder.cc
index 60a6d14..c8ae57e 100644
--- a/third_party/blink/renderer/core/layout/ng/inline/ng_inline_items_builder.cc
+++ b/third_party/blink/renderer/core/layout/ng/inline/ng_inline_items_builder.cc
@@ -1099,6 +1099,7 @@
     LayoutObject* layout_object) {
   AppendOpaque(NGInlineItem::kFloating, kObjectReplacementCharacter,
                layout_object);
+  has_floats_ = true;
   // Floats/exclusions require computing line heights, which is currently
   // skipped during the bisect. See `NGParagraphLineBreaker`.
   is_bisect_line_break_disabled_ = true;
@@ -1429,6 +1430,7 @@
   // |SegmentText()| will analyze the text and reset |is_bidi_enabled_| if it
   // doesn't contain any RTL characters.
   data->is_bidi_enabled_ = MayBeBidiEnabled();
+  data->has_floats_ = has_floats_;
   data->has_initial_letter_box_ = has_initial_letter_box_;
   data->has_ruby_ = has_ruby_;
   data->is_block_level_ = IsBlockLevel();
diff --git a/third_party/blink/renderer/core/layout/ng/inline/ng_inline_items_builder.h b/third_party/blink/renderer/core/layout/ng/inline/ng_inline_items_builder.h
index 09b4c52..ed32d5d 100644
--- a/third_party/blink/renderer/core/layout/ng/inline/ng_inline_items_builder.h
+++ b/third_party/blink/renderer/core/layout/ng/inline/ng_inline_items_builder.h
@@ -195,6 +195,7 @@
 
   const bool is_text_combine_;
   bool has_bidi_controls_ = false;
+  bool has_floats_ = false;
   bool has_initial_letter_box_ = false;
   bool has_ruby_ = false;
   bool is_block_level_ = true;
diff --git a/third_party/blink/renderer/core/layout/ng/inline/ng_inline_node.h b/third_party/blink/renderer/core/layout/ng/inline/ng_inline_node.h
index f840a03..4cb509a 100644
--- a/third_party/blink/renderer/core/layout/ng/inline/ng_inline_node.h
+++ b/third_party/blink/renderer/core/layout/ng/inline/ng_inline_node.h
@@ -96,8 +96,8 @@
   bool IsBidiEnabled() const { return Data().is_bidi_enabled_; }
   TextDirection BaseDirection() const { return Data().BaseDirection(); }
 
+  bool HasFloats() const { return Data().HasFloats(); }
   bool HasInitialLetterBox() const { return Data().has_initial_letter_box_; }
-
   bool HasRuby() const { return Data().has_ruby_; }
 
   bool IsBlockLevel() { return EnsureData().is_block_level_; }
diff --git a/third_party/blink/renderer/core/layout/ng/inline/ng_inline_node_data.h b/third_party/blink/renderer/core/layout/ng/inline/ng_inline_node_data.h
index c3cba8eb..153f30e 100644
--- a/third_party/blink/renderer/core/layout/ng/inline/ng_inline_node_data.h
+++ b/third_party/blink/renderer/core/layout/ng/inline/ng_inline_node_data.h
@@ -23,6 +23,7 @@
     return static_cast<TextDirection>(base_direction_);
   }
 
+  bool HasFloats() const { return has_floats_; }
   bool HasInitialLetterBox() const { return has_initial_letter_box_; }
   bool HasRuby() const { return has_ruby_; }
 
@@ -72,6 +73,8 @@
   unsigned is_bidi_enabled_ : 1;
   unsigned base_direction_ : 1;  // TextDirection
 
+  unsigned has_floats_ : 1;
+
   // True if this node contains initial letter box. This value is used for
   // clearing. To control whether subsequent blocks overlap with initial
   // letter[1].
diff --git a/third_party/blink/renderer/core/layout/ng/inline/ng_line_widths.cc b/third_party/blink/renderer/core/layout/ng/inline/ng_line_widths.cc
index 514ae9ef..86bb40eb 100644
--- a/third_party/blink/renderer/core/layout/ng/inline/ng_line_widths.cc
+++ b/third_party/blink/renderer/core/layout/ng/inline/ng_line_widths.cc
@@ -15,7 +15,7 @@
   // Set the default width if no exclusions.
   DCHECK_GE(opportunities.size(), 1u);
   const NGLayoutOpportunity& first_opportunity = opportunities.front();
-  if (opportunities.size() == 1) {
+  if (opportunities.size() == 1 && !node.HasFloats()) {
     DCHECK(!first_opportunity.HasShapeExclusions());
     default_width_ = first_opportunity.rect.InlineSize();
     DCHECK(!num_excluded_lines_);
@@ -42,9 +42,13 @@
   // `::first-line` is not supported.
   DCHECK_EQ(&items_data, &node.ItemsData(true));
   const HeapVector<NGInlineItem>& items = items_data.items;
+  bool is_empty_so_far = true;
   for (const NGInlineItem& item : items) {
     switch (item.Type()) {
       case NGInlineItem::kText: {
+        if (UNLIKELY(!item.Length())) {
+          break;
+        }
         const ShapeResult* shape_result = item.TextShapeResult();
         DCHECK(shape_result);
         if (shape_result->PrimaryFont() != primary_font ||
@@ -72,13 +76,32 @@
       case NGInlineItem::kOpenTag: {
         DCHECK(item.Style());
         const ComputedStyle& style = *item.Style();
-        if (style.VerticalAlign() != EVerticalAlign::kBaseline) {
+        if (UNLIKELY(style.VerticalAlign() != EVerticalAlign::kBaseline)) {
           return false;
         }
         break;
       }
-      default:
+      case NGInlineItem::kCloseTag:
+      case NGInlineItem::kControl:
+      case NGInlineItem::kOutOfFlowPositioned:
+      case NGInlineItem::kBidiControl:
+        // These items don't affect line heights.
         break;
+      case NGInlineItem::kFloating:
+        // Only leading floats are computable without layout.
+        if (is_empty_so_far) {
+          break;
+        }
+        return false;
+      case NGInlineItem::kAtomicInline:
+      case NGInlineItem::kBlockInInline:
+      case NGInlineItem::kInitialLetterBox:
+      case NGInlineItem::kListMarker:
+        // These items need layout to determine the height.
+        return false;
+    }
+    if (is_empty_so_far && !item.IsEmptyItem()) {
+      is_empty_so_far = false;
     }
   }
 
diff --git a/third_party/blink/renderer/core/layout/ng/inline/ng_line_widths_test.cc b/third_party/blink/renderer/core/layout/ng/inline/ng_line_widths_test.cc
index 07ae1e4..f96d6c03 100644
--- a/third_party/blink/renderer/core/layout/ng/inline/ng_line_widths_test.cc
+++ b/third_party/blink/renderer/core/layout/ng/inline/ng_line_widths_test.cc
@@ -65,7 +65,7 @@
     {{100, 100}, R"HTML(
       <div id="target">0123 <b>5</b>678</div>
     )HTML"},
-    // Single left/right float should be computable.
+    // Single left/right leading float should be computable.
     {{70, 100}, R"HTML(
       <div id="target">
         <div class="left"></div>
@@ -76,8 +76,17 @@
       <div id="target">
         <div class="right"></div>
         0123 5678
+      </div
+    )HTML"},
+    // Non-leading floats are not computable.
+    {{}, R"HTML(
+      <div id="target">
+        0123 5678
+        <div class="left"></div>
       </div>
     )HTML"},
+    // Even when the float is taller than the font, it's computable as long as
+    // it fits in the leading.
     {{70, 100}, R"HTML(
       <div id="target" style="line-height: 15px">
         <div class="left" style="height: 11px"></div>
@@ -135,6 +144,18 @@
         0123 5678 <big>0123</big> 5678
       </div>
     )HTML"},
+    // Atomic inlines are not computable if there are leading floats.
+    {{100, 100}, R"HTML(
+      <div id="target">
+        0123 <span style="display: inline-block"></span> 5678
+      </div>
+    )HTML"},
+    {{}, R"HTML(
+      <div id="target">
+        <div class="left"></div>
+        0123 <span style="display: inline-block"></span> 5678
+      </div>
+    )HTML"},
 };
 class NGLineWidthsDataTest
     : public NGLineWidthsTest,
@@ -170,14 +191,12 @@
                                   data.html));
   const NGInlineNode target = GetInlineNodeByElementId("target");
   const absl::optional<NGLineWidths> line_widths = ComputeLineWidths(target);
-  if (!line_widths) {
-    EXPECT_EQ(data.widths.size(), 0u);
-    return;
-  }
-  EXPECT_GT(data.widths.size(), 0u);
   std::vector<int> actual_widths;
-  for (wtf_size_t i = 0; i < data.widths.size(); ++i) {
-    actual_widths.push_back((*line_widths)[i].ToInt());
+  if (line_widths) {
+    const size_t size = data.widths.size() ? data.widths.size() : 3;
+    for (wtf_size_t i = 0; i < size; ++i) {
+      actual_widths.push_back((*line_widths)[i].ToInt());
+    }
   }
   EXPECT_THAT(actual_widths, data.widths);
 }
diff --git a/third_party/blink/renderer/core/paint/build.gni b/third_party/blink/renderer/core/paint/build.gni
index e0f2fcaa..76ce0de 100644
--- a/third_party/blink/renderer/core/paint/build.gni
+++ b/third_party/blink/renderer/core/paint/build.gni
@@ -107,7 +107,9 @@
   "object_painter.h",
   "object_paint_invalidator.cc",
   "object_paint_invalidator.h",
+  "object_paint_properties.cc",
   "object_paint_properties.h",
+  "object_paint_properties_impl.h",
   "outline_painter.cc",
   "outline_painter.h",
   "paint_auto_dark_mode.cc",
diff --git a/third_party/blink/renderer/core/paint/fragment_data.h b/third_party/blink/renderer/core/paint/fragment_data.h
index d95b722..ab5d9ac 100644
--- a/third_party/blink/renderer/core/paint/fragment_data.h
+++ b/third_party/blink/renderer/core/paint/fragment_data.h
@@ -98,7 +98,7 @@
   ObjectPaintProperties& EnsurePaintProperties() {
     EnsureRareData();
     if (!rare_data_->paint_properties)
-      rare_data_->paint_properties = std::make_unique<ObjectPaintProperties>();
+      rare_data_->paint_properties = ObjectPaintProperties::Create();
     return *rare_data_->paint_properties;
   }
   void ClearPaintProperties() {
diff --git a/third_party/blink/renderer/core/paint/object_paint_properties.cc b/third_party/blink/renderer/core/paint/object_paint_properties.cc
new file mode 100644
index 0000000..52a796d
--- /dev/null
+++ b/third_party/blink/renderer/core/paint/object_paint_properties.cc
@@ -0,0 +1,18 @@
+// 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 "third_party/blink/renderer/core/paint/object_paint_properties.h"
+
+#include "third_party/blink/renderer/core/paint/object_paint_properties_impl.h"
+
+namespace blink {
+
+ObjectPaintProperties::~ObjectPaintProperties() = default;
+
+// static
+std::unique_ptr<ObjectPaintProperties> ObjectPaintProperties::Create() {
+  return std::make_unique<ObjectPaintPropertiesImpl>();
+}
+
+}  // namespace blink
diff --git a/third_party/blink/renderer/core/paint/object_paint_properties.h b/third_party/blink/renderer/core/paint/object_paint_properties.h
index 98fa226..d1c9dfe 100644
--- a/third_party/blink/renderer/core/paint/object_paint_properties.h
+++ b/third_party/blink/renderer/core/paint/object_paint_properties.h
@@ -1,13 +1,11 @@
-// Copyright 2015 The Chromium Authors
+// 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 THIRD_PARTY_BLINK_RENDERER_CORE_PAINT_OBJECT_PAINT_PROPERTIES_H_
 #define THIRD_PARTY_BLINK_RENDERER_CORE_PAINT_OBJECT_PAINT_PROPERTIES_H_
 
-#include <array>
 #include <memory>
-#include <utility>
 
 #include "base/dcheck_is_on.h"
 #include "base/memory/ptr_util.h"
@@ -21,10 +19,11 @@
 
 namespace blink {
 
-// This class stores the paint property nodes created by a LayoutObject. The
-// object owns each of the property nodes directly and RefPtrs are only used to
-// harden against use-after-free bugs. These paint properties are built/updated
-// by PaintPropertyTreeBuilder during the PrePaint lifecycle step.
+// This interface is for storing the paint property nodes created by a
+// LayoutObject. The object owns each of the property nodes directly and RefPtrs
+// are only used to harden against use-after-free bugs. These paint properties
+// are built/updated by PaintPropertyTreeBuilder during the PrePaint lifecycle
+// step.
 //
 // [update & clear implementation note] This class has Update[property](...) and
 // Clear[property]() helper functions for efficiently creating and updating
@@ -41,13 +40,11 @@
   USING_FAST_MALLOC(ObjectPaintProperties);
 
  public:
-  ObjectPaintProperties() = default;
-  ObjectPaintProperties(const ObjectPaintProperties&) = delete;
-  ObjectPaintProperties& operator=(const ObjectPaintProperties&) = delete;
-#if DCHECK_IS_ON()
-  ~ObjectPaintProperties() { DCHECK(!is_immutable_); }
-#endif
+  virtual ~ObjectPaintProperties();
+  static std::unique_ptr<ObjectPaintProperties> Create();
 
+// Preprocessor macro declarations.
+//
 // The following defines 3 functions and one variable:
 // - Foo(): a getter for the property.
 // - UpdateFoo(): an update function.
@@ -58,42 +55,27 @@
 // changes (an existing node was deleted), and false otherwise. See the
 // class-level comment ("update & clear implementation note") for details
 // about why this is needed for efficient updates.
-#define ADD_NODE(type, function, variable)                                   \
- public:                                                                     \
-  const type##PaintPropertyNode* function() const { return variable.get(); } \
-  PaintPropertyChangeType Update##function(                                  \
-      const type##PaintPropertyNodeOrAlias& parent,                          \
-      type##PaintPropertyNode::State&& state,                                \
-      const type##PaintPropertyNode::AnimationState& animation_state =       \
-          type##PaintPropertyNode::AnimationState()) {                       \
-    return Update(variable, parent, std::move(state), animation_state);      \
-  }                                                                          \
-  bool Clear##function() { return Clear(variable); }                         \
-                                                                             \
- private:                                                                    \
-  scoped_refptr<type##PaintPropertyNode> variable
-  // (End of ADD_NODE definition)
+#define ADD_NODE_DECL(type, function)                                  \
+  virtual const type##PaintPropertyNode* function() const = 0;         \
+  virtual PaintPropertyChangeType Update##function(                    \
+      const type##PaintPropertyNodeOrAlias& parent,                    \
+      type##PaintPropertyNode::State&& state,                          \
+      const type##PaintPropertyNode::AnimationState& animation_state = \
+          type##PaintPropertyNode::AnimationState()) = 0;              \
+  virtual bool Clear##function() = 0  // (End of ADD_NODE_DECL definition)
 
-#define ADD_ALIAS_NODE(type, function, variable)           \
- public:                                                   \
-  const type##PaintPropertyNodeOrAlias* function() const { \
-    return variable.get();                                 \
-  }                                                        \
-  PaintPropertyChangeType Update##function(                \
-      const type##PaintPropertyNodeOrAlias& parent) {      \
-    return UpdateAlias(variable, parent);                  \
-  }                                                        \
-  bool Clear##function() { return Clear(variable); }       \
-                                                           \
- private:                                                  \
-  scoped_refptr<type##PaintPropertyNodeAlias> variable
-  // (End of ADD_ALIAS_NODE definition)
+#define ADD_ALIAS_NODE_DECL(type, function)                           \
+  virtual const type##PaintPropertyNodeOrAlias* function() const = 0; \
+  virtual PaintPropertyChangeType Update##function(                   \
+      const type##PaintPropertyNodeOrAlias& parent) = 0;              \
+  virtual bool Clear##function() = 0  // (End of ADD_ALIAS_NODE_DECL definition)
 
-#define ADD_TRANSFORM(function, variable) \
-  ADD_NODE(Transform, function, variable)
-#define ADD_EFFECT(function, variable) ADD_NODE(Effect, function, variable)
-#define ADD_CLIP(function, variable) ADD_NODE(Clip, function, variable)
+#define ADD_TRANSFORM_DECL(function) ADD_NODE_DECL(Transform, function)
+#define ADD_EFFECT_DECL(function) ADD_NODE_DECL(Effect, function)
+#define ADD_CLIP_DECL(function) ADD_NODE_DECL(Clip, function)
 
+  // Transform node method declarations.
+  //
   // The hierarchy of the transform subtree created by a LayoutObject is as
   // follows:
   // [ PaintOffsetTranslation ]
@@ -156,36 +138,27 @@
   //
   // This hierarchy is related to the order of transform operations in
   // https://drafts.csswg.org/css-transforms-2/#accumulated-3d-transformation-matrix-computation
- public:
-  bool HasTransformNode() const {
-    return paint_offset_translation_ || sticky_translation_ ||
-           anchor_scroll_translation_ || translate_ || rotate_ || scale_ ||
-           offset_ || transform_ || perspective_ ||
-           replaced_content_transform_ || scroll_translation_ ||
-           transform_isolation_node_;
-  }
-  bool HasCSSTransformPropertyNode() const {
-    return translate_ || rotate_ || scale_ || offset_ || transform_;
-  }
-  std::array<const TransformPaintPropertyNode*, 5>
-  AllCSSTransformPropertiesOutsideToInside() const {
-    return {Translate(), Rotate(), Scale(), Offset(), Transform()};
-  }
-  ADD_TRANSFORM(PaintOffsetTranslation, paint_offset_translation_);
-  ADD_TRANSFORM(StickyTranslation, sticky_translation_);
-  ADD_TRANSFORM(AnchorScrollTranslation, anchor_scroll_translation_);
-  ADD_TRANSFORM(Translate, translate_);
-  ADD_TRANSFORM(Rotate, rotate_);
-  ADD_TRANSFORM(Scale, scale_);
-  ADD_TRANSFORM(Offset, offset_);
-  ADD_TRANSFORM(Transform, transform_);
-  ADD_TRANSFORM(Perspective, perspective_);
-  ADD_TRANSFORM(ReplacedContentTransform, replaced_content_transform_);
-  ADD_TRANSFORM(ScrollTranslation, scroll_translation_);
+  virtual bool HasTransformNode() const = 0;
+  virtual bool HasCSSTransformPropertyNode() const = 0;
+  virtual std::array<const TransformPaintPropertyNode*, 5>
+  AllCSSTransformPropertiesOutsideToInside() const = 0;
+  ADD_TRANSFORM_DECL(PaintOffsetTranslation);
+  ADD_TRANSFORM_DECL(StickyTranslation);
+  ADD_TRANSFORM_DECL(AnchorScrollTranslation);
+  ADD_TRANSFORM_DECL(Translate);
+  ADD_TRANSFORM_DECL(Rotate);
+  ADD_TRANSFORM_DECL(Scale);
+  ADD_TRANSFORM_DECL(Offset);
+  ADD_TRANSFORM_DECL(Transform);
+  ADD_TRANSFORM_DECL(Perspective);
+  ADD_TRANSFORM_DECL(ReplacedContentTransform);
+  ADD_TRANSFORM_DECL(ScrollTranslation);
   using ScrollPaintPropertyNodeOrAlias = ScrollPaintPropertyNode;
-  ADD_NODE(Scroll, Scroll, scroll_);
-  ADD_ALIAS_NODE(Transform, TransformIsolationNode, transform_isolation_node_);
+  ADD_NODE_DECL(Scroll, Scroll);
+  ADD_ALIAS_NODE_DECL(Transform, TransformIsolationNode);
 
+  // Effect node method declarations.
+  //
   // The hierarchy of the effect subtree created by a LayoutObject is as
   // follows:
   // [ Effect ]
@@ -212,21 +185,18 @@
   //       This serves as a parent to subtree effects on an element with paint
   //       containment, It is the deepest child of any effect tree on the
   //       contain: paint element.
- public:
-  bool HasEffectNode() const {
-    return effect_ || filter_ || vertical_scrollbar_effect_ ||
-           horizontal_scrollbar_effect_ || scroll_corner_effect_ || mask_ ||
-           clip_path_mask_ || effect_isolation_node_;
-  }
-  ADD_EFFECT(Effect, effect_);
-  ADD_EFFECT(Filter, filter_);
-  ADD_EFFECT(VerticalScrollbarEffect, vertical_scrollbar_effect_);
-  ADD_EFFECT(HorizontalScrollbarEffect, horizontal_scrollbar_effect_);
-  ADD_EFFECT(ScrollCornerEffect, scroll_corner_effect_);
-  ADD_EFFECT(Mask, mask_);
-  ADD_EFFECT(ClipPathMask, clip_path_mask_);
-  ADD_ALIAS_NODE(Effect, EffectIsolationNode, effect_isolation_node_);
+  virtual bool HasEffectNode() const = 0;
+  ADD_EFFECT_DECL(Effect);
+  ADD_EFFECT_DECL(Filter);
+  ADD_EFFECT_DECL(VerticalScrollbarEffect);
+  ADD_EFFECT_DECL(HorizontalScrollbarEffect);
+  ADD_EFFECT_DECL(ScrollCornerEffect);
+  ADD_EFFECT_DECL(Mask);
+  ADD_EFFECT_DECL(ClipPathMask);
+  ADD_ALIAS_NODE_DECL(Effect, EffectIsolationNode);
 
+  // Clip node declarations.
+  //
   // The hierarchy of the clip subtree created by a LayoutObject is as follows:
   // [ ViewTransitionClip ]
   // |   Clip created only when there is an active ViewTransition. This is used
@@ -274,135 +244,43 @@
   //       This serves as a parent to subtree clips on an element with paint
   //       containment. It is the deepest child of any clip tree on the contain:
   //       paint element.
- public:
-  bool HasClipNode() const {
-    return pixel_moving_filter_clip_expaner_ || clip_path_clip_ || mask_clip_ ||
-           css_clip_ || overflow_controls_clip_ || inner_border_radius_clip_ ||
-           overflow_clip_ || clip_isolation_node_;
-  }
-  ADD_CLIP(PixelMovingFilterClipExpander, pixel_moving_filter_clip_expaner_);
-  ADD_CLIP(ClipPathClip, clip_path_clip_);
-  ADD_CLIP(MaskClip, mask_clip_);
-  ADD_CLIP(CssClip, css_clip_);
-  ADD_CLIP(CssClipFixedPosition, css_clip_fixed_position_);
-  ADD_CLIP(OverflowControlsClip, overflow_controls_clip_);
-  ADD_CLIP(BackgroundClip, background_clip_);
-  ADD_CLIP(InnerBorderRadiusClip, inner_border_radius_clip_);
-  ADD_CLIP(OverflowClip, overflow_clip_);
-  ADD_ALIAS_NODE(Clip, ClipIsolationNode, clip_isolation_node_);
+  virtual bool HasClipNode() const = 0;
+  ADD_CLIP_DECL(PixelMovingFilterClipExpander);
+  ADD_CLIP_DECL(ClipPathClip);
+  ADD_CLIP_DECL(MaskClip);
+  ADD_CLIP_DECL(CssClip);
+  ADD_CLIP_DECL(CssClipFixedPosition);
+  ADD_CLIP_DECL(OverflowControlsClip);
+  ADD_CLIP_DECL(BackgroundClip);
+  ADD_CLIP_DECL(InnerBorderRadiusClip);
+  ADD_CLIP_DECL(OverflowClip);
+  ADD_ALIAS_NODE_DECL(Clip, ClipIsolationNode);
 
-#undef ADD_CLIP
-#undef ADD_EFFECT
-#undef ADD_TRANSFORM
-#undef ADD_NODE
-#undef ADD_ALIAS_NODE
+#undef ADD_CLIP_DECL
+#undef ADD_EFFECT_DECL
+#undef ADD_TRANSFORM_DECL
+#undef ADD_NODE_DECL
+#undef ADD_ALIAS_NODE_DECL
 
- public:
+// Debug-only state change validation method declarations.
+//
+// Used by find_properties_needing_update.h for verifying state doesn't
+// change.
 #if DCHECK_IS_ON()
-  // Used by find_properties_needing_update.h for verifying state doesn't
-  // change.
-  void SetImmutable() const { is_immutable_ = true; }
-  bool IsImmutable() const { return is_immutable_; }
-  void SetMutable() const { is_immutable_ = false; }
-
-  void Validate() {
-    DCHECK(!ScrollTranslation() || !ReplacedContentTransform())
-        << "Replaced elements don't scroll so there should never be both a "
-           "scroll translation and a replaced content transform.";
-    DCHECK(!ClipPathClip() || !ClipPathMask())
-        << "ClipPathClip and ClipPathshould be mutually exclusive.";
-    DCHECK((!TransformIsolationNode() && !ClipIsolationNode() &&
-            !EffectIsolationNode()) ||
-           (TransformIsolationNode() && ClipIsolationNode() &&
-            EffectIsolationNode()))
-        << "Isolation nodes have to be created for all of transform, clip, and "
-           "effect trees.";
-  }
+  virtual void SetImmutable() const = 0;
+  virtual bool IsImmutable() const = 0;
+  virtual void SetMutable() const = 0;
+  virtual void Validate() = 0;
 #endif
 
-  PaintPropertyChangeType DirectlyUpdateTransformAndOrigin(
+  // Direct update method declarations.
+  virtual PaintPropertyChangeType DirectlyUpdateTransformAndOrigin(
       TransformPaintPropertyNode::TransformAndOrigin&& transform_and_origin,
-      const TransformPaintPropertyNode::AnimationState& animation_state) {
-    return transform_->DirectlyUpdateTransformAndOrigin(
-        std::move(transform_and_origin), animation_state);
-  }
+      const TransformPaintPropertyNode::AnimationState& animation_state) = 0;
 
-  PaintPropertyChangeType DirectlyUpdateOpacity(
+  virtual PaintPropertyChangeType DirectlyUpdateOpacity(
       float opacity,
-      const EffectPaintPropertyNode::AnimationState& animation_state) {
-    // TODO(yotha): Remove this check once we make sure crbug.com/1370268 is
-    // fixed
-    DCHECK(effect_ != nullptr);
-    if (effect_ == nullptr) {
-      return PaintPropertyChangeType::kNodeAddedOrRemoved;
-    }
-    return effect_->DirectlyUpdateOpacity(opacity, animation_state);
-  }
-
- private:
-  // Return true if the property tree structure changes (an existing node was
-  // deleted), and false otherwise. See the class-level comment ("update & clear
-  // implementation note") for details about why this is needed for efficiency.
-  template <typename PaintPropertyNode>
-  bool Clear(scoped_refptr<PaintPropertyNode>& field) {
-    if (field) {
-      field = nullptr;
-      return true;
-    }
-    return false;
-  }
-
-  // Return true if the property tree structure changes (a new node was
-  // created), and false otherwise. See the class-level comment ("update & clear
-  // implementation note") for details about why this is needed for efficiency.
-  template <typename PaintPropertyNode, typename PaintPropertyNodeOrAlias>
-  PaintPropertyChangeType Update(
-      scoped_refptr<PaintPropertyNode>& field,
-      const PaintPropertyNodeOrAlias& parent,
-      typename PaintPropertyNode::State&& state,
-      const typename PaintPropertyNode::AnimationState& animation_state) {
-    if (field) {
-      auto changed = field->Update(parent, std::move(state), animation_state);
-#if DCHECK_IS_ON()
-      DCHECK(!is_immutable_ || changed == PaintPropertyChangeType::kUnchanged)
-          << "Value changed while immutable. New state:\n"
-          << *field;
-#endif
-      return changed;
-    }
-    field = PaintPropertyNode::Create(parent, std::move(state));
-#if DCHECK_IS_ON()
-    DCHECK(!is_immutable_) << "Node added while immutable. New state:\n"
-                           << *field;
-#endif
-    return PaintPropertyChangeType::kNodeAddedOrRemoved;
-  }
-
-  template <typename PaintPropertyNodeAlias, typename PaintPropertyNodeOrAlias>
-  PaintPropertyChangeType UpdateAlias(
-      scoped_refptr<PaintPropertyNodeAlias>& field,
-      const PaintPropertyNodeOrAlias& parent) {
-    if (field) {
-      DCHECK(field->IsParentAlias());
-      auto changed = field->SetParent(parent);
-#if DCHECK_IS_ON()
-      DCHECK(!is_immutable_ || changed == PaintPropertyChangeType::kUnchanged)
-          << "Parent changed while immutable. New state:\n"
-          << *field;
-#endif
-      return changed;
-    }
-    field = PaintPropertyNodeAlias::Create(parent);
-#if DCHECK_IS_ON()
-    DCHECK(!is_immutable_) << "Node added while immutable. New state:\n"
-                           << *field;
-#endif
-    return PaintPropertyChangeType::kNodeAddedOrRemoved;
-  }
-
-#if DCHECK_IS_ON()
-  mutable bool is_immutable_ = false;
-#endif
+      const EffectPaintPropertyNode::AnimationState& animation_state) = 0;
 };
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/paint/object_paint_properties_impl.h b/third_party/blink/renderer/core/paint/object_paint_properties_impl.h
new file mode 100644
index 0000000..4e904ca
--- /dev/null
+++ b/third_party/blink/renderer/core/paint/object_paint_properties_impl.h
@@ -0,0 +1,263 @@
+// 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 THIRD_PARTY_BLINK_RENDERER_CORE_PAINT_OBJECT_PAINT_PROPERTIES_IMPL_H_
+#define THIRD_PARTY_BLINK_RENDERER_CORE_PAINT_OBJECT_PAINT_PROPERTIES_IMPL_H_
+
+#include <array>
+#include <memory>
+#include <utility>
+
+#include "base/dcheck_is_on.h"
+#include "base/memory/ptr_util.h"
+#include "base/memory/scoped_refptr.h"
+#include "third_party/blink/renderer/core/core_export.h"
+#include "third_party/blink/renderer/core/paint/object_paint_properties.h"
+#include "third_party/blink/renderer/platform/graphics/paint/clip_paint_property_node.h"
+#include "third_party/blink/renderer/platform/graphics/paint/effect_paint_property_node.h"
+#include "third_party/blink/renderer/platform/graphics/paint/scroll_paint_property_node.h"
+#include "third_party/blink/renderer/platform/graphics/paint/transform_paint_property_node.h"
+#include "third_party/blink/renderer/platform/wtf/allocator/allocator.h"
+
+namespace blink {
+
+// This class is the implementation of the ObjectPaintProperties interface,
+// and is used for storing the paint property nodes created by a LayoutObject.
+class CORE_EXPORT ObjectPaintPropertiesImpl : public ObjectPaintProperties {
+  USING_FAST_MALLOC(ObjectPaintPropertiesImpl);
+
+ public:
+  ObjectPaintPropertiesImpl() = default;
+  ObjectPaintPropertiesImpl(ObjectPaintPropertiesImpl&&) = default;
+  ObjectPaintPropertiesImpl(const ObjectPaintPropertiesImpl&) = delete;
+  ObjectPaintPropertiesImpl& operator=(ObjectPaintPropertiesImpl&&) = default;
+  ObjectPaintPropertiesImpl& operator=(const ObjectPaintPropertiesImpl&) =
+      delete;
+
+  ~ObjectPaintPropertiesImpl() override {
+#if DCHECK_IS_ON()
+    DCHECK(!is_immutable_);
+#endif
+  }
+
+// Preprocessor macro implementations.
+#define ADD_NODE(type, function, variable)                              \
+ public:                                                                \
+  const type##PaintPropertyNode* function() const override {            \
+    return variable.get();                                              \
+  }                                                                     \
+  PaintPropertyChangeType Update##function(                             \
+      const type##PaintPropertyNodeOrAlias& parent,                     \
+      type##PaintPropertyNode::State&& state,                           \
+      const type##PaintPropertyNode::AnimationState& animation_state =  \
+          type##PaintPropertyNode::AnimationState()) override {         \
+    return Update(variable, parent, std::move(state), animation_state); \
+  }                                                                     \
+  bool Clear##function() override { return Clear(variable); }           \
+                                                                        \
+ private:                                                               \
+  scoped_refptr<type##PaintPropertyNode> variable
+  // (End of ADD_NODE definition)
+
+#define ADD_ALIAS_NODE(type, function, variable)                    \
+ public:                                                            \
+  const type##PaintPropertyNodeOrAlias* function() const override { \
+    return variable.get();                                          \
+  }                                                                 \
+  PaintPropertyChangeType Update##function(                         \
+      const type##PaintPropertyNodeOrAlias& parent) override {      \
+    return UpdateAlias(variable, parent);                           \
+  }                                                                 \
+  bool Clear##function() override { return Clear(variable); }       \
+                                                                    \
+ private:                                                           \
+  scoped_refptr<type##PaintPropertyNodeAlias> variable
+  // (End of ADD_ALIAS_NODE definition)
+
+#define ADD_TRANSFORM(function, variable) \
+  ADD_NODE(Transform, function, variable)
+#define ADD_EFFECT(function, variable) ADD_NODE(Effect, function, variable)
+#define ADD_CLIP(function, variable) ADD_NODE(Clip, function, variable)
+
+  // Transform node implementations.
+  bool HasTransformNode() const override {
+    return paint_offset_translation_ || sticky_translation_ ||
+           anchor_scroll_translation_ || translate_ || rotate_ || scale_ ||
+           offset_ || transform_ || perspective_ ||
+           replaced_content_transform_ || scroll_translation_ ||
+           transform_isolation_node_;
+  }
+  bool HasCSSTransformPropertyNode() const override {
+    return translate_ || rotate_ || scale_ || offset_ || transform_;
+  }
+  std::array<const TransformPaintPropertyNode*, 5>
+  AllCSSTransformPropertiesOutsideToInside() const override {
+    return {Translate(), Rotate(), Scale(), Offset(), Transform()};
+  }
+  ADD_TRANSFORM(PaintOffsetTranslation, paint_offset_translation_);
+  ADD_TRANSFORM(StickyTranslation, sticky_translation_);
+  ADD_TRANSFORM(AnchorScrollTranslation, anchor_scroll_translation_);
+  ADD_TRANSFORM(Translate, translate_);
+  ADD_TRANSFORM(Rotate, rotate_);
+  ADD_TRANSFORM(Scale, scale_);
+  ADD_TRANSFORM(Offset, offset_);
+  ADD_TRANSFORM(Transform, transform_);
+  ADD_TRANSFORM(Perspective, perspective_);
+  ADD_TRANSFORM(ReplacedContentTransform, replaced_content_transform_);
+  ADD_TRANSFORM(ScrollTranslation, scroll_translation_);
+  using ScrollPaintPropertyNodeOrAlias = ScrollPaintPropertyNode;
+  ADD_NODE(Scroll, Scroll, scroll_);
+  ADD_ALIAS_NODE(Transform, TransformIsolationNode, transform_isolation_node_);
+
+  // Effect node implementations.
+  bool HasEffectNode() const override {
+    return effect_ || filter_ || vertical_scrollbar_effect_ ||
+           horizontal_scrollbar_effect_ || scroll_corner_effect_ || mask_ ||
+           clip_path_mask_ || effect_isolation_node_;
+  }
+  ADD_EFFECT(Effect, effect_);
+  ADD_EFFECT(Filter, filter_);
+  ADD_EFFECT(VerticalScrollbarEffect, vertical_scrollbar_effect_);
+  ADD_EFFECT(HorizontalScrollbarEffect, horizontal_scrollbar_effect_);
+  ADD_EFFECT(ScrollCornerEffect, scroll_corner_effect_);
+  ADD_EFFECT(Mask, mask_);
+  ADD_EFFECT(ClipPathMask, clip_path_mask_);
+  ADD_ALIAS_NODE(Effect, EffectIsolationNode, effect_isolation_node_);
+
+  // Clip node implementations.
+  bool HasClipNode() const override {
+    return pixel_moving_filter_clip_expander_ || clip_path_clip_ ||
+           mask_clip_ || css_clip_ || overflow_controls_clip_ ||
+           inner_border_radius_clip_ || overflow_clip_ || clip_isolation_node_;
+  }
+  ADD_CLIP(PixelMovingFilterClipExpander, pixel_moving_filter_clip_expander_);
+  ADD_CLIP(ClipPathClip, clip_path_clip_);
+  ADD_CLIP(MaskClip, mask_clip_);
+  ADD_CLIP(CssClip, css_clip_);
+  ADD_CLIP(CssClipFixedPosition, css_clip_fixed_position_);
+  ADD_CLIP(OverflowControlsClip, overflow_controls_clip_);
+  ADD_CLIP(BackgroundClip, background_clip_);
+  ADD_CLIP(InnerBorderRadiusClip, inner_border_radius_clip_);
+  ADD_CLIP(OverflowClip, overflow_clip_);
+  ADD_ALIAS_NODE(Clip, ClipIsolationNode, clip_isolation_node_);
+
+#undef ADD_CLIP
+#undef ADD_EFFECT
+#undef ADD_TRANSFORM
+#undef ADD_NODE
+#undef ADD_ALIAS_NODE
+
+// Debug-only state change validation method implementations.
+#if DCHECK_IS_ON()
+  void SetImmutable() const override { is_immutable_ = true; }
+  bool IsImmutable() const override { return is_immutable_; }
+  void SetMutable() const override { is_immutable_ = false; }
+
+  void Validate() override {
+    DCHECK(!ScrollTranslation() || !ReplacedContentTransform())
+        << "Replaced elements don't scroll so there should never be both a "
+           "scroll translation and a replaced content transform.";
+    DCHECK(!ClipPathClip() || !ClipPathMask())
+        << "ClipPathClip and ClipPathshould be mutually exclusive.";
+    DCHECK((!TransformIsolationNode() && !ClipIsolationNode() &&
+            !EffectIsolationNode()) ||
+           (TransformIsolationNode() && ClipIsolationNode() &&
+            EffectIsolationNode()))
+        << "Isolation nodes have to be created for all of transform, clip, and "
+           "effect trees.";
+  }
+#endif
+
+  // Direct update method implementations.
+  PaintPropertyChangeType DirectlyUpdateTransformAndOrigin(
+      TransformPaintPropertyNode::TransformAndOrigin&& transform_and_origin,
+      const TransformPaintPropertyNode::AnimationState& animation_state)
+      override {
+    return transform_->DirectlyUpdateTransformAndOrigin(
+        std::move(transform_and_origin), animation_state);
+  }
+
+  PaintPropertyChangeType DirectlyUpdateOpacity(
+      float opacity,
+      const EffectPaintPropertyNode::AnimationState& animation_state) override {
+    // TODO(yotha): Remove this check once we make sure crbug.com/1370268 is
+    // fixed
+    DCHECK(effect_ != nullptr);
+    if (effect_ == nullptr) {
+      return PaintPropertyChangeType::kNodeAddedOrRemoved;
+    }
+    return effect_->DirectlyUpdateOpacity(opacity, animation_state);
+  }
+
+ private:
+  // Return true if the property tree structure changes (an existing node was
+  // deleted), and false otherwise. See the class-level comment on
+  // ObjectPaintProperties ("update & clear implementation note") for details
+  // about why this is needed for efficiency.
+  template <typename PaintPropertyNode>
+  bool Clear(scoped_refptr<PaintPropertyNode>& field) {
+    if (field) {
+      field = nullptr;
+      return true;
+    }
+    return false;
+  }
+
+  // Return true if the property tree structure changes (a new node was
+  // created), and false otherwise. See the class-level comment on
+  // ObjectPaintProperties ("update & clear implementation note") for details
+  // about why this is needed for efficiency.
+  template <typename PaintPropertyNode, typename PaintPropertyNodeOrAlias>
+  PaintPropertyChangeType Update(
+      scoped_refptr<PaintPropertyNode>& field,
+      const PaintPropertyNodeOrAlias& parent,
+      typename PaintPropertyNode::State&& state,
+      const typename PaintPropertyNode::AnimationState& animation_state) {
+    if (field) {
+      auto changed = field->Update(parent, std::move(state), animation_state);
+#if DCHECK_IS_ON()
+      DCHECK(!is_immutable_ || changed == PaintPropertyChangeType::kUnchanged)
+          << "Value changed while immutable. New state:\n"
+          << *field;
+#endif
+      return changed;
+    }
+    field = PaintPropertyNode::Create(parent, std::move(state));
+#if DCHECK_IS_ON()
+    DCHECK(!is_immutable_) << "Node added while immutable. New state:\n"
+                           << *field;
+#endif
+    return PaintPropertyChangeType::kNodeAddedOrRemoved;
+  }
+
+  template <typename PaintPropertyNodeAlias, typename PaintPropertyNodeOrAlias>
+  PaintPropertyChangeType UpdateAlias(
+      scoped_refptr<PaintPropertyNodeAlias>& field,
+      const PaintPropertyNodeOrAlias& parent) {
+    if (field) {
+      DCHECK(field->IsParentAlias());
+      auto changed = field->SetParent(parent);
+#if DCHECK_IS_ON()
+      DCHECK(!is_immutable_ || changed == PaintPropertyChangeType::kUnchanged)
+          << "Parent changed while immutable. New state:\n"
+          << *field;
+#endif
+      return changed;
+    }
+    field = PaintPropertyNodeAlias::Create(parent);
+#if DCHECK_IS_ON()
+    DCHECK(!is_immutable_) << "Node added while immutable. New state:\n"
+                           << *field;
+#endif
+    return PaintPropertyChangeType::kNodeAddedOrRemoved;
+  }
+
+#if DCHECK_IS_ON()
+  mutable bool is_immutable_ = false;
+#endif
+};
+
+}  // namespace blink
+
+#endif  // THIRD_PARTY_BLINK_RENDERER_CORE_PAINT_OBJECT_PAINT_PROPERTIES_IMPL_H_
diff --git a/third_party/blink/renderer/modules/accessibility/ax_object_cache_impl.cc b/third_party/blink/renderer/modules/accessibility/ax_object_cache_impl.cc
index e24dc151..afa6f5d3 100644
--- a/third_party/blink/renderer/modules/accessibility/ax_object_cache_impl.cc
+++ b/third_party/blink/renderer/modules/accessibility/ax_object_cache_impl.cc
@@ -1719,7 +1719,9 @@
 }
 
 void AXObjectCacheImpl::RemoveSubtreeWhenSafe(Node* node) {
-  DCHECK(node);
+  if (!node || !node->isConnected()) {
+    return;
+  }
   if (AXObject::CanSafelyUseFlatTreeTraversalNow(node->GetDocument())) {
     RemoveSubtreeWithFlatTraversal(node, /* remove_root */ true,
                                    /* notify_parent */ true);
diff --git a/third_party/blink/tools/blinkpy/w3c/wpt_expectations_updater.py b/third_party/blink/tools/blinkpy/w3c/wpt_expectations_updater.py
index c35a3f5..bc667d69 100644
--- a/third_party/blink/tools/blinkpy/w3c/wpt_expectations_updater.py
+++ b/third_party/blink/tools/blinkpy/w3c/wpt_expectations_updater.py
@@ -25,21 +25,17 @@
 from blinkpy.common.system.executive import ScriptError
 from blinkpy.common.system.log_utils import configure_logging
 from blinkpy.web_tests.models.test_expectations import (
-    ParseError, SystemConfigurationEditor, TestExpectations)
+    ExpectationsChange,
+    ParseError,
+    SystemConfigurationEditor,
+    TestExpectations,
+)
 from blinkpy.web_tests.models.typ_types import ResultType
 
 _log = logging.getLogger(__name__)
 
 
-# TODO(crbug.com/1149035): Investigate reusing
-# `web_tests.models.test_expectations` and alike in this module.
-#
-# That module has functionality to update existing lines, or group lines
-# affecting the same test. Neither is possible currently; only new lines can be
-# added.
-
 SimpleTestResult = namedtuple('SimpleTestResult', ['expected', 'actual', 'bug'])
-
 DesktopConfig = namedtuple('DesktopConfig', ['port_name'])
 
 
@@ -517,204 +513,17 @@
         # add a Missing expectation (this is not allowed), but no other
         # expectation is correct.
         if 'MISSING' in actual_results:
-            return {'Skip'}
+            return {ResultType.Skip}
         expectations = set()
         failure_types = {'TEXT', 'IMAGE+TEXT', 'IMAGE', 'AUDIO', 'FAIL'}
         other_types = {'TIMEOUT', 'CRASH', 'PASS'}
         for actual in actual_results:
             if actual in failure_types:
-                expectations.add('Failure')
+                expectations.add(ResultType.Failure)
             if actual in other_types:
-                expectations.add(actual.capitalize())
+                expectations.add(actual)
         return expectations
 
-    def remove_configurations(self, configs_to_remove):
-        """Removes configs from test expectations files for some tests
-
-        Args:
-            configs_to_remove: A dict maps test names to set of os versions:
-                {
-                    'test-with-failing-result': ['os1', 'os2', ...]
-                }
-        Returns: None
-        """
-        # SystemConfigurationEditor now only works on generic test expectations
-        # files. This is good enough for now
-        path = self.port.path_to_generic_test_expectations_file()
-        test_expectations = TestExpectations(
-            self.port,
-            expectations_dict={
-                path: self.host.filesystem.read_text_file(path),
-            })
-        system_remover = SystemConfigurationEditor(test_expectations)
-        for test, versions in configs_to_remove.items():
-            system_remover.remove_os_versions(test, versions)
-        system_remover.update_expectations()
-
-    def create_line_dict_for_flag_specific(self, merged_results, generic_expectations):
-        """Creates list of test expectations lines for flag specific builder.
-
-        Traverses through the given |merged_results| dictionary and parses the
-        value to create one test expectations line per key. If a test expectation
-        from generic expectations can be inherited, we will reuse that expectation
-        so that we can keep the file size small. Flag specific expectations
-        does not use platform tag, so we don't need handle conflicts either.
-
-        Test expectation lines have the following format:
-            ['BUG_URL TEST_NAME [EXPECTATION(S)]']
-
-        Args:
-            merged_results: A dictionary with the format:
-                {
-                    'test-with-failing-result': {
-                        (config1,): SimpleTestResult
-                    }
-                }
-
-        Returns:
-            line_dict: A dictionary from test names to a list of test
-                       expectation lines
-                       (each SimpleTestResult turns into a line).
-            configs_to_remove: An empty dictionary
-        """
-        line_dict = defaultdict(list)
-        for test_name, test_results in sorted(merged_results.items()):
-            if not self._is_wpt_test(test_name):
-                _log.warning(
-                    'Non-WPT test "%s" unexpectedly passed to create_line_dict.',
-                    test_name)
-                continue
-            expectation_line = generic_expectations.get_expectations(test_name)
-            expectations = expectation_line.results
-            for configs, result in sorted(test_results.items()):
-                new_expectations = self.get_expectations(result, test_name)
-                if 'Failure' in new_expectations:
-                    new_expectations.remove('Failure')
-                    new_expectations.add('FAIL')
-                if new_expectations != expectations:
-                    line_dict[test_name].extend(
-                        self._create_lines(test_name, [], result))
-                # for flag-specific builders, we always have one config for each
-                # test, so quit the loop here
-                break
-
-        return line_dict, {}
-
-    def create_line_dict(self, merged_results):
-        """Creates list of test expectations lines.
-
-        Traverses through the given |merged_results| dictionary and parses the
-        value to create one test expectations line per key.
-
-        Test expectation lines have the following format:
-            ['BUG_URL [PLATFORM(S)] TEST_NAME [EXPECTATION(S)]']
-
-        Args:
-            merged_results: A dictionary with the format:
-                {
-                    'test-with-failing-result': {
-                        (config1, config2): SimpleTestResult,
-                        (config3,): SimpleTestResult
-                    }
-                }
-
-        Returns:
-            line_dict: A dictionary from test names to a list of test
-                       expectation lines
-                       (each SimpleTestResult turns into a line).
-            configs_to_remove: A dictionary from test names to a set
-                               of os specifiers
-        """
-        line_dict = defaultdict(list)
-        configs_to_remove = defaultdict(set)
-        for test_name, test_results in sorted(merged_results.items()):
-            if not self._is_wpt_test(test_name):
-                _log.warning(
-                    'Non-WPT test "%s" unexpectedly passed to create_line_dict.',
-                    test_name)
-                continue
-            for configs, result in sorted(test_results.items()):
-                line_dict[test_name].extend(
-                    self._create_lines(test_name, configs, result))
-                for config in configs:
-                    configs_to_remove[test_name].add(
-                        self.host.builders.version_specifier_for_port_name(
-                            config.port_name))
-
-        return line_dict, configs_to_remove
-
-    def _create_lines(self, test_name, configs, result):
-        """Constructs test expectation line strings.
-
-        Args:
-            test_name: The test name string.
-            configs: A list of full configs that the line should apply to.
-            result: A SimpleTestResult.
-
-        Returns:
-            A list of strings which each is a line of test expectation for given
-            |test_name|.
-        """
-        lines = []
-
-        expectations = '[ %s ]' % \
-            ' '.join(self.get_expectations(result, test_name))
-        for specifier in self.normalized_specifiers(test_name, configs):
-            line_parts = []
-            if specifier:
-                line_parts.append('[ %s ]' % specifier)
-            # Escape literal asterisks for typ (https://crbug.com/1036130).
-            # TODO(weizhong): consider other escapes we added recently
-            line_parts.append(test_name.replace('*', '\\*'))
-            line_parts.append(expectations)
-
-            # Only add the bug link if the expectations do not include SKIP.
-            if 'Skip' not in expectations and result.bug:
-                line_parts.insert(0, result.bug)
-
-            lines.append(' '.join(line_parts))
-        return lines
-
-    def normalized_specifiers(self, test_name, configs):
-        """Converts and simplifies ports into platform specifiers.
-
-        Args:
-            test_name: The test name string.
-            configs: A list of full configs that the line should apply to.
-
-        Returns:
-            A list of specifier string, e.g. ["Mac", "Win"].
-            [''] will be returned if the line should apply to all platforms.
-        """
-        if not configs:
-            return ['']
-
-        specifiers = []
-        for config in configs:
-            specifiers.append(
-                self.host.builders.version_specifier_for_port_name(
-                    config.port_name))
-
-        if self.specifiers_can_extend_to_all_platforms(specifiers, test_name):
-            return ['']
-
-        specifiers = self.simplify_specifiers(
-            specifiers, self.port.configuration_specifier_macros())
-        if not specifiers:
-            return ['']
-        return specifiers
-
-    def specifiers_can_extend_to_all_platforms(self, specifiers, test_name):
-        """Tests whether a list of specifiers can be extended to all platforms.
-
-        Tries to add skipped platform specifiers to the list and tests if the
-        extended list covers all platforms.
-        """
-        extended_specifiers = specifiers + self.skipped_specifiers(test_name)
-        # If the list is simplified to empty, then all platforms are covered.
-        return not self.simplify_specifiers(
-            extended_specifiers, self.port.configuration_specifier_macros())
-
     def skipped_specifiers(self, test_name):
         """Returns a list of platform specifiers for which the test is skipped."""
         specifiers = []
@@ -733,48 +542,10 @@
             for name in self._get_try_bots()
         ]
 
-    def simplify_specifiers(self, specifiers, specifier_macros):
-        """Simplifies the specifier part of an expectation line if possible.
-
-        "Simplifying" means finding the shortest list of platform specifiers
-        that is equivalent to the given list of specifiers. This can be done
-        because there are "macro specifiers" that stand in for multiple version
-        specifiers, and an empty list stands in for "all platforms".
-
-        Args:
-            specifiers: A collection of specifiers (case insensitive).
-            specifier_macros: A dict mapping "macros" for groups of specifiers
-                to lists of version specifiers. e.g. {"win": ["win10", "win11"]}.
-                If there are versions in this dict for that have no corresponding
-                try bots, they are ignored.
-
-        Returns:
-            A shortened list of specifiers (capitalized). For example, ["win10",
-            "win11"] would be converted to ["Win"]. If the given list covers
-            all supported platforms, then an empty list is returned.
-        """
-        specifiers = {s.lower() for s in specifiers}
-        covered_by_try_bots = self._platform_specifiers_covered_by_try_bots()
-        for macro, versions in specifier_macros.items():
-            macro = macro.lower()
-
-            # Only consider version specifiers that have corresponding try bots.
-            versions = {
-                s.lower()
-                for s in versions if s.lower() in covered_by_try_bots
-            }
-            if len(versions) == 0:
-                continue
-            if versions <= specifiers:
-                specifiers -= versions
-                specifiers.add(macro)
-        if specifiers == {macro.lower() for macro in specifier_macros}:
-            return []
-        return sorted(specifier.capitalize() for specifier in specifiers)
-
-    def _platform_specifiers_covered_by_try_bots(self):
+    def _platform_specifiers_covered_by_try_bots(
+            self, flag_specific: Optional[str] = None):
         all_platform_specifiers = set()
-        for builder_name in self._get_try_bots():
+        for builder_name in self._get_try_bots(flag_specific):
             all_platform_specifiers.add(
                 self.host.builders.platform_specifier_for_builder(
                     builder_name).lower())
@@ -795,66 +566,62 @@
         Returns:
             Dictionary mapping test names to lists of test expectation strings.
         """
+        covered_versions = self._platform_specifiers_covered_by_try_bots(
+            flag_specific)
+        port = self.host.port_factory.get()
         if flag_specific:
-            line_dict, configs_to_remove = self.create_line_dict_for_flag_specific(
-                test_expectations, TestExpectations(self.port))
+            port.set_option_default('flag_specific', flag_specific)
+            path = port.path_to_flag_specific_expectations_file(flag_specific)
         else:
-            line_dict, configs_to_remove = self.create_line_dict(test_expectations)
-        if not line_dict:
-            _log.info('No lines to write to %s or WebdriverExpectations.',
-                      flag_specific or 'TestExpectations')
-            return {}
-
-        if configs_to_remove:
-            _log.info('Clean up stale expectations that'
-                      ' could conflict with new expectations')
-            self.remove_configurations(configs_to_remove)
-
-        line_list = []
-        webdriver_list = []
-        # CQ/CI always skips manual tests, so writing them to `NeverFixTests`
-        # is unnecessary. See also: crrev.com/c/3658291.
-        for lines in line_dict.values():
-            for line in lines:
-                if self.finder.webdriver_prefix() in line:
-                    webdriver_list.append(line)
-                else:
-                    line_list.append(line)
-
-        if flag_specific:
-            list_to_expectation = {
-                self.port.path_to_flag_specific_expectations_file(flag_specific): line_list
+            path = port.path_to_generic_test_expectations_file()
+        expectations, change = TestExpectations(port), ExpectationsChange()
+        for test in sorted(filter(self._is_wpt_test, test_expectations)):
+            skipped_versions = {
+                version.lower()
+                for version in self.skipped_specifiers(test)
             }
-        else:
-            list_to_expectation = {
-                self.port.path_to_generic_test_expectations_file(): line_list,
-                self.port.path_to_webdriver_expectations_file(): webdriver_list
+            # Find version specifiers needed to promote versions to their OS
+            # (covered versions that are not skipped). For flag-specific
+            # expectations, there should only be one covered version at most
+            # that will automatically be promoted to a generic line.
+            macros = {
+                os: [
+                    version for version in versions
+                    if version in covered_versions - skipped_versions
+                ]
+                for os, versions in
+                self.port.configuration_specifier_macros().items()
             }
-        for expectations_file_path, lines in list_to_expectation.items():
-            if not lines:
-                continue
-
-            _log.info('Lines to write to %s:\n %s', expectations_file_path,
-                      '\n'.join(lines))
-            # Writes to TestExpectations file.
-            file_contents = self.host.filesystem.read_text_file(
-                expectations_file_path)
-
-            marker_comment_index = file_contents.find(self.MARKER_COMMENT)
-            if marker_comment_index == -1:
-                file_contents += '\n%s\n' % self.MARKER_COMMENT
-                file_contents += '\n'.join(lines)
-            else:
-                end_of_marker_line = (file_contents[marker_comment_index:].
-                                      find('\n')) + marker_comment_index
-                file_contents = (
-                    file_contents[:end_of_marker_line + 1] + '\n'.join(lines) +
-                    file_contents[end_of_marker_line:])
-
-            self.host.filesystem.write_text_file(expectations_file_path,
-                                                 file_contents)
-
-        return line_dict
+            editor = SystemConfigurationEditor(expectations, path, macros)
+            for configs, result in test_expectations[test].items():
+                versions = set()
+                for config in configs:
+                    specifier = self.host.builders.version_specifier_for_port_name(
+                        config.port_name)
+                    if specifier:
+                        versions.add(specifier)
+                statuses = self.get_expectations(result, test)
+                # Avoid writing flag-specific expectations redundant with
+                # generic ones.
+                if flag_specific and statuses == expectations.get_expectations(
+                        test).results:
+                    continue
+                change += editor.update_versions(
+                    test,
+                    versions,
+                    statuses,
+                    reason=result.bug,
+                    marker=self.MARKER_COMMENT[len('# '):])
+                change += editor.merge_versions(test)
+        if not change.lines_added:
+            _log.info(
+                'No lines to write to %s.',
+                self.host.filesystem.relpath(path, self.port.web_tests_dir()))
+        expectations.commit_changes()
+        new_lines = defaultdict(list)
+        for line in change.lines_added:
+            new_lines[line.test].append(line.to_string())
+        return {test: sorted(lines) for test, lines in new_lines.items()}
 
     def skip_slow_timeout_tests(self, port):
         """Skip any Slow and Timeout tests found in TestExpectations.
@@ -1162,6 +929,8 @@
         return self.finder.is_webdriver_test_path(test_name)
 
     @memoized
-    def _get_try_bots(self):
+    def _get_try_bots(self, flag_specific: Optional[str] = None):
         return self.host.builders.filter_builders(
-            is_try=True, exclude_specifiers={'android'})
+            is_try=True,
+            exclude_specifiers={'android'},
+            flag_specific=flag_specific)
diff --git a/third_party/blink/tools/blinkpy/w3c/wpt_expectations_updater_unittest.py b/third_party/blink/tools/blinkpy/w3c/wpt_expectations_updater_unittest.py
index 938830c..756739b 100644
--- a/third_party/blink/tools/blinkpy/w3c/wpt_expectations_updater_unittest.py
+++ b/third_party/blink/tools/blinkpy/w3c/wpt_expectations_updater_unittest.py
@@ -4,6 +4,7 @@
 
 import copy
 import json
+import textwrap
 import unittest
 
 from blinkpy.common.host_mock import MockHost
@@ -21,6 +22,7 @@
 
 from blinkpy.web_tests.builder_list import BuilderList
 from blinkpy.web_tests.models.test_expectations import TestExpectations
+from blinkpy.web_tests.models.typ_types import ResultType
 from blinkpy.web_tests.port.factory_mock import MockPortFactory
 from blinkpy.web_tests.port.test import MOCK_WEB_TESTS
 
@@ -81,10 +83,23 @@
             },
         })
 
+        fs = host.filesystem
+        port = host.port_factory.get()
+        for path in ('TestExpectations', 'FlagExpectations/fake-flag'):
+            fs.write_text_file(
+                fs.join(port.web_tests_dir(), path),
+                textwrap.dedent("""\
+                    # tags: [ Mac10.10 Mac10.11 Mac Trusty Precise Linux Win7 Win10 Win ]
+                    # results: [ Timeout Crash Pass Failure Skip ]
+                    """))
+        fs.write_text_file(fs.join(port.web_tests_dir(), 'FlagSpecificConfig'),
+                           json.dumps([{
+                               'name': 'fake-flag',
+                               'args': [],
+                           }]))
         # Write a dummy manifest file, describing what tests exist.
-        host.filesystem.write_text_file(
-            host.port_factory.get().web_tests_dir() + 'external/' +
-            BASE_MANIFEST_NAME,
+        fs.write_text_file(
+            fs.join(port.web_tests_dir(), 'external', BASE_MANIFEST_NAME),
             json.dumps({
                 'items': {
                     'reftest': {
@@ -179,10 +194,16 @@
     def test_run_inherited_results(self):
         host = self.mock_host()
         # Fill in an initial value for TestExpectations
-        expectations_path = \
-            host.port_factory.get().path_to_generic_test_expectations_file()
+        port = host.port_factory.get()
+        expectations_path = port.path_to_generic_test_expectations_file()
         host.filesystem.write_text_file(
-            expectations_path, WPTExpectationsUpdater.MARKER_COMMENT + '\n')
+            expectations_path,
+            textwrap.dedent("""\
+                # tags: [ Mac10.10 Mac10.11 Mac Trusty Precise Linux Win7 Win10 Win ]
+                # results: [ Timeout Crash Pass Failure Skip ]
+
+                # ====== New tests from wpt-importer added here ======
+                """))
         # Set up fake try job results.
         updater = WPTExpectationsUpdater(host)
         updater.git_cl = MockGitCL(
@@ -224,9 +245,13 @@
         self.assertEqual(0, updater.run())
         self.assertEqual(
             host.filesystem.read_text_file(expectations_path),
-            '# ====== New tests from wpt-importer added here ======\n'
-            'crbug.com/626703 [ Mac ] external/wpt/test/path.html [ Timeout ]\n'
-        )
+            textwrap.dedent("""\
+                # tags: [ Mac10.10 Mac10.11 Mac Trusty Precise Linux Win7 Win10 Win ]
+                # results: [ Timeout Crash Pass Failure Skip ]
+
+                # ====== New tests from wpt-importer added here ======
+                crbug.com/626703 [ Mac ] external/wpt/test/path.html [ Timeout ]
+                """))
 
     def test_run_single_flag_specific_failure(self):
         """Tests the main run method in a case where one test fails on one
@@ -457,66 +482,72 @@
         # Positional arguments of SimpleTestResult: (expected, actual, bug)
         self.assertEqual(
             updater.get_expectations(SimpleTestResult('FAIL', 'PASS', 'bug')),
-            {'Pass'})
+            {'PASS'})
         self.assertEqual(
             updater.get_expectations(
-                SimpleTestResult('FAIL', 'PASS PASS', 'bug')), {'Pass'})
+                SimpleTestResult('FAIL', 'PASS PASS', 'bug')), {'PASS'})
         self.assertEqual(
-            updater.get_expectations(
-                SimpleTestResult('FAIL', 'TIMEOUT', 'bug')), {'Timeout'})
+            updater.get_expectations(SimpleTestResult('FAIL', 'TIMEOUT',
+                                                      'bug')), {'TIMEOUT'})
         self.assertEqual(
             updater.get_expectations(
                 SimpleTestResult('FAIL', 'TIMEOUT TIMEOUT', 'bug')),
-            {'Timeout'})
+            {'TIMEOUT'})
+        self.assertEqual(
+            updater.get_expectations(SimpleTestResult('TIMEOUT', 'PASS',
+                                                      'bug')), {'PASS'})
         self.assertEqual(
             updater.get_expectations(
-                SimpleTestResult('TIMEOUT', 'PASS', 'bug')), {'Pass'})
-        self.assertEqual(
-            updater.get_expectations(
-                SimpleTestResult('TIMEOUT', 'PASS PASS', 'bug')), {'Pass'})
+                SimpleTestResult('TIMEOUT', 'PASS PASS', 'bug')), {'PASS'})
         self.assertEqual(
             updater.get_expectations(
                 SimpleTestResult('PASS', 'TEXT PASS', 'bug')),
-            {'Pass', 'Failure'})
+            {'PASS', 'FAIL'})
         self.assertEqual(
             updater.get_expectations(
                 SimpleTestResult('PASS', 'TIMEOUT CRASH TEXT', 'bug')),
-            {'Crash', 'Failure', 'Timeout'})
+            {'CRASH', 'FAIL', 'TIMEOUT'})
         self.assertEqual(
             updater.get_expectations(
                 SimpleTestResult('SLOW CRASH FAIL TIMEOUT', 'PASS', 'bug')),
-            {'Pass'})
+            {'PASS'})
         self.assertEqual(
             updater.get_expectations(
                 SimpleTestResult('PASS', 'IMAGE+TEXT IMAGE IMAGE', 'bug')),
-            {'Failure'})
+            {'FAIL'})
+        self.assertEqual(
+            updater.get_expectations(SimpleTestResult('PASS', 'MISSING',
+                                                      'bug')), {'SKIP'})
         self.assertEqual(
             updater.get_expectations(
-                SimpleTestResult('PASS', 'MISSING', 'bug')), {'Skip'})
+                SimpleTestResult('PASS', 'MISSING MISSING', 'bug')), {'SKIP'})
         self.assertEqual(
-            updater.get_expectations(
-                SimpleTestResult('PASS', 'MISSING MISSING', 'bug')), {'Skip'})
-        self.assertEqual(
-            updater.get_expectations(
-                SimpleTestResult('PASS', 'FAIL', 'bug'),
-                test_name='external/wpt/webdriver/foo/a'), {'Failure'})
+            updater.get_expectations(SimpleTestResult('PASS', 'FAIL', 'bug'),
+                                     test_name='external/wpt/webdriver/foo/a'),
+            {'FAIL'})
 
     def test_remove_configurations(self):
         host = self.mock_host()
 
-        initial_expectations = (
-            '# tags: [ Android Fuchsia Linux Mac Mac10.12 Mac10.15 Mac11 Win Win7 Win10 ]\n'
-            + '# results: [ Timeout Crash Pass Failure Skip ]\n' +
-            'crbug.com/1234 test/foo.html [ Failure ]\n' +
-            'crbug.com/1235 [ Win ] test/bar.html [ Timeout ]\n')
+        initial_expectations = textwrap.dedent("""\
+            # tags: [ Android Fuchsia Linux Mac Mac10.12 Mac10.15 Mac11 Win Win7 Win10 ]
+            # results: [ Timeout Crash Pass Failure Skip ]
+            crbug.com/1234 external/wpt/test/foo.html [ Failure ]
+            crbug.com/1235 [ Win ] external/wpt/test/bar.html [ Timeout ]
+            """)
 
-        final_expectations = (
-            '# tags: [ Android Fuchsia Linux Mac Mac10.12 Mac10.15 Mac11 Win Win7 Win10 ]\n'
-            + '# results: [ Timeout Crash Pass Failure Skip ]\n' +
-            'crbug.com/1234 [ Linux ] test/foo.html [ Failure ]\n' +
-            'crbug.com/1234 [ Mac ] test/foo.html [ Failure ]\n' +
-            'crbug.com/1234 [ Win10 ] test/foo.html [ Failure ]\n' +
-            'crbug.com/1235 [ Win10 ] test/bar.html [ Timeout ]\n')
+        final_expectations = textwrap.dedent("""\
+            # tags: [ Android Fuchsia Linux Mac Mac10.12 Mac10.15 Mac11 Win Win7 Win10 ]
+            # results: [ Timeout Crash Pass Failure Skip ]
+            crbug.com/1234 [ Linux ] external/wpt/test/foo.html [ Failure ]
+            crbug.com/1234 [ Mac ] external/wpt/test/foo.html [ Failure ]
+            crbug.com/1234 [ Win10 ] external/wpt/test/foo.html [ Failure ]
+            crbug.com/1235 [ Win10 ] external/wpt/test/bar.html [ Timeout ]
+
+            # ====== New tests from wpt-importer added here ======
+            crbug.com/123 [ Win7 ] external/wpt/test/bar.html [ Failure Timeout ]
+            crbug.com/123 [ Win7 ] external/wpt/test/foo.html [ Failure Timeout ]
+            """)
 
         # Fill in an initial value for TestExpectations
         expectations_path = \
@@ -524,11 +555,20 @@
         host.filesystem.write_text_file(expectations_path, initial_expectations)
 
         updater = WPTExpectationsUpdater(host)
-        configs_to_remove = {
-            'test/foo.html': set(['win7']),
-            'test/bar.html': set(['win7'])
-        }
-        updater.remove_configurations(configs_to_remove)
+        updater.write_to_test_expectations({
+            'external/wpt/test/foo.html': {
+                (DesktopConfig(port_name='test-win-win7'), ):
+                SimpleTestResult(expected='FAIL',
+                                 actual='FAIL TIMEOUT',
+                                 bug='crbug.com/123'),
+            },
+            'external/wpt/test/bar.html': {
+                (DesktopConfig(port_name='test-win-win7'), ):
+                SimpleTestResult(expected='TIMEOUT',
+                                 actual='FAIL TIMEOUT',
+                                 bug='crbug.com/123'),
+            },
+        })
 
         value = host.filesystem.read_text_file(expectations_path)
         self.assertMultiLineEqual(value, final_expectations)
@@ -550,52 +590,71 @@
         host.filesystem.write_text_file(expectations_path, content)
 
         updater = WPTExpectationsUpdater(host)
-
         results = {
             'external/wpt/reftest.html': {
-                tuple([DesktopConfig(port_name='one')]):
-                SimpleTestResult(
-                    expected='FAIL', actual='FAIL', bug='crbug.com/test'),
+                tuple([DesktopConfig(port_name='test-linux-trusty')]):
+                SimpleTestResult(expected='FAIL',
+                                 actual='FAIL',
+                                 bug='crbug.com/123'),
             },
             'external/wpt/test/path.html': {
-                tuple([DesktopConfig(port_name='one')]):
-                SimpleTestResult(
-                    expected='FAIL', actual='CRASH', bug='crbug.com/test'),
+                tuple([DesktopConfig(port_name='test-linux-trusty')]):
+                SimpleTestResult(expected='FAIL',
+                                 actual='CRASH',
+                                 bug='crbug.com/123'),
             },
             'external/wpt/test/zzzz.html': {
-                tuple([DesktopConfig(port_name='one')]):
-                SimpleTestResult(
-                    expected='PASS', actual='CRASH', bug='crbug.com/test'),
+                tuple([DesktopConfig(port_name='test-linux-trusty')]):
+                SimpleTestResult(expected='PASS',
+                                 actual='CRASH',
+                                 bug='crbug.com/123'),
             }
         }
-        generic_expectations = TestExpectations(port)
-        line_dict, configs_to_remove = updater.create_line_dict_for_flag_specific(
-            results,
-            generic_expectations)
+
+        updater.write_to_test_expectations(results, 'fake-flag')
+        port.set_option_default('flag_specific', 'fake-flag')
+        expectations = TestExpectations(port)
         self.assertEqual(
-            line_dict, {
-                'external/wpt/test/path.html': [
-                    'crbug.com/test external/wpt/test/path.html [ Crash ]'],
-                'external/wpt/test/zzzz.html': [
-                    'crbug.com/test external/wpt/test/zzzz.html [ Crash ]']})
-        self.assertEqual(configs_to_remove, {})
+            expectations.get_expectations_from_file(
+                expectations_path, 'external/wpt/test/zzzz.html'), [])
+
+        expectations_path = port.path_to_flag_specific_expectations_file(
+            'fake-flag')
+        self.assertEqual(
+            expectations.get_expectations_from_file(
+                expectations_path, 'external/wpt/reftest.html'), [])
+        (line, ) = expectations.get_expectations_from_file(
+            expectations_path, 'external/wpt/test/path.html')
+        self.assertEqual(line.reason, 'crbug.com/123')
+        self.assertEqual(line.tags, set())
+        self.assertEqual(line.results, {ResultType.Crash})
+        (line, ) = expectations.get_expectations_from_file(
+            expectations_path, 'external/wpt/test/zzzz.html')
+        self.assertEqual(line.reason, 'crbug.com/123')
+        self.assertEqual(line.tags, set())
+        self.assertEqual(line.results, {ResultType.Crash})
 
     def test_create_line_dict_old_tests(self):
         # In this example, there are two failures that are not in wpt.
         updater = WPTExpectationsUpdater(self.mock_host())
         results = {
             'fake/test/path.html': {
-                tuple([DesktopConfig(port_name='one')]):
-                SimpleTestResult(
-                    expected='FAIL', actual='PASS', bug='crbug.com/test'),
+                tuple([DesktopConfig(port_name='test-mac-mac10.10')]):
+                SimpleTestResult(expected='FAIL',
+                                 actual='PASS',
+                                 bug='crbug.com/123'),
                 tuple([DesktopConfig(port_name='two')]):
-                SimpleTestResult(
-                    expected='FAIL', actual='PASS', bug='crbug.com/test'),
+                SimpleTestResult(expected='FAIL',
+                                 actual='PASS',
+                                 bug='crbug.com/123'),
             }
         }
-        line_dict, configs_to_remove = updater.create_line_dict(results)
-        self.assertEqual(line_dict, {})
-        self.assertEqual(configs_to_remove, {})
+        updater.write_to_test_expectations(results)
+        expectations = TestExpectations(updater.port)
+        path = updater.port.path_to_generic_test_expectations_file()
+        self.assertEqual(
+            expectations.get_expectations_from_file(path,
+                                                    'fake/test/path.html'), [])
 
     def test_create_line_dict_new_tests(self):
         # In this example, there are three unexpected results for wpt tests.
@@ -604,39 +663,45 @@
         results = {
             'external/wpt/test/zzzz.html': {
                 tuple([DesktopConfig(port_name='test-mac-mac10.10')]):
-                SimpleTestResult(
-                    expected='PASS', actual='TEXT', bug='crbug.com/test'),
+                SimpleTestResult(expected='PASS',
+                                 actual='TEXT',
+                                 bug='crbug.com/123'),
             },
             'virtual/foo/external/wpt/test/zzzz.html': {
                 tuple([DesktopConfig(port_name='test-linux-trusty')]):
-                SimpleTestResult(
-                    expected='FAIL', actual='PASS', bug='crbug.com/test'),
+                SimpleTestResult(expected='FAIL',
+                                 actual='PASS',
+                                 bug='crbug.com/123'),
                 tuple([DesktopConfig(port_name='test-mac-mac10.11')]):
-                SimpleTestResult(
-                    expected='FAIL', actual='TIMEOUT', bug='crbug.com/test'),
+                SimpleTestResult(expected='FAIL',
+                                 actual='TIMEOUT',
+                                 bug='crbug.com/123'),
             },
             'unrelated/test.html': {
                 tuple([DesktopConfig(port_name='test-linux-trusty')]):
-                SimpleTestResult(
-                    expected='FAIL', actual='PASS', bug='crbug.com/test'),
+                SimpleTestResult(expected='FAIL',
+                                 actual='PASS',
+                                 bug='crbug.com/123'),
             },
         }
-        line_dict, configs_to_remove = updater.create_line_dict(results)
-        self.assertEqual(
-            line_dict, {
-                'external/wpt/test/zzzz.html': [
-                    'crbug.com/test [ Mac10.10 ] external/wpt/test/zzzz.html [ Failure ]'
-                ],
-                'virtual/foo/external/wpt/test/zzzz.html': [
-                    'crbug.com/test [ Trusty ] virtual/foo/external/wpt/test/zzzz.html [ Pass ]',
-                    'crbug.com/test [ Mac10.11 ] virtual/foo/external/wpt/test/zzzz.html [ Timeout ]',
-                ],
-            })
-        self.assertEqual(
-            configs_to_remove, {
-                'external/wpt/test/zzzz.html': set(['Mac10.10']),
-                'virtual/foo/external/wpt/test/zzzz.html': set(['Trusty', 'Mac10.11'])
-            })
+        updater.write_to_test_expectations(results)
+        expectations = TestExpectations(updater.port)
+        path = updater.port.path_to_generic_test_expectations_file()
+        (line, ) = expectations.get_expectations_from_file(
+            path, 'external/wpt/test/zzzz.html')
+        self.assertEqual(line.reason, 'crbug.com/123')
+        self.assertEqual(line.tags, {'mac10.10'})
+        self.assertEqual(line.results, {ResultType.Failure})
+        lines = expectations.get_expectations_from_file(
+            path, 'virtual/foo/external/wpt/test/zzzz.html')
+        self.assertEqual(len(lines), 2)
+        line1, line2 = sorted(lines, key=lambda line: line.tags)
+        self.assertEqual(line1.reason, 'crbug.com/123')
+        self.assertEqual(line1.tags, {'trusty'})
+        self.assertEqual(line1.results, {ResultType.Pass})
+        self.assertEqual(line2.reason, 'crbug.com/123')
+        self.assertEqual(line2.tags, {'mac10.11'})
+        self.assertEqual(line2.results, {ResultType.Timeout})
 
     def test_create_line_dict_with_asterisks(self):
         # Literal asterisks in test names need to be escaped in expectations.
@@ -645,31 +710,50 @@
             'external/wpt/html/dom/interfaces.https.html?exclude=(Document.*|HTML.*)':
             {
                 tuple([DesktopConfig(port_name='test-linux-trusty')]):
-                SimpleTestResult(
-                    expected='PASS', actual='FAIL', bug='crbug.com/test'),
+                SimpleTestResult(expected='PASS',
+                                 actual='FAIL',
+                                 bug='crbug.com/123'),
             },
         }
-        line_dict, _ = updater.create_line_dict(results)
+        line_dict = updater.write_to_test_expectations(results)
         self.assertEqual(
             line_dict, {
                 'external/wpt/html/dom/interfaces.https.html?exclude=(Document.*|HTML.*)':
                 [
-                    'crbug.com/test [ Trusty ] external/wpt/html/dom/interfaces.https.html?exclude=(Document.\*|HTML.\*) [ Failure ]',
+                    'crbug.com/123 [ Trusty ] external/wpt/html/dom/'
+                    'interfaces.https.html?exclude=(Document.\*|HTML.\*) '
+                    '[ Failure ]',
                 ],
             })
 
-    def test_normalized_specifiers(self):
+    def test_unsimplifiable_specifiers(self):
         updater = WPTExpectationsUpdater(self.mock_host())
-        self.assertEqual(
-            updater.normalized_specifiers('x/y.html', [DesktopConfig(port_name='test-mac-mac10.10')]),
-            ['Mac10.10'])
-        self.assertEqual(updater.normalized_specifiers('x/y.html', []), [''])
+        updater.write_to_test_expectations({
+            'external/wpt/x/y.html': {
+                (DesktopConfig(port_name='test-mac-mac10.10'), ):
+                SimpleTestResult(expected='PASS',
+                                 actual='FAIL',
+                                 bug='crbug.com/123'),
+            },
+            'external/wpt/x/z.html': {
+                (
+                    DesktopConfig(port_name='test-mac-mac10.10'),
+                    DesktopConfig(port_name='test-win-win7'),
+                ):
+                SimpleTestResult(expected='PASS',
+                                 actual='FAIL',
+                                 bug='crbug.com/123'),
+            },
+        })
 
-        self.assertEqual(
-            updater.normalized_specifiers(
-                'x/y.html', [DesktopConfig(port_name='test-mac-mac10.10'),
-                             DesktopConfig(port_name='test-win-win7')]),
-            ['Mac10.10', 'Win7'])
+        expectations = TestExpectations(updater.port)
+        path = updater.port.path_to_generic_test_expectations_file()
+        (line, ) = expectations.get_expectations_from_file(
+            path, 'external/wpt/x/y.html')
+        self.assertEqual(line.tags, {'mac10.10'})
+        line1, line2 = expectations.get_expectations_from_file(
+            path, 'external/wpt/x/z.html')
+        self.assertEqual(line1.tags | line2.tags, {'win7', 'mac10.10'})
 
     def test_skipped_specifiers_when_test_is_skip(self):
         host = self.mock_host()
@@ -696,65 +780,42 @@
         host.filesystem.write_text_file(
             MOCK_WEB_TESTS + 'external/wpt/test.html', '')
         updater = WPTExpectationsUpdater(host)
-        self.assertTrue(
-            updater.specifiers_can_extend_to_all_platforms(
-                ['Mac10.10', 'Mac10.11', 'Win7', 'Win10'],
-                'external/wpt/test.html'))
-        self.assertFalse(
-            updater.specifiers_can_extend_to_all_platforms(
-                ['Mac10.10', 'Win7', 'Win10'], 'external/wpt/test.html'))
 
-    def test_simplify_specifiers(self):
-        host = self.mock_host()
-        updater = WPTExpectationsUpdater(host)
-        macros = {
-            'mac': ['Mac10.10', 'mac10.11'],
-            'win': ['Win7', 'win10'],
-            'Linux': ['Trusty'],
-        }
-        self.assertEqual(
-            updater.simplify_specifiers(['mac10.10', 'mac10.11'], macros),
-            ['Mac'])
-        self.assertEqual(
-            updater.simplify_specifiers(['Mac10.10', 'Mac10.11', 'Trusty'],
-                                        macros), ['Linux', 'Mac'])
-        self.assertEqual(
-            updater.simplify_specifiers(
-                ['Mac10.10', 'Mac10.11', 'Trusty', 'Win7', 'Win10'], macros),
-            [])
-        self.assertEqual(
-            updater.simplify_specifiers(['Mac', 'Win', 'Linux'], macros), [])
+        updater.write_to_test_expectations({
+            'external/wpt/test.html': {
+                (
+                    DesktopConfig('test-mac-mac10.10'),
+                    DesktopConfig('test-win-win7'),
+                    DesktopConfig('test-win-win10'),
+                ):
+                SimpleTestResult(expected='PASS',
+                                 actual='FAIL',
+                                 bug='crbug.com/123'),
+            },
+        })
+        expectations = TestExpectations(updater.port)
+        path = updater.port.path_to_generic_test_expectations_file()
+        line1, line2 = expectations.get_expectations_from_file(
+            path, 'external/wpt/test.html')
+        self.assertEqual(line1.tags | line2.tags, {'mac10.10', 'win'})
 
-    def test_simplify_specifiers_uses_specifiers_in_builder_list(self):
-        # Even if there are extra specifiers in the macro dictionary, we can simplify specifier
-        # lists if they contain all of the specifiers the are represented in the builder list.
-        # This way specifier simplification can still be done while a new platform is being added.
-        host = self.mock_host()
-        updater = WPTExpectationsUpdater(host)
-        macros = {
-            'mac': ['Mac10.10', 'mac10.11', 'mac10.14'],
-            'win': ['Win7', 'win10'],
-            'Linux': ['Trusty'],
-        }
-        self.assertEqual(
-            updater.simplify_specifiers(['mac10.10', 'mac10.11'], macros),
-            ['Mac'])
-        self.assertEqual(
-            updater.simplify_specifiers(
-                ['Mac10.10', 'Mac10.11', 'Trusty', 'Win7', 'Win10'], macros),
-            [])
-
-    def test_simplify_specifiers_port_not_tested_by_trybots(self):
-        host = self.mock_host()
-        updater = WPTExpectationsUpdater(host)
-        macros = {
-            'mac': ['Mac10.10', 'mac10.11'],
-            'win': ['win10'],
-            'foo': ['bar'],
-        }
-        self.assertEqual(
-            updater.simplify_specifiers(['mac10.10', 'mac10.11'], macros),
-            ['Mac'])
+        updater.write_to_test_expectations({
+            'external/wpt/test.html': {
+                (
+                    DesktopConfig('test-mac-mac10.10'),
+                    DesktopConfig('test-mac-mac10.11'),
+                    DesktopConfig('test-win-win7'),
+                    DesktopConfig('test-win-win10'),
+                ):
+                SimpleTestResult(expected='PASS',
+                                 actual='FAIL',
+                                 bug='crbug.com/123'),
+            },
+        })
+        expectations = TestExpectations(updater.port)
+        (line, ) = expectations.get_expectations_from_file(
+            path, 'external/wpt/test.html')
+        self.assertEqual(line.tags, set())
 
     def test_normalized_specifiers_with_skipped_test(self):
         host = self.mock_host()
@@ -768,23 +829,36 @@
         host.filesystem.write_text_file(
             MOCK_WEB_TESTS + 'external/wpt/test.html', '')
         updater = WPTExpectationsUpdater(host)
-        self.assertEqual(
-            updater.normalized_specifiers(
-                'external/wpt/test.html',
-                [DesktopConfig(port_name='test-mac-mac10.10'),
-                 DesktopConfig(port_name='test-win-win7'),
-                 DesktopConfig(port_name='test-win-win10')]),
-            [''])
-        self.assertEqual(
-            updater.normalized_specifiers('external/wpt/test.html',
-                                          [DesktopConfig(port_name='test-win-win7'),
-                                           DesktopConfig(port_name='test-win-win10')]),
-            ['Win'])
-        self.assertEqual(
-            updater.normalized_specifiers('external/wpt/another.html',
-                                          [DesktopConfig('test-win-win7'),
-                                           DesktopConfig('test-win-win10')]),
-            ['Win'])
+        updater.write_to_test_expectations({
+            'external/wpt/test.html': {
+                (
+                    DesktopConfig(port_name='test-win-win7'),
+                    DesktopConfig(port_name='test-win-win10'),
+                    DesktopConfig(port_name='test-mac-mac10.10'),
+                ):
+                SimpleTestResult(expected='PASS',
+                                 actual='FAIL',
+                                 bug='crbug.com/123'),
+            },
+            'external/wpt/another.html': {
+                (
+                    DesktopConfig(port_name='test-win-win7'),
+                    DesktopConfig(port_name='test-win-win10'),
+                ):
+                SimpleTestResult(expected='PASS',
+                                 actual='FAIL',
+                                 bug='crbug.com/123'),
+            },
+        })
+
+        expectations = TestExpectations(updater.port)
+        path = updater.port.path_to_generic_test_expectations_file()
+        (line, ) = expectations.get_expectations_from_file(
+            path, 'external/wpt/test.html')
+        self.assertEqual(line.tags, set())
+        (line, ) = expectations.get_expectations_from_file(
+            path, 'external/wpt/another.html')
+        self.assertEqual(line.tags, {'win'})
 
     def test_merge_dicts_with_differing_status_is_merged(self):
         updater = WPTExpectationsUpdater(self.mock_host())
@@ -892,9 +966,7 @@
         exp_dict = updater.write_to_test_expectations(test_expectations)
         self.assertEqual(exp_dict, {})
         logs = ''.join(self.logMessages()).lower()
-        self.assertIn(
-            'no lines to write to testexpectations or webdriverexpectations.',
-            logs)
+        self.assertIn('no lines to write to testexpectations.', logs)
 
     def test_cleanup_outside_affected_expectations_in_cl(self):
         host = self.mock_host()
@@ -981,10 +1053,14 @@
         updater.write_to_test_expectations(test_expectations)
         value = host.filesystem.read_text_file(expectations_path)
         self.assertMultiLineEqual(
-            value, ('# tags: [ Win Linux ]\n' +
-                    '# results: [ Pass Failure ]\n\n' +
-                    WPTExpectationsUpdater.MARKER_COMMENT + '\n' +
-                    'crbug.com/123 [ Trusty ] external/wpt/fake/file/path.html [ Pass ]'))
+            value,
+            textwrap.dedent("""\
+                # tags: [ Win Linux ]
+                # results: [ Pass Failure ]
+
+                # ====== New tests from wpt-importer added here ======
+                crbug.com/123 [ Trusty ] external/wpt/fake/file/path.html [ Pass ]
+                """))
         skip_value = host.filesystem.read_text_file(skip_path)
         self.assertMultiLineEqual(skip_value, skip_value_origin)
 
@@ -1068,6 +1144,9 @@
         skip_value = host.filesystem.read_text_file(skip_path)
         self.assertMultiLineEqual(skip_value, skip_value_origin)
 
+    @unittest.skip(
+        "The webdriver test runner doesn't upload results to ResultDB; "
+        'see crbug.com/1414565')
     def test_write_to_test_expectations_with_webdriver_lines(self):
         host = self.mock_host()
 
@@ -1122,12 +1201,17 @@
 
         updater.write_to_test_expectations(test_expectations)
         value = host.filesystem.read_text_file(expectations_path)
-
         self.assertMultiLineEqual(
-            value, (raw_exps + '\n' +
-                    'crbug.com/111 [ Trusty ] foo/bar.html [ Failure ]\n'
-                    '\n' + WPTExpectationsUpdater.MARKER_COMMENT + '\n'
-                    'crbug.com/123 [ Trusty ] external/wpt/fake/file/path.html [ Pass ]'))
+            value,
+            textwrap.dedent("""\
+                # tags: [ Trusty ]
+                # results: [ Pass Failure ]
+
+                crbug.com/111 [ Trusty ] foo/bar.html [ Failure ]
+
+                # ====== New tests from wpt-importer added here ======
+                crbug.com/123 [ Trusty ] external/wpt/fake/file/path.html [ Pass ]
+                """))
         skip_value = host.filesystem.read_text_file(skip_path)
         self.assertMultiLineEqual(skip_value, skip_value_origin)
 
@@ -1190,14 +1274,17 @@
         two = {
             'external/wpt/reftest.html': {
                 'one':
-                SimpleTestResult(
-                    expected='FAIL', actual='PASS', bug='crbug.com/test'),
+                SimpleTestResult(expected='FAIL',
+                                 actual='PASS',
+                                 bug='crbug.com/123'),
                 'two':
-                SimpleTestResult(
-                    expected='FAIL', actual='TIMEOUT', bug='crbug.com/test'),
+                SimpleTestResult(expected='FAIL',
+                                 actual='TIMEOUT',
+                                 bug='crbug.com/123'),
                 'three':
-                SimpleTestResult(
-                    expected='FAIL', actual='PASS', bug='crbug.com/test'),
+                SimpleTestResult(expected='FAIL',
+                                 actual='PASS',
+                                 bug='crbug.com/123'),
             }
         }
         tests_to_rebaseline, _ = updater.get_tests_to_rebaseline(two)
@@ -1308,13 +1395,12 @@
                     DesktopConfig('test-win-win7'),
                     DesktopConfig('test-win-win10'),
                 ):
-                SimpleTestResult(
-                    expected='PASS', actual='MISSING', bug='crbug.com/test')
+                SimpleTestResult(expected='PASS', actual='MISSING', bug='')
             }
         }
         tests_to_rebaseline, _ = updater.get_tests_to_rebaseline(results)
         self.assertEqual(tests_to_rebaseline, [])
-        line_dict, _ = updater.create_line_dict(results)
+        line_dict = updater.write_to_test_expectations(results)
         self.assertEqual(
             line_dict, {
                 'external/wpt/x-manual.html':
@@ -1337,19 +1423,16 @@
                 ):
                 SimpleTestResult(expected='PASS',
                                  actual='TEXT',
-                                 bug='crbug.com/test')
+                                 bug='crbug.com/123')
             }
         }
-        updater.configs_with_no_results = [
-            DesktopConfig(port_name='test-mac-mac10.10')
-        ]
-        line_dict, _ = updater.create_line_dict(results)
+        line_dict = updater.write_to_test_expectations(results)
         self.assertEqual(
             line_dict, {
                 'external/wpt/x.html': [
-                    'crbug.com/test [ Linux ] external/wpt/x.html [ Failure ]',
-                    'crbug.com/test [ Mac10.10 ] external/wpt/x.html [ Failure ]',
-                ]
+                    'crbug.com/123 [ Linux ] external/wpt/x.html [ Failure ]',
+                    'crbug.com/123 [ Mac10.10 ] external/wpt/x.html [ Failure ]',
+                ],
             })
 
     def test_cleanup_all_deleted_tests_in_expectations_files(self):
@@ -1482,18 +1565,19 @@
                     DesktopConfig('test-mac-mac10.11'),
                     DesktopConfig('test-win-win7'),
                 ):
-                SimpleTestResult(
-                    expected='PASS', actual='TEXT', bug='crbug.com/test')
+                SimpleTestResult(expected='PASS',
+                                 actual='TEXT',
+                                 bug='crbug.com/123')
             }
         }
-        line_dict, _ = updater.create_line_dict(results)
+        line_dict = updater.write_to_test_expectations(results)
         self.assertEqual(
             line_dict, {
                 'external/wpt/x.html': [
-                    'crbug.com/test [ Linux ] external/wpt/x.html [ Failure ]',
-                    'crbug.com/test [ Mac ] external/wpt/x.html [ Failure ]',
-                    'crbug.com/test [ Win7 ] external/wpt/x.html [ Failure ]',
-                ]
+                    'crbug.com/123 [ Linux ] external/wpt/x.html [ Failure ]',
+                    'crbug.com/123 [ Mac ] external/wpt/x.html [ Failure ]',
+                    'crbug.com/123 [ Win7 ] external/wpt/x.html [ Failure ]',
+                ],
             })
 
     def test_inheriting_results(self):
diff --git a/third_party/blink/web_tests/TestExpectations b/third_party/blink/web_tests/TestExpectations
index 8c0ab61d..536ec87 100644
--- a/third_party/blink/web_tests/TestExpectations
+++ b/third_party/blink/web_tests/TestExpectations
@@ -4097,7 +4097,7 @@
 crbug.com/978966 [ Mac ] paint/markers/ellipsis-mixed-text-in-ltr-flow-with-markers.html [ Failure Pass ]
 crbug.com/1451329 [ Mac11-arm64 ] paint/markers/marker-invalidation.html [ Failure Pass ]
 crbug.com/1451329 [ Mac12-arm64 ] paint/markers/marker-invalidation.html [ Failure Pass ]
-crbug.com/1451329 [ Mac13-arm64 ] paint/markers/marker-invalidation.html [ Failure Pass ]
+crbug.com/1451329 [ Mac13 ] paint/markers/marker-invalidation.html [ Failure Pass ]
 
 # Sheriff 2019-06-27
 crbug.com/979243 [ Mac10.14 Release ] editing/selection/inline-closest-leaf-child.html [ Failure Pass ]
diff --git a/third_party/blink/web_tests/css3/filters/effect-reference-zoom-expected.png b/third_party/blink/web_tests/css3/filters/effect-reference-zoom-expected.png
index c812f9f..1dfb5b5 100644
--- a/third_party/blink/web_tests/css3/filters/effect-reference-zoom-expected.png
+++ b/third_party/blink/web_tests/css3/filters/effect-reference-zoom-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/css3/filters/effect-reference-zoom-hw-expected.png b/third_party/blink/web_tests/css3/filters/effect-reference-zoom-hw-expected.png
index eeb8924..d60b693 100644
--- a/third_party/blink/web_tests/css3/filters/effect-reference-zoom-hw-expected.png
+++ b/third_party/blink/web_tests/css3/filters/effect-reference-zoom-hw-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/external/wpt/accessibility/crashtests/append-image-using-illegal-map.html b/third_party/blink/web_tests/external/wpt/accessibility/crashtests/append-image-using-illegal-map.html
new file mode 100644
index 0000000..c78e74ee
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/accessibility/crashtests/append-image-using-illegal-map.html
@@ -0,0 +1,8 @@
+<!DOCTYPE html>
+<map name="map1"><button><li>xyz</li></button></map>
+<script>
+ const image = document.createElement('img');
+ image.setAttribute('src', 'exists.gif');
+ image.setAttribute('usemap', '#map1');
+ document.documentElement.appendChild(image);
+</script>
diff --git a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.fillStyle.CSSRGB.html b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.fillStyle.CSSRGB.html
new file mode 100644
index 0000000..1670d64
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.fillStyle.CSSRGB.html
@@ -0,0 +1,60 @@
+<!DOCTYPE html>
+<!-- DO NOT EDIT! This test has been generated by /html/canvas/tools/gentest.py. -->
+<title>OffscreenCanvas test: 2d.fillStyle.CSSRGB</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/html/canvas/resources/canvas-tests.js"></script>
+
+<h1>2d.fillStyle.CSSRGB</h1>
+<p class="desc">CSSRGB works as color input</p>
+
+
+<script>
+var t = async_test("CSSRGB works as color input");
+var t_pass = t.done.bind(t);
+var t_fail = t.step_func(function(reason) {
+    throw reason;
+});
+t.step(function() {
+
+  var canvas = new OffscreenCanvas(100, 50);
+  var ctx = canvas.getContext('2d');
+
+  ctx.fillStyle = new CSSRGB(1, 0, 1);
+  _assertSame(ctx.fillStyle, '#ff00ff', "ctx.fillStyle", "'#ff00ff'");
+  ctx.fillRect(0, 0, 100, 50);
+  _assertPixel(canvas, 50,25, 255,0,255,255);
+
+  const color = new CSSRGB(0, CSS.percent(50), 0);
+  ctx.fillStyle = color;
+  _assertSame(ctx.fillStyle, '#008000', "ctx.fillStyle", "'#008000'");
+  ctx.fillRect(0, 0, 100, 50);
+  _assertPixel(canvas, 50,25, 0,128,0,255);
+  color.g = 0;
+  ctx.fillStyle = color;
+  _assertSame(ctx.fillStyle, '#000000', "ctx.fillStyle", "'#000000'");
+  ctx.fillRect(0, 0, 100, 50);
+  _assertPixel(canvas, 50,25, 0,0,0,255);
+
+  color.alpha = 0;
+  ctx.fillStyle = color;
+  _assertSame(ctx.fillStyle, 'rgba(0, 0, 0, 0)', "ctx.fillStyle", "'rgba(0, 0, 0, 0)'");
+  ctx.reset();
+  color.alpha = 0.5;
+  ctx.fillStyle = color;
+  ctx.fillRect(0, 0, 100, 50);
+  _assertPixel(canvas, 50,25, 0,0,0,128);
+
+  ctx.fillStyle = new CSSHSL(CSS.deg(0), 1, 1).toRGB();
+  _assertSame(ctx.fillStyle, '#ffffff', "ctx.fillStyle", "'#ffffff'");
+  ctx.fillRect(0, 0, 100, 50);
+  _assertPixel(canvas, 50,25, 255,255,255,255);
+
+  color.alpha = 1;
+  color.g = 1;
+  ctx.fillStyle = color;
+  ctx.fillRect(0, 0, 100, 50);
+  t.done();
+
+});
+</script>
diff --git a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.gradient.interpolate.alpha.png b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.gradient.interpolate.alpha.png
new file mode 100644
index 0000000..af5ac0f
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.gradient.interpolate.alpha.png
Binary files differ
diff --git a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.gradient.interpolate.color.png b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.gradient.interpolate.color.png
new file mode 100644
index 0000000..af5ac0f
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.gradient.interpolate.color.png
Binary files differ
diff --git a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.gradient.interpolate.coloralpha.png b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.gradient.interpolate.coloralpha.png
new file mode 100644
index 0000000..552e6ee
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.gradient.interpolate.coloralpha.png
Binary files differ
diff --git a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.gradient.interpolate.multiple.png b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.gradient.interpolate.multiple.png
new file mode 100644
index 0000000..8612245
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.gradient.interpolate.multiple.png
Binary files differ
diff --git a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.gradient.interpolate.outside.html b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.gradient.interpolate.outside.html
index 0dda6c0c..02ca008 100644
--- a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.gradient.interpolate.outside.html
+++ b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.gradient.interpolate.outside.html
@@ -22,9 +22,11 @@
 
   ctx.fillStyle = '#f00';
   ctx.fillRect(0, 0, 100, 50);
+
   var g = ctx.createLinearGradient(25, 0, 75, 0);
   g.addColorStop(0.4, '#0f0');
   g.addColorStop(0.6, '#0f0');
+
   ctx.fillStyle = g;
   ctx.fillRect(0, 0, 100, 50);
   _assertPixelApprox(canvas, 20,25, 0,255,0,255, 2);
diff --git a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.gradient.interpolate.outside.worker.js b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.gradient.interpolate.outside.worker.js
index 66fc180..d6c8961 100644
--- a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.gradient.interpolate.outside.worker.js
+++ b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.gradient.interpolate.outside.worker.js
@@ -18,9 +18,11 @@
 
   ctx.fillStyle = '#f00';
   ctx.fillRect(0, 0, 100, 50);
+
   var g = ctx.createLinearGradient(25, 0, 75, 0);
   g.addColorStop(0.4, '#0f0');
   g.addColorStop(0.6, '#0f0');
+
   ctx.fillStyle = g;
   ctx.fillRect(0, 0, 100, 50);
   _assertPixelApprox(canvas, 20,25, 0,255,0,255, 2);
diff --git a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.gradient.interpolate.overlap.png b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.gradient.interpolate.overlap.png
new file mode 100644
index 0000000..5c2bb96
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.gradient.interpolate.overlap.png
Binary files differ
diff --git a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.gradient.interpolate.vertical.png b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.gradient.interpolate.vertical.png
new file mode 100644
index 0000000..37d6a00
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.gradient.interpolate.vertical.png
Binary files differ
diff --git a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.gradient.interpolate.zerosize.fill.html b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.gradient.interpolate.zerosize.fill.html
index 71d5cb85..27579fc 100644
--- a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.gradient.interpolate.zerosize.fill.html
+++ b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.gradient.interpolate.zerosize.fill.html
@@ -22,6 +22,7 @@
 
   ctx.fillStyle = '#0f0';
   ctx.fillRect(0, 0, 100, 50);
+
   var g = ctx.createLinearGradient(50, 25, 50, 25); // zero-length line (undefined direction)
   g.addColorStop(0, '#f00');
   g.addColorStop(1, '#f00');
diff --git a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.gradient.interpolate.zerosize.fill.worker.js b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.gradient.interpolate.zerosize.fill.worker.js
index 3b86cec..916b1cb 100644
--- a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.gradient.interpolate.zerosize.fill.worker.js
+++ b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.gradient.interpolate.zerosize.fill.worker.js
@@ -18,6 +18,7 @@
 
   ctx.fillStyle = '#0f0';
   ctx.fillRect(0, 0, 100, 50);
+
   var g = ctx.createLinearGradient(50, 25, 50, 25); // zero-length line (undefined direction)
   g.addColorStop(0, '#f00');
   g.addColorStop(1, '#f00');
diff --git a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.gradient.interpolate.zerosize.fillRect.html b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.gradient.interpolate.zerosize.fillRect.html
index 1b3851c..9cf2c735 100644
--- a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.gradient.interpolate.zerosize.fillRect.html
+++ b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.gradient.interpolate.zerosize.fillRect.html
@@ -22,6 +22,7 @@
 
   ctx.fillStyle = '#0f0';
   ctx.fillRect(0, 0, 100, 50);
+
   var g = ctx.createLinearGradient(50, 25, 50, 25); // zero-length line (undefined direction)
   g.addColorStop(0, '#f00');
   g.addColorStop(1, '#f00');
diff --git a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.gradient.interpolate.zerosize.fillRect.worker.js b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.gradient.interpolate.zerosize.fillRect.worker.js
index f6ab1d9..607ad85 100644
--- a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.gradient.interpolate.zerosize.fillRect.worker.js
+++ b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.gradient.interpolate.zerosize.fillRect.worker.js
@@ -18,6 +18,7 @@
 
   ctx.fillStyle = '#0f0';
   ctx.fillRect(0, 0, 100, 50);
+
   var g = ctx.createLinearGradient(50, 25, 50, 25); // zero-length line (undefined direction)
   g.addColorStop(0, '#f00');
   g.addColorStop(1, '#f00');
diff --git a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.gradient.interpolate.zerosize.fillText.html b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.gradient.interpolate.zerosize.fillText.html
new file mode 100644
index 0000000..59f09644
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.gradient.interpolate.zerosize.fillText.html
@@ -0,0 +1,36 @@
+<!DOCTYPE html>
+<!-- DO NOT EDIT! This test has been generated by /html/canvas/tools/gentest.py. -->
+<title>OffscreenCanvas test: 2d.gradient.interpolate.zerosize.fillText</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/html/canvas/resources/canvas-tests.js"></script>
+
+<h1>2d.gradient.interpolate.zerosize.fillText</h1>
+<p class="desc"></p>
+
+
+<script>
+var t = async_test("");
+var t_pass = t.done.bind(t);
+var t_fail = t.step_func(function(reason) {
+    throw reason;
+});
+t.step(function() {
+
+  var canvas = new OffscreenCanvas(100, 50);
+  var ctx = canvas.getContext('2d');
+
+  ctx.fillStyle = '#0f0';
+  ctx.fillRect(0, 0, 100, 50);
+
+  var g = ctx.createLinearGradient(50, 25, 50, 25); // zero-length line (undefined direction)
+  g.addColorStop(0, '#f00');
+  g.addColorStop(1, '#f00');
+  ctx.fillStyle = g;
+  ctx.font = '100px sans-serif';
+  ctx.fillText("AA", 0, 50);
+  _assertGreen(ctx, 100, 50);
+  t.done();
+
+});
+</script>
diff --git a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.gradient.interpolate.zerosize.fillText.worker.js b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.gradient.interpolate.zerosize.fillText.worker.js
new file mode 100644
index 0000000..417b564
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.gradient.interpolate.zerosize.fillText.worker.js
@@ -0,0 +1,31 @@
+// DO NOT EDIT! This test has been generated by /html/canvas/tools/gentest.py.
+// OffscreenCanvas test in a worker:2d.gradient.interpolate.zerosize.fillText
+// Description:
+// Note:
+
+importScripts("/resources/testharness.js");
+importScripts("/html/canvas/resources/canvas-tests.js");
+
+var t = async_test("");
+var t_pass = t.done.bind(t);
+var t_fail = t.step_func(function(reason) {
+    throw reason;
+});
+t.step(function() {
+
+  var canvas = new OffscreenCanvas(100, 50);
+  var ctx = canvas.getContext('2d');
+
+  ctx.fillStyle = '#0f0';
+  ctx.fillRect(0, 0, 100, 50);
+
+  var g = ctx.createLinearGradient(50, 25, 50, 25); // zero-length line (undefined direction)
+  g.addColorStop(0, '#f00');
+  g.addColorStop(1, '#f00');
+  ctx.fillStyle = g;
+  ctx.font = '100px sans-serif';
+  ctx.fillText("AA", 0, 50);
+  _assertGreen(ctx, 100, 50);
+  t.done();
+});
+done();
diff --git a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.gradient.interpolate.zerosize.stroke.html b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.gradient.interpolate.zerosize.stroke.html
index 3b66f6c..562f467 100644
--- a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.gradient.interpolate.zerosize.stroke.html
+++ b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.gradient.interpolate.zerosize.stroke.html
@@ -22,6 +22,7 @@
 
   ctx.fillStyle = '#0f0';
   ctx.fillRect(0, 0, 100, 50);
+
   var g = ctx.createLinearGradient(50, 25, 50, 25); // zero-length line (undefined direction)
   g.addColorStop(0, '#f00');
   g.addColorStop(1, '#f00');
diff --git a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.gradient.interpolate.zerosize.stroke.worker.js b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.gradient.interpolate.zerosize.stroke.worker.js
index 7e21501..b969be6 100644
--- a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.gradient.interpolate.zerosize.stroke.worker.js
+++ b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.gradient.interpolate.zerosize.stroke.worker.js
@@ -18,6 +18,7 @@
 
   ctx.fillStyle = '#0f0';
   ctx.fillRect(0, 0, 100, 50);
+
   var g = ctx.createLinearGradient(50, 25, 50, 25); // zero-length line (undefined direction)
   g.addColorStop(0, '#f00');
   g.addColorStop(1, '#f00');
diff --git a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.gradient.interpolate.zerosize.strokeRect.html b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.gradient.interpolate.zerosize.strokeRect.html
index e7fd7e78..de1e57bb 100644
--- a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.gradient.interpolate.zerosize.strokeRect.html
+++ b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.gradient.interpolate.zerosize.strokeRect.html
@@ -22,6 +22,7 @@
 
   ctx.fillStyle = '#0f0';
   ctx.fillRect(0, 0, 100, 50);
+
   var g = ctx.createLinearGradient(50, 25, 50, 25); // zero-length line (undefined direction)
   g.addColorStop(0, '#f00');
   g.addColorStop(1, '#f00');
diff --git a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.gradient.interpolate.zerosize.strokeRect.worker.js b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.gradient.interpolate.zerosize.strokeRect.worker.js
index d7344fb4e..b9884d4a 100644
--- a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.gradient.interpolate.zerosize.strokeRect.worker.js
+++ b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.gradient.interpolate.zerosize.strokeRect.worker.js
@@ -18,6 +18,7 @@
 
   ctx.fillStyle = '#0f0';
   ctx.fillRect(0, 0, 100, 50);
+
   var g = ctx.createLinearGradient(50, 25, 50, 25); // zero-length line (undefined direction)
   g.addColorStop(0, '#f00');
   g.addColorStop(1, '#f00');
diff --git a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.gradient.interpolate.zerosize.strokeText.html b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.gradient.interpolate.zerosize.strokeText.html
new file mode 100644
index 0000000..153ec102
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.gradient.interpolate.zerosize.strokeText.html
@@ -0,0 +1,36 @@
+<!DOCTYPE html>
+<!-- DO NOT EDIT! This test has been generated by /html/canvas/tools/gentest.py. -->
+<title>OffscreenCanvas test: 2d.gradient.interpolate.zerosize.strokeText</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/html/canvas/resources/canvas-tests.js"></script>
+
+<h1>2d.gradient.interpolate.zerosize.strokeText</h1>
+<p class="desc"></p>
+
+
+<script>
+var t = async_test("");
+var t_pass = t.done.bind(t);
+var t_fail = t.step_func(function(reason) {
+    throw reason;
+});
+t.step(function() {
+
+  var canvas = new OffscreenCanvas(100, 50);
+  var ctx = canvas.getContext('2d');
+
+  ctx.fillStyle = '#0f0';
+  ctx.fillRect(0, 0, 100, 50);
+
+  var g = ctx.createLinearGradient(50, 25, 50, 25); // zero-length line (undefined direction)
+  g.addColorStop(0, '#f00');
+  g.addColorStop(1, '#f00');
+  ctx.strokeStyle = g;
+  ctx.font = '100px sans-serif';
+  ctx.strokeText("AA", 0, 50);
+  _assertGreen(ctx, 100, 50);
+  t.done();
+
+});
+</script>
diff --git a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.gradient.interpolate.zerosize.strokeText.worker.js b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.gradient.interpolate.zerosize.strokeText.worker.js
new file mode 100644
index 0000000..f0d7192
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.gradient.interpolate.zerosize.strokeText.worker.js
@@ -0,0 +1,31 @@
+// DO NOT EDIT! This test has been generated by /html/canvas/tools/gentest.py.
+// OffscreenCanvas test in a worker:2d.gradient.interpolate.zerosize.strokeText
+// Description:
+// Note:
+
+importScripts("/resources/testharness.js");
+importScripts("/html/canvas/resources/canvas-tests.js");
+
+var t = async_test("");
+var t_pass = t.done.bind(t);
+var t_fail = t.step_func(function(reason) {
+    throw reason;
+});
+t.step(function() {
+
+  var canvas = new OffscreenCanvas(100, 50);
+  var ctx = canvas.getContext('2d');
+
+  ctx.fillStyle = '#0f0';
+  ctx.fillRect(0, 0, 100, 50);
+
+  var g = ctx.createLinearGradient(50, 25, 50, 25); // zero-length line (undefined direction)
+  g.addColorStop(0, '#f00');
+  g.addColorStop(1, '#f00');
+  ctx.strokeStyle = g;
+  ctx.font = '100px sans-serif';
+  ctx.strokeText("AA", 0, 50);
+  _assertGreen(ctx, 100, 50);
+  t.done();
+});
+done();
diff --git a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.gradient.linear.nonfinite.html b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.gradient.linear.nonfinite.html
index bc10ece..6c8f8ec 100644
--- a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.gradient.linear.nonfinite.html
+++ b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.gradient.linear.nonfinite.html
@@ -8,7 +8,7 @@
 <h1>2d.gradient.linear.nonfinite</h1>
 <p class="desc">createLinearGradient() throws TypeError if arguments are not finite</p>
 
-
+<p class="notes">Defined in "Web IDL" (draft)
 <script>
 var t = async_test("createLinearGradient() throws TypeError if arguments are not finite");
 var t_pass = t.done.bind(t);
diff --git a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.gradient.linear.nonfinite.worker.js b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.gradient.linear.nonfinite.worker.js
index 87f68918..9fd4949 100644
--- a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.gradient.linear.nonfinite.worker.js
+++ b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.gradient.linear.nonfinite.worker.js
@@ -1,7 +1,7 @@
 // DO NOT EDIT! This test has been generated by /html/canvas/tools/gentest.py.
 // OffscreenCanvas test in a worker:2d.gradient.linear.nonfinite
 // Description:createLinearGradient() throws TypeError if arguments are not finite
-// Note:
+// Note:<p class="notes">Defined in "Web IDL" (draft)
 
 importScripts("/resources/testharness.js");
 importScripts("/html/canvas/resources/canvas-tests.js");
diff --git a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.gradient.object.crosscanvas.html b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.gradient.object.crosscanvas.html
index 7ea9d84..6d229ea 100644
--- a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.gradient.object.crosscanvas.html
+++ b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.gradient.object.crosscanvas.html
@@ -22,8 +22,7 @@
 
   ctx.fillStyle = '#f00';
   ctx.fillRect(0, 0, 100, 50);
-  var offscreenCanvas2 = new OffscreenCanvas(100, 50);
-  var g = offscreenCanvas2.getContext('2d').createLinearGradient(0, 0, 100, 0);
+  var g = new OffscreenCanvas(100, 50).getContext('2d').createLinearGradient(0, 0, 100, 0);
   g.addColorStop(0, '#0f0');
   g.addColorStop(1, '#0f0');
   ctx.fillStyle = g;
diff --git a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.gradient.object.crosscanvas.worker.js b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.gradient.object.crosscanvas.worker.js
index f877d59..1afebdea1 100644
--- a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.gradient.object.crosscanvas.worker.js
+++ b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.gradient.object.crosscanvas.worker.js
@@ -18,8 +18,7 @@
 
   ctx.fillStyle = '#f00';
   ctx.fillRect(0, 0, 100, 50);
-  var offscreenCanvas2 = new OffscreenCanvas(100, 50);
-  var g = offscreenCanvas2.getContext('2d').createLinearGradient(0, 0, 100, 0);
+  var g = new OffscreenCanvas(100, 50).getContext('2d').createLinearGradient(0, 0, 100, 0);
   g.addColorStop(0, '#0f0');
   g.addColorStop(1, '#0f0');
   ctx.fillStyle = g;
diff --git a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.gradient.object.invalidcolor.html b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.gradient.object.invalidcolor.html
index 51160c5..3db5afe 100644
--- a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.gradient.object.invalidcolor.html
+++ b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.gradient.object.invalidcolor.html
@@ -22,6 +22,15 @@
 
   var g = ctx.createLinearGradient(0, 0, 100, 0);
   assert_throws_dom("SYNTAX_ERR", function() { g.addColorStop(0, ""); });
+  assert_throws_dom("SYNTAX_ERR", function() { g.addColorStop(0, 'rgb(NaN%, NaN%, NaN%)'); });
+  assert_throws_dom("SYNTAX_ERR", function() { g.addColorStop(0, 'null'); });
+  assert_throws_dom("SYNTAX_ERR", function() { g.addColorStop(0, 'undefined'); });
+  assert_throws_dom("SYNTAX_ERR", function() { g.addColorStop(0, null); });
+  assert_throws_dom("SYNTAX_ERR", function() { g.addColorStop(0, undefined); });
+
+  var g = ctx.createRadialGradient(0, 0, 0, 100, 0, 0);
+  assert_throws_dom("SYNTAX_ERR", function() { g.addColorStop(0, ""); });
+  assert_throws_dom("SYNTAX_ERR", function() { g.addColorStop(0, 'rgb(NaN%, NaN%, NaN%)'); });
   assert_throws_dom("SYNTAX_ERR", function() { g.addColorStop(0, 'null'); });
   assert_throws_dom("SYNTAX_ERR", function() { g.addColorStop(0, 'undefined'); });
   assert_throws_dom("SYNTAX_ERR", function() { g.addColorStop(0, null); });
diff --git a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.gradient.object.invalidcolor.worker.js b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.gradient.object.invalidcolor.worker.js
index 40e84ba..33b524f 100644
--- a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.gradient.object.invalidcolor.worker.js
+++ b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.gradient.object.invalidcolor.worker.js
@@ -18,6 +18,15 @@
 
   var g = ctx.createLinearGradient(0, 0, 100, 0);
   assert_throws_dom("SYNTAX_ERR", function() { g.addColorStop(0, ""); });
+  assert_throws_dom("SYNTAX_ERR", function() { g.addColorStop(0, 'rgb(NaN%, NaN%, NaN%)'); });
+  assert_throws_dom("SYNTAX_ERR", function() { g.addColorStop(0, 'null'); });
+  assert_throws_dom("SYNTAX_ERR", function() { g.addColorStop(0, 'undefined'); });
+  assert_throws_dom("SYNTAX_ERR", function() { g.addColorStop(0, null); });
+  assert_throws_dom("SYNTAX_ERR", function() { g.addColorStop(0, undefined); });
+
+  var g = ctx.createRadialGradient(0, 0, 0, 100, 0, 0);
+  assert_throws_dom("SYNTAX_ERR", function() { g.addColorStop(0, ""); });
+  assert_throws_dom("SYNTAX_ERR", function() { g.addColorStop(0, 'rgb(NaN%, NaN%, NaN%)'); });
   assert_throws_dom("SYNTAX_ERR", function() { g.addColorStop(0, 'null'); });
   assert_throws_dom("SYNTAX_ERR", function() { g.addColorStop(0, 'undefined'); });
   assert_throws_dom("SYNTAX_ERR", function() { g.addColorStop(0, null); });
diff --git a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.gradient.object.return.html b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.gradient.object.return.html
new file mode 100644
index 0000000..779b941d
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.gradient.object.return.html
@@ -0,0 +1,35 @@
+<!DOCTYPE html>
+<!-- DO NOT EDIT! This test has been generated by /html/canvas/tools/gentest.py. -->
+<title>OffscreenCanvas test: 2d.gradient.object.return</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/html/canvas/resources/canvas-tests.js"></script>
+
+<h1>2d.gradient.object.return</h1>
+<p class="desc">createLinearGradient() and createRadialGradient() returns objects implementing CanvasGradient</p>
+
+
+<script>
+var t = async_test("createLinearGradient() and createRadialGradient() returns objects implementing CanvasGradient");
+var t_pass = t.done.bind(t);
+var t_fail = t.step_func(function(reason) {
+    throw reason;
+});
+t.step(function() {
+
+  var canvas = new OffscreenCanvas(100, 50);
+  var ctx = canvas.getContext('2d');
+
+  window.CanvasGradient.prototype.thisImplementsCanvasGradient = true;
+
+  var g1 = ctx.createLinearGradient(0, 0, 100, 0);
+  _assertDifferent(g1.addColorStop, undefined, "g1.addColorStop", "undefined");
+  _assertSame(g1.thisImplementsCanvasGradient, true, "g1.thisImplementsCanvasGradient", "true");
+
+  var g2 = ctx.createRadialGradient(0, 0, 10, 0, 0, 20);
+  _assertDifferent(g2.addColorStop, undefined, "g2.addColorStop", "undefined");
+  _assertSame(g2.thisImplementsCanvasGradient, true, "g2.thisImplementsCanvasGradient", "true");
+  t.done();
+
+});
+</script>
diff --git a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.gradient.object.return.worker.js b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.gradient.object.return.worker.js
new file mode 100644
index 0000000..a279eed
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.gradient.object.return.worker.js
@@ -0,0 +1,30 @@
+// DO NOT EDIT! This test has been generated by /html/canvas/tools/gentest.py.
+// OffscreenCanvas test in a worker:2d.gradient.object.return
+// Description:createLinearGradient() and createRadialGradient() returns objects implementing CanvasGradient
+// Note:
+
+importScripts("/resources/testharness.js");
+importScripts("/html/canvas/resources/canvas-tests.js");
+
+var t = async_test("createLinearGradient() and createRadialGradient() returns objects implementing CanvasGradient");
+var t_pass = t.done.bind(t);
+var t_fail = t.step_func(function(reason) {
+    throw reason;
+});
+t.step(function() {
+
+  var canvas = new OffscreenCanvas(100, 50);
+  var ctx = canvas.getContext('2d');
+
+  self.CanvasGradient.prototype.thisImplementsCanvasGradient = true;
+
+  var g1 = ctx.createLinearGradient(0, 0, 100, 0);
+  _assertDifferent(g1.addColorStop, undefined, "g1.addColorStop", "undefined");
+  _assertSame(g1.thisImplementsCanvasGradient, true, "g1.thisImplementsCanvasGradient", "true");
+
+  var g2 = ctx.createRadialGradient(0, 0, 10, 0, 0, 20);
+  _assertDifferent(g2.addColorStop, undefined, "g2.addColorStop", "undefined");
+  _assertSame(g2.thisImplementsCanvasGradient, true, "g2.thisImplementsCanvasGradient", "true");
+  t.done();
+});
+done();
diff --git a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.gradient.object.type.html b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.gradient.object.type.html
new file mode 100644
index 0000000..aa72183
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.gradient.object.type.html
@@ -0,0 +1,28 @@
+<!DOCTYPE html>
+<!-- DO NOT EDIT! This test has been generated by /html/canvas/tools/gentest.py. -->
+<title>OffscreenCanvas test: 2d.gradient.object.type</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/html/canvas/resources/canvas-tests.js"></script>
+
+<h1>2d.gradient.object.type</h1>
+<p class="desc">window.CanvasGradient exists and has the right properties</p>
+
+<p class="notes">Defined in "Web IDL" (draft)
+<script>
+var t = async_test("window.CanvasGradient exists and has the right properties");
+var t_pass = t.done.bind(t);
+var t_fail = t.step_func(function(reason) {
+    throw reason;
+});
+t.step(function() {
+
+  var canvas = new OffscreenCanvas(100, 50);
+  var ctx = canvas.getContext('2d');
+
+  _assertDifferent(window.CanvasGradient, undefined, "window.CanvasGradient", "undefined");
+  _assertDifferent(window.CanvasGradient.prototype.addColorStop, undefined, "window.CanvasGradient.prototype.addColorStop", "undefined");
+  t.done();
+
+});
+</script>
diff --git a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.gradient.object.type.worker.js b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.gradient.object.type.worker.js
new file mode 100644
index 0000000..e0101df
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.gradient.object.type.worker.js
@@ -0,0 +1,23 @@
+// DO NOT EDIT! This test has been generated by /html/canvas/tools/gentest.py.
+// OffscreenCanvas test in a worker:2d.gradient.object.type
+// Description:window.CanvasGradient exists and has the right properties
+// Note:<p class="notes">Defined in "Web IDL" (draft)
+
+importScripts("/resources/testharness.js");
+importScripts("/html/canvas/resources/canvas-tests.js");
+
+var t = async_test("window.CanvasGradient exists and has the right properties");
+var t_pass = t.done.bind(t);
+var t_fail = t.step_func(function(reason) {
+    throw reason;
+});
+t.step(function() {
+
+  var canvas = new OffscreenCanvas(100, 50);
+  var ctx = canvas.getContext('2d');
+
+  _assertDifferent(self.CanvasGradient, undefined, "self.CanvasGradient", "undefined");
+  _assertDifferent(self.CanvasGradient.prototype.addColorStop, undefined, "self.CanvasGradient.prototype.addColorStop", "undefined");
+  t.done();
+});
+done();
diff --git a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.gradient.radial.cone.behind.html b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.gradient.radial.cone.behind.html
index 9a07ec4..4a639778 100644
--- a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.gradient.radial.cone.behind.html
+++ b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.gradient.radial.cone.behind.html
@@ -22,11 +22,13 @@
 
   ctx.fillStyle = '#0f0';
   ctx.fillRect(0, 0, 100, 50);
+
   var g = ctx.createRadialGradient(120, 25, 10, 211, 25, 100);
   g.addColorStop(0, '#f00');
   g.addColorStop(1, '#f00');
   ctx.fillStyle = g;
   ctx.fillRect(0, 0, 100, 50);
+
   _assertPixel(canvas, 1,1, 0,255,0,255);
   _assertPixel(canvas, 50,1, 0,255,0,255);
   _assertPixel(canvas, 98,1, 0,255,0,255);
diff --git a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.gradient.radial.cone.behind.worker.js b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.gradient.radial.cone.behind.worker.js
index 806a83f..114dab0a 100644
--- a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.gradient.radial.cone.behind.worker.js
+++ b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.gradient.radial.cone.behind.worker.js
@@ -18,11 +18,13 @@
 
   ctx.fillStyle = '#0f0';
   ctx.fillRect(0, 0, 100, 50);
+
   var g = ctx.createRadialGradient(120, 25, 10, 211, 25, 100);
   g.addColorStop(0, '#f00');
   g.addColorStop(1, '#f00');
   ctx.fillStyle = g;
   ctx.fillRect(0, 0, 100, 50);
+
   _assertPixel(canvas, 1,1, 0,255,0,255);
   _assertPixel(canvas, 50,1, 0,255,0,255);
   _assertPixel(canvas, 98,1, 0,255,0,255);
diff --git a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.gradient.radial.cone.beside.html b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.gradient.radial.cone.beside.html
index 1ab27b46..94fe289b 100644
--- a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.gradient.radial.cone.beside.html
+++ b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.gradient.radial.cone.beside.html
@@ -22,11 +22,13 @@
 
   ctx.fillStyle = '#0f0';
   ctx.fillRect(0, 0, 100, 50);
+
   var g = ctx.createRadialGradient(0, 100, 40, 100, 100, 50);
   g.addColorStop(0, '#f00');
   g.addColorStop(1, '#f00');
   ctx.fillStyle = g;
   ctx.fillRect(0, 0, 100, 50);
+
   _assertPixel(canvas, 1,1, 0,255,0,255);
   _assertPixel(canvas, 50,1, 0,255,0,255);
   _assertPixel(canvas, 98,1, 0,255,0,255);
diff --git a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.gradient.radial.cone.beside.worker.js b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.gradient.radial.cone.beside.worker.js
index 2bb0860..e6f53fb 100644
--- a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.gradient.radial.cone.beside.worker.js
+++ b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.gradient.radial.cone.beside.worker.js
@@ -18,11 +18,13 @@
 
   ctx.fillStyle = '#0f0';
   ctx.fillRect(0, 0, 100, 50);
+
   var g = ctx.createRadialGradient(0, 100, 40, 100, 100, 50);
   g.addColorStop(0, '#f00');
   g.addColorStop(1, '#f00');
   ctx.fillStyle = g;
   ctx.fillRect(0, 0, 100, 50);
+
   _assertPixel(canvas, 1,1, 0,255,0,255);
   _assertPixel(canvas, 50,1, 0,255,0,255);
   _assertPixel(canvas, 98,1, 0,255,0,255);
diff --git a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.gradient.radial.cone.bottom.html b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.gradient.radial.cone.bottom.html
index 87cac451..7cf9867 100644
--- a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.gradient.radial.cone.bottom.html
+++ b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.gradient.radial.cone.bottom.html
@@ -22,11 +22,13 @@
 
   ctx.fillStyle = '#f00';
   ctx.fillRect(0, 0, 100, 50);
+
   var g = ctx.createRadialGradient(210, 25, 100, 230, 25, 101);
   g.addColorStop(0, '#0f0');
   g.addColorStop(1, '#f00');
   ctx.fillStyle = g;
   ctx.fillRect(0, 0, 100, 50);
+
   _assertPixel(canvas, 1,1, 0,255,0,255);
   _assertPixel(canvas, 50,1, 0,255,0,255);
   _assertPixel(canvas, 98,1, 0,255,0,255);
diff --git a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.gradient.radial.cone.bottom.worker.js b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.gradient.radial.cone.bottom.worker.js
index a9c859e..132d131 100644
--- a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.gradient.radial.cone.bottom.worker.js
+++ b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.gradient.radial.cone.bottom.worker.js
@@ -18,11 +18,13 @@
 
   ctx.fillStyle = '#f00';
   ctx.fillRect(0, 0, 100, 50);
+
   var g = ctx.createRadialGradient(210, 25, 100, 230, 25, 101);
   g.addColorStop(0, '#0f0');
   g.addColorStop(1, '#f00');
   ctx.fillStyle = g;
   ctx.fillRect(0, 0, 100, 50);
+
   _assertPixel(canvas, 1,1, 0,255,0,255);
   _assertPixel(canvas, 50,1, 0,255,0,255);
   _assertPixel(canvas, 98,1, 0,255,0,255);
diff --git a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.gradient.radial.cone.cylinder.html b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.gradient.radial.cone.cylinder.html
index 649b83c..27f75a8 100644
--- a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.gradient.radial.cone.cylinder.html
+++ b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.gradient.radial.cone.cylinder.html
@@ -22,11 +22,13 @@
 
   ctx.fillStyle = '#f00';
   ctx.fillRect(0, 0, 100, 50);
+
   var g = ctx.createRadialGradient(210, 25, 100, 230, 25, 100);
   g.addColorStop(0, '#0f0');
   g.addColorStop(1, '#f00');
   ctx.fillStyle = g;
   ctx.fillRect(0, 0, 100, 50);
+
   _assertPixel(canvas, 1,1, 0,255,0,255);
   _assertPixel(canvas, 50,1, 0,255,0,255);
   _assertPixel(canvas, 98,1, 0,255,0,255);
diff --git a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.gradient.radial.cone.cylinder.worker.js b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.gradient.radial.cone.cylinder.worker.js
index 8223f52..9534174 100644
--- a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.gradient.radial.cone.cylinder.worker.js
+++ b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.gradient.radial.cone.cylinder.worker.js
@@ -18,11 +18,13 @@
 
   ctx.fillStyle = '#f00';
   ctx.fillRect(0, 0, 100, 50);
+
   var g = ctx.createRadialGradient(210, 25, 100, 230, 25, 100);
   g.addColorStop(0, '#0f0');
   g.addColorStop(1, '#f00');
   ctx.fillStyle = g;
   ctx.fillRect(0, 0, 100, 50);
+
   _assertPixel(canvas, 1,1, 0,255,0,255);
   _assertPixel(canvas, 50,1, 0,255,0,255);
   _assertPixel(canvas, 98,1, 0,255,0,255);
diff --git a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.gradient.radial.cone.front.html b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.gradient.radial.cone.front.html
index 6ad51c1..b4615570 100644
--- a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.gradient.radial.cone.front.html
+++ b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.gradient.radial.cone.front.html
@@ -22,11 +22,13 @@
 
   ctx.fillStyle = '#f00';
   ctx.fillRect(0, 0, 100, 50);
+
   var g = ctx.createRadialGradient(311, 25, 10, 210, 25, 100);
   g.addColorStop(0, '#f00');
   g.addColorStop(1, '#0f0');
   ctx.fillStyle = g;
   ctx.fillRect(0, 0, 100, 50);
+
   _assertPixel(canvas, 1,1, 0,255,0,255);
   _assertPixel(canvas, 50,1, 0,255,0,255);
   _assertPixel(canvas, 98,1, 0,255,0,255);
diff --git a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.gradient.radial.cone.front.worker.js b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.gradient.radial.cone.front.worker.js
index a1eb55f..fffce0f 100644
--- a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.gradient.radial.cone.front.worker.js
+++ b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.gradient.radial.cone.front.worker.js
@@ -18,11 +18,13 @@
 
   ctx.fillStyle = '#f00';
   ctx.fillRect(0, 0, 100, 50);
+
   var g = ctx.createRadialGradient(311, 25, 10, 210, 25, 100);
   g.addColorStop(0, '#f00');
   g.addColorStop(1, '#0f0');
   ctx.fillStyle = g;
   ctx.fillRect(0, 0, 100, 50);
+
   _assertPixel(canvas, 1,1, 0,255,0,255);
   _assertPixel(canvas, 50,1, 0,255,0,255);
   _assertPixel(canvas, 98,1, 0,255,0,255);
diff --git a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.gradient.radial.cone.shape1.html b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.gradient.radial.cone.shape1.html
index 7de944a..5c4d8125 100644
--- a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.gradient.radial.cone.shape1.html
+++ b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.gradient.radial.cone.shape1.html
@@ -21,19 +21,23 @@
   var ctx = canvas.getContext('2d');
 
   var tol = 1; // tolerance to avoid antialiasing artifacts
+
   ctx.fillStyle = '#0f0';
   ctx.fillRect(0, 0, 100, 50);
+
   ctx.fillStyle = '#f00';
   ctx.beginPath();
   ctx.moveTo(30+tol, 40);
   ctx.lineTo(110, -20+tol);
   ctx.lineTo(110, 100-tol);
   ctx.fill();
+
   var g = ctx.createRadialGradient(30+10*5/2, 40, 10*3/2, 30+10*15/4, 40, 10*9/4);
   g.addColorStop(0, '#0f0');
   g.addColorStop(1, '#0f0');
   ctx.fillStyle = g;
   ctx.fillRect(0, 0, 100, 50);
+
   _assertPixel(canvas, 1,1, 0,255,0,255);
   _assertPixel(canvas, 50,1, 0,255,0,255);
   _assertPixel(canvas, 98,1, 0,255,0,255);
diff --git a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.gradient.radial.cone.shape1.worker.js b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.gradient.radial.cone.shape1.worker.js
index 0d211a0c..5bd3f5e 100644
--- a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.gradient.radial.cone.shape1.worker.js
+++ b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.gradient.radial.cone.shape1.worker.js
@@ -17,19 +17,23 @@
   var ctx = canvas.getContext('2d');
 
   var tol = 1; // tolerance to avoid antialiasing artifacts
+
   ctx.fillStyle = '#0f0';
   ctx.fillRect(0, 0, 100, 50);
+
   ctx.fillStyle = '#f00';
   ctx.beginPath();
   ctx.moveTo(30+tol, 40);
   ctx.lineTo(110, -20+tol);
   ctx.lineTo(110, 100-tol);
   ctx.fill();
+
   var g = ctx.createRadialGradient(30+10*5/2, 40, 10*3/2, 30+10*15/4, 40, 10*9/4);
   g.addColorStop(0, '#0f0');
   g.addColorStop(1, '#0f0');
   ctx.fillStyle = g;
   ctx.fillRect(0, 0, 100, 50);
+
   _assertPixel(canvas, 1,1, 0,255,0,255);
   _assertPixel(canvas, 50,1, 0,255,0,255);
   _assertPixel(canvas, 98,1, 0,255,0,255);
diff --git a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.gradient.radial.cone.shape2.html b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.gradient.radial.cone.shape2.html
index a748546..1dec145e 100644
--- a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.gradient.radial.cone.shape2.html
+++ b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.gradient.radial.cone.shape2.html
@@ -21,19 +21,23 @@
   var ctx = canvas.getContext('2d');
 
   var tol = 1; // tolerance to avoid antialiasing artifacts
+
   ctx.fillStyle = '#0f0';
   ctx.fillRect(0, 0, 100, 50);
+
   var g = ctx.createRadialGradient(30+10*5/2, 40, 10*3/2, 30+10*15/4, 40, 10*9/4);
   g.addColorStop(0, '#f00');
   g.addColorStop(1, '#f00');
   ctx.fillStyle = g;
   ctx.fillRect(0, 0, 100, 50);
+
   ctx.fillStyle = '#0f0';
   ctx.beginPath();
   ctx.moveTo(30-tol, 40);
   ctx.lineTo(110, -20-tol);
   ctx.lineTo(110, 100+tol);
   ctx.fill();
+
   _assertPixel(canvas, 1,1, 0,255,0,255);
   _assertPixel(canvas, 50,1, 0,255,0,255);
   _assertPixel(canvas, 98,1, 0,255,0,255);
diff --git a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.gradient.radial.cone.shape2.worker.js b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.gradient.radial.cone.shape2.worker.js
index 2f56159..31638cf 100644
--- a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.gradient.radial.cone.shape2.worker.js
+++ b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.gradient.radial.cone.shape2.worker.js
@@ -17,19 +17,23 @@
   var ctx = canvas.getContext('2d');
 
   var tol = 1; // tolerance to avoid antialiasing artifacts
+
   ctx.fillStyle = '#0f0';
   ctx.fillRect(0, 0, 100, 50);
+
   var g = ctx.createRadialGradient(30+10*5/2, 40, 10*3/2, 30+10*15/4, 40, 10*9/4);
   g.addColorStop(0, '#f00');
   g.addColorStop(1, '#f00');
   ctx.fillStyle = g;
   ctx.fillRect(0, 0, 100, 50);
+
   ctx.fillStyle = '#0f0';
   ctx.beginPath();
   ctx.moveTo(30-tol, 40);
   ctx.lineTo(110, -20-tol);
   ctx.lineTo(110, 100+tol);
   ctx.fill();
+
   _assertPixel(canvas, 1,1, 0,255,0,255);
   _assertPixel(canvas, 50,1, 0,255,0,255);
   _assertPixel(canvas, 98,1, 0,255,0,255);
diff --git a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.gradient.radial.cone.top.html b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.gradient.radial.cone.top.html
index 802006b..6cffa28 100644
--- a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.gradient.radial.cone.top.html
+++ b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.gradient.radial.cone.top.html
@@ -22,11 +22,13 @@
 
   ctx.fillStyle = '#f00';
   ctx.fillRect(0, 0, 100, 50);
+
   var g = ctx.createRadialGradient(230, 25, 100, 100, 25, 101);
   g.addColorStop(0, '#f00');
   g.addColorStop(1, '#0f0');
   ctx.fillStyle = g;
   ctx.fillRect(0, 0, 100, 50);
+
   _assertPixel(canvas, 1,1, 0,255,0,255);
   _assertPixel(canvas, 50,1, 0,255,0,255);
   _assertPixel(canvas, 98,1, 0,255,0,255);
diff --git a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.gradient.radial.cone.top.worker.js b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.gradient.radial.cone.top.worker.js
index 731ee8f..85a9eae4 100644
--- a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.gradient.radial.cone.top.worker.js
+++ b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.gradient.radial.cone.top.worker.js
@@ -18,11 +18,13 @@
 
   ctx.fillStyle = '#f00';
   ctx.fillRect(0, 0, 100, 50);
+
   var g = ctx.createRadialGradient(230, 25, 100, 100, 25, 101);
   g.addColorStop(0, '#f00');
   g.addColorStop(1, '#0f0');
   ctx.fillStyle = g;
   ctx.fillRect(0, 0, 100, 50);
+
   _assertPixel(canvas, 1,1, 0,255,0,255);
   _assertPixel(canvas, 50,1, 0,255,0,255);
   _assertPixel(canvas, 98,1, 0,255,0,255);
diff --git a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.gradient.radial.equal.html b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.gradient.radial.equal.html
index 3786e18..52319ef 100644
--- a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.gradient.radial.equal.html
+++ b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.gradient.radial.equal.html
@@ -22,11 +22,13 @@
 
   ctx.fillStyle = '#0f0';
   ctx.fillRect(0, 0, 100, 50);
+
   var g = ctx.createRadialGradient(50, 25, 20, 50, 25, 20);
   g.addColorStop(0, '#f00');
   g.addColorStop(1, '#f00');
   ctx.fillStyle = g;
   ctx.fillRect(0, 0, 100, 50);
+
   _assertPixel(canvas, 1,1, 0,255,0,255);
   _assertPixel(canvas, 50,1, 0,255,0,255);
   _assertPixel(canvas, 98,1, 0,255,0,255);
diff --git a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.gradient.radial.equal.worker.js b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.gradient.radial.equal.worker.js
index d046809..7f5ad50f 100644
--- a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.gradient.radial.equal.worker.js
+++ b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.gradient.radial.equal.worker.js
@@ -18,11 +18,13 @@
 
   ctx.fillStyle = '#0f0';
   ctx.fillRect(0, 0, 100, 50);
+
   var g = ctx.createRadialGradient(50, 25, 20, 50, 25, 20);
   g.addColorStop(0, '#f00');
   g.addColorStop(1, '#f00');
   ctx.fillStyle = g;
   ctx.fillRect(0, 0, 100, 50);
+
   _assertPixel(canvas, 1,1, 0,255,0,255);
   _assertPixel(canvas, 50,1, 0,255,0,255);
   _assertPixel(canvas, 98,1, 0,255,0,255);
diff --git a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.gradient.radial.inside1.html b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.gradient.radial.inside1.html
index db269a9..f0da2c2 100644
--- a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.gradient.radial.inside1.html
+++ b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.gradient.radial.inside1.html
@@ -22,11 +22,13 @@
 
   ctx.fillStyle = '#f00';
   ctx.fillRect(0, 0, 100, 50);
+
   var g = ctx.createRadialGradient(50, 25, 100, 50, 25, 200);
   g.addColorStop(0, '#0f0');
   g.addColorStop(1, '#f00');
   ctx.fillStyle = g;
   ctx.fillRect(0, 0, 100, 50);
+
   _assertPixel(canvas, 1,1, 0,255,0,255);
   _assertPixel(canvas, 50,1, 0,255,0,255);
   _assertPixel(canvas, 98,1, 0,255,0,255);
diff --git a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.gradient.radial.inside1.worker.js b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.gradient.radial.inside1.worker.js
index bc48654..37ed851a 100644
--- a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.gradient.radial.inside1.worker.js
+++ b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.gradient.radial.inside1.worker.js
@@ -18,11 +18,13 @@
 
   ctx.fillStyle = '#f00';
   ctx.fillRect(0, 0, 100, 50);
+
   var g = ctx.createRadialGradient(50, 25, 100, 50, 25, 200);
   g.addColorStop(0, '#0f0');
   g.addColorStop(1, '#f00');
   ctx.fillStyle = g;
   ctx.fillRect(0, 0, 100, 50);
+
   _assertPixel(canvas, 1,1, 0,255,0,255);
   _assertPixel(canvas, 50,1, 0,255,0,255);
   _assertPixel(canvas, 98,1, 0,255,0,255);
diff --git a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.gradient.radial.inside2.html b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.gradient.radial.inside2.html
index 23de3b00..3f25502 100644
--- a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.gradient.radial.inside2.html
+++ b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.gradient.radial.inside2.html
@@ -22,11 +22,13 @@
 
   ctx.fillStyle = '#f00';
   ctx.fillRect(0, 0, 100, 50);
+
   var g = ctx.createRadialGradient(50, 25, 200, 50, 25, 100);
   g.addColorStop(0, '#f00');
   g.addColorStop(1, '#0f0');
   ctx.fillStyle = g;
   ctx.fillRect(0, 0, 100, 50);
+
   _assertPixel(canvas, 1,1, 0,255,0,255);
   _assertPixel(canvas, 50,1, 0,255,0,255);
   _assertPixel(canvas, 98,1, 0,255,0,255);
diff --git a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.gradient.radial.inside2.worker.js b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.gradient.radial.inside2.worker.js
index ffc1040..0f36051 100644
--- a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.gradient.radial.inside2.worker.js
+++ b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.gradient.radial.inside2.worker.js
@@ -18,11 +18,13 @@
 
   ctx.fillStyle = '#f00';
   ctx.fillRect(0, 0, 100, 50);
+
   var g = ctx.createRadialGradient(50, 25, 200, 50, 25, 100);
   g.addColorStop(0, '#f00');
   g.addColorStop(1, '#0f0');
   ctx.fillStyle = g;
   ctx.fillRect(0, 0, 100, 50);
+
   _assertPixel(canvas, 1,1, 0,255,0,255);
   _assertPixel(canvas, 50,1, 0,255,0,255);
   _assertPixel(canvas, 98,1, 0,255,0,255);
diff --git a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.gradient.radial.inside3.html b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.gradient.radial.inside3.html
index bbb2ecbc..1ef80a5 100644
--- a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.gradient.radial.inside3.html
+++ b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.gradient.radial.inside3.html
@@ -22,12 +22,14 @@
 
   ctx.fillStyle = '#f00';
   ctx.fillRect(0, 0, 100, 50);
+
   var g = ctx.createRadialGradient(50, 25, 200, 50, 25, 100);
   g.addColorStop(0, '#f00');
   g.addColorStop(0.993, '#f00');
   g.addColorStop(1, '#0f0');
   ctx.fillStyle = g;
   ctx.fillRect(0, 0, 100, 50);
+
   _assertPixel(canvas, 1,1, 0,255,0,255);
   _assertPixel(canvas, 50,1, 0,255,0,255);
   _assertPixel(canvas, 98,1, 0,255,0,255);
diff --git a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.gradient.radial.inside3.worker.js b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.gradient.radial.inside3.worker.js
index 792d9e78..3c9131d 100644
--- a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.gradient.radial.inside3.worker.js
+++ b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.gradient.radial.inside3.worker.js
@@ -18,12 +18,14 @@
 
   ctx.fillStyle = '#f00';
   ctx.fillRect(0, 0, 100, 50);
+
   var g = ctx.createRadialGradient(50, 25, 200, 50, 25, 100);
   g.addColorStop(0, '#f00');
   g.addColorStop(0.993, '#f00');
   g.addColorStop(1, '#0f0');
   ctx.fillStyle = g;
   ctx.fillRect(0, 0, 100, 50);
+
   _assertPixel(canvas, 1,1, 0,255,0,255);
   _assertPixel(canvas, 50,1, 0,255,0,255);
   _assertPixel(canvas, 98,1, 0,255,0,255);
diff --git a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.gradient.radial.nonfinite.html b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.gradient.radial.nonfinite.html
index 8ba04769..4f068bb 100644
--- a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.gradient.radial.nonfinite.html
+++ b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.gradient.radial.nonfinite.html
@@ -8,7 +8,7 @@
 <h1>2d.gradient.radial.nonfinite</h1>
 <p class="desc">createRadialGradient() throws TypeError if arguments are not finite</p>
 
-
+<p class="notes">Defined in "Web IDL" (draft)
 <script>
 var t = async_test("createRadialGradient() throws TypeError if arguments are not finite");
 var t_pass = t.done.bind(t);
diff --git a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.gradient.radial.nonfinite.worker.js b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.gradient.radial.nonfinite.worker.js
index dd25e607..641a341 100644
--- a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.gradient.radial.nonfinite.worker.js
+++ b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.gradient.radial.nonfinite.worker.js
@@ -1,7 +1,7 @@
 // DO NOT EDIT! This test has been generated by /html/canvas/tools/gentest.py.
 // OffscreenCanvas test in a worker:2d.gradient.radial.nonfinite
 // Description:createRadialGradient() throws TypeError if arguments are not finite
-// Note:
+// Note:<p class="notes">Defined in "Web IDL" (draft)
 
 importScripts("/resources/testharness.js");
 importScripts("/html/canvas/resources/canvas-tests.js");
diff --git a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.gradient.radial.outside1.html b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.gradient.radial.outside1.html
index 6c65f23..61fed27 100644
--- a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.gradient.radial.outside1.html
+++ b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.gradient.radial.outside1.html
@@ -22,11 +22,13 @@
 
   ctx.fillStyle = '#f00';
   ctx.fillRect(0, 0, 100, 50);
+
   var g = ctx.createRadialGradient(200, 25, 10, 200, 25, 20);
   g.addColorStop(0, '#f00');
   g.addColorStop(1, '#0f0');
   ctx.fillStyle = g;
   ctx.fillRect(0, 0, 100, 50);
+
   _assertPixel(canvas, 1,1, 0,255,0,255);
   _assertPixel(canvas, 50,1, 0,255,0,255);
   _assertPixel(canvas, 98,1, 0,255,0,255);
diff --git a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.gradient.radial.outside1.worker.js b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.gradient.radial.outside1.worker.js
index d7beb71..b23eb81 100644
--- a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.gradient.radial.outside1.worker.js
+++ b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.gradient.radial.outside1.worker.js
@@ -18,11 +18,13 @@
 
   ctx.fillStyle = '#f00';
   ctx.fillRect(0, 0, 100, 50);
+
   var g = ctx.createRadialGradient(200, 25, 10, 200, 25, 20);
   g.addColorStop(0, '#f00');
   g.addColorStop(1, '#0f0');
   ctx.fillStyle = g;
   ctx.fillRect(0, 0, 100, 50);
+
   _assertPixel(canvas, 1,1, 0,255,0,255);
   _assertPixel(canvas, 50,1, 0,255,0,255);
   _assertPixel(canvas, 98,1, 0,255,0,255);
diff --git a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.gradient.radial.outside2.html b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.gradient.radial.outside2.html
index 8f4c48e..814beccf51 100644
--- a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.gradient.radial.outside2.html
+++ b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.gradient.radial.outside2.html
@@ -22,11 +22,13 @@
 
   ctx.fillStyle = '#f00';
   ctx.fillRect(0, 0, 100, 50);
+
   var g = ctx.createRadialGradient(200, 25, 20, 200, 25, 10);
   g.addColorStop(0, '#0f0');
   g.addColorStop(1, '#f00');
   ctx.fillStyle = g;
   ctx.fillRect(0, 0, 100, 50);
+
   _assertPixel(canvas, 1,1, 0,255,0,255);
   _assertPixel(canvas, 50,1, 0,255,0,255);
   _assertPixel(canvas, 98,1, 0,255,0,255);
diff --git a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.gradient.radial.outside2.worker.js b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.gradient.radial.outside2.worker.js
index 6c753e3..44eb4f4 100644
--- a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.gradient.radial.outside2.worker.js
+++ b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.gradient.radial.outside2.worker.js
@@ -18,11 +18,13 @@
 
   ctx.fillStyle = '#f00';
   ctx.fillRect(0, 0, 100, 50);
+
   var g = ctx.createRadialGradient(200, 25, 20, 200, 25, 10);
   g.addColorStop(0, '#0f0');
   g.addColorStop(1, '#f00');
   ctx.fillStyle = g;
   ctx.fillRect(0, 0, 100, 50);
+
   _assertPixel(canvas, 1,1, 0,255,0,255);
   _assertPixel(canvas, 50,1, 0,255,0,255);
   _assertPixel(canvas, 98,1, 0,255,0,255);
diff --git a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.gradient.radial.outside3.html b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.gradient.radial.outside3.html
index 5cfd308..632ada6 100644
--- a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.gradient.radial.outside3.html
+++ b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.gradient.radial.outside3.html
@@ -22,12 +22,14 @@
 
   ctx.fillStyle = '#f00';
   ctx.fillRect(0, 0, 100, 50);
+
   var g = ctx.createRadialGradient(200, 25, 20, 200, 25, 10);
   g.addColorStop(0, '#0f0');
   g.addColorStop(0.001, '#f00');
   g.addColorStop(1, '#f00');
   ctx.fillStyle = g;
   ctx.fillRect(0, 0, 100, 50);
+
   _assertPixel(canvas, 1,1, 0,255,0,255);
   _assertPixel(canvas, 50,1, 0,255,0,255);
   _assertPixel(canvas, 98,1, 0,255,0,255);
diff --git a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.gradient.radial.outside3.worker.js b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.gradient.radial.outside3.worker.js
index 70593ce..16d0c1a 100644
--- a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.gradient.radial.outside3.worker.js
+++ b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.gradient.radial.outside3.worker.js
@@ -18,12 +18,14 @@
 
   ctx.fillStyle = '#f00';
   ctx.fillRect(0, 0, 100, 50);
+
   var g = ctx.createRadialGradient(200, 25, 20, 200, 25, 10);
   g.addColorStop(0, '#0f0');
   g.addColorStop(0.001, '#f00');
   g.addColorStop(1, '#f00');
   ctx.fillStyle = g;
   ctx.fillRect(0, 0, 100, 50);
+
   _assertPixel(canvas, 1,1, 0,255,0,255);
   _assertPixel(canvas, 50,1, 0,255,0,255);
   _assertPixel(canvas, 98,1, 0,255,0,255);
diff --git a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.gradient.radial.touch1.html b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.gradient.radial.touch1.html
index 983071ec..3823518 100644
--- a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.gradient.radial.touch1.html
+++ b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.gradient.radial.touch1.html
@@ -22,11 +22,13 @@
 
   ctx.fillStyle = '#0f0';
   ctx.fillRect(0, 0, 100, 50);
+
   var g = ctx.createRadialGradient(150, 25, 50, 200, 25, 100);
   g.addColorStop(0, '#f00');
   g.addColorStop(1, '#f00');
   ctx.fillStyle = g;
   ctx.fillRect(0, 0, 100, 50);
+
   _assertPixel(canvas, 1,1, 0,255,0,255);
   _assertPixel(canvas, 50,1, 0,255,0,255);
   _assertPixel(canvas, 98,1, 0,255,0,255);
diff --git a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.gradient.radial.touch1.worker.js b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.gradient.radial.touch1.worker.js
index 21bbce1..853ad16 100644
--- a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.gradient.radial.touch1.worker.js
+++ b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.gradient.radial.touch1.worker.js
@@ -18,11 +18,13 @@
 
   ctx.fillStyle = '#0f0';
   ctx.fillRect(0, 0, 100, 50);
+
   var g = ctx.createRadialGradient(150, 25, 50, 200, 25, 100);
   g.addColorStop(0, '#f00');
   g.addColorStop(1, '#f00');
   ctx.fillStyle = g;
   ctx.fillRect(0, 0, 100, 50);
+
   _assertPixel(canvas, 1,1, 0,255,0,255);
   _assertPixel(canvas, 50,1, 0,255,0,255);
   _assertPixel(canvas, 98,1, 0,255,0,255);
diff --git a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.gradient.radial.touch2.html b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.gradient.radial.touch2.html
index bd23924..af5bbd7 100644
--- a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.gradient.radial.touch2.html
+++ b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.gradient.radial.touch2.html
@@ -22,6 +22,7 @@
 
   ctx.fillStyle = '#f00';
   ctx.fillRect(0, 0, 100, 50);
+
   var g = ctx.createRadialGradient(-80, 25, 70, 0, 25, 150);
   g.addColorStop(0, '#f00');
   g.addColorStop(0.01, '#0f0');
@@ -29,6 +30,7 @@
   g.addColorStop(1, '#f00');
   ctx.fillStyle = g;
   ctx.fillRect(0, 0, 100, 50);
+
   _assertPixel(canvas, 1,1, 0,255,0,255);
   _assertPixel(canvas, 50,1, 0,255,0,255);
   _assertPixel(canvas, 98,1, 0,255,0,255);
diff --git a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.gradient.radial.touch2.worker.js b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.gradient.radial.touch2.worker.js
index 75abd38d..434c27b 100644
--- a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.gradient.radial.touch2.worker.js
+++ b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.gradient.radial.touch2.worker.js
@@ -18,6 +18,7 @@
 
   ctx.fillStyle = '#f00';
   ctx.fillRect(0, 0, 100, 50);
+
   var g = ctx.createRadialGradient(-80, 25, 70, 0, 25, 150);
   g.addColorStop(0, '#f00');
   g.addColorStop(0.01, '#0f0');
@@ -25,6 +26,7 @@
   g.addColorStop(1, '#f00');
   ctx.fillStyle = g;
   ctx.fillRect(0, 0, 100, 50);
+
   _assertPixel(canvas, 1,1, 0,255,0,255);
   _assertPixel(canvas, 50,1, 0,255,0,255);
   _assertPixel(canvas, 98,1, 0,255,0,255);
diff --git a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.gradient.radial.touch3.html b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.gradient.radial.touch3.html
index bdaed6f..1cf578b 100644
--- a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.gradient.radial.touch3.html
+++ b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.gradient.radial.touch3.html
@@ -22,11 +22,13 @@
 
   ctx.fillStyle = '#0f0';
   ctx.fillRect(0, 0, 100, 50);
+
   var g = ctx.createRadialGradient(120, -15, 25, 140, -30, 50);
   g.addColorStop(0, '#f00');
   g.addColorStop(1, '#f00');
   ctx.fillStyle = g;
   ctx.fillRect(0, 0, 100, 50);
+
   _assertPixel(canvas, 1,1, 0,255,0,255);
   _assertPixel(canvas, 50,1, 0,255,0,255);
   _assertPixel(canvas, 98,1, 0,255,0,255);
diff --git a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.gradient.radial.touch3.worker.js b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.gradient.radial.touch3.worker.js
index b2f3b0b4..34fb0ca 100644
--- a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.gradient.radial.touch3.worker.js
+++ b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.gradient.radial.touch3.worker.js
@@ -18,11 +18,13 @@
 
   ctx.fillStyle = '#0f0';
   ctx.fillRect(0, 0, 100, 50);
+
   var g = ctx.createRadialGradient(120, -15, 25, 140, -30, 50);
   g.addColorStop(0, '#f00');
   g.addColorStop(1, '#f00');
   ctx.fillStyle = g;
   ctx.fillRect(0, 0, 100, 50);
+
   _assertPixel(canvas, 1,1, 0,255,0,255);
   _assertPixel(canvas, 50,1, 0,255,0,255);
   _assertPixel(canvas, 98,1, 0,255,0,255);
diff --git a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.basic.canvas.html b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.basic.canvas.html
index 6f66f50..f82a259e 100644
--- a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.basic.canvas.html
+++ b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.basic.canvas.html
@@ -22,13 +22,16 @@
 
   ctx.fillStyle = '#f00';
   ctx.fillRect(0, 0, 100, 50);
-  var offscreenCanvas2 = new OffscreenCanvas(100, 50);
-  var ctx2 = offscreenCanvas2.getContext('2d');
+
+  var canvas2 = new OffscreenCanvas(100, 50);
+  var ctx2 = canvas2.getContext('2d');
   ctx2.fillStyle = '#0f0';
   ctx2.fillRect(0, 0, 100, 50);
-  var pattern = ctx.createPattern(offscreenCanvas2, 'no-repeat');
+
+  var pattern = ctx.createPattern(canvas2, 'no-repeat');
   ctx.fillStyle = pattern;
   ctx.fillRect(0, 0, 100, 50);
+
   _assertPixel(canvas, 1,1, 0,255,0,255);
   _assertPixel(canvas, 50,1, 0,255,0,255);
   _assertPixel(canvas, 98,1, 0,255,0,255);
diff --git a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.basic.canvas.worker.js b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.basic.canvas.worker.js
index 6f6b882..510723f0 100644
--- a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.basic.canvas.worker.js
+++ b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.basic.canvas.worker.js
@@ -18,13 +18,16 @@
 
   ctx.fillStyle = '#f00';
   ctx.fillRect(0, 0, 100, 50);
-  var offscreenCanvas2 = new OffscreenCanvas(100, 50);
-  var ctx2 = offscreenCanvas2.getContext('2d');
+
+  var canvas2 = new OffscreenCanvas(100, 50);
+  var ctx2 = canvas2.getContext('2d');
   ctx2.fillStyle = '#0f0';
   ctx2.fillRect(0, 0, 100, 50);
-  var pattern = ctx.createPattern(offscreenCanvas2, 'no-repeat');
+
+  var pattern = ctx.createPattern(canvas2, 'no-repeat');
   ctx.fillStyle = pattern;
   ctx.fillRect(0, 0, 100, 50);
+
   _assertPixel(canvas, 1,1, 0,255,0,255);
   _assertPixel(canvas, 50,1, 0,255,0,255);
   _assertPixel(canvas, 98,1, 0,255,0,255);
diff --git a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.basic.image.html b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.basic.image.html
index 9efa9d636..ab7b7bb 100644
--- a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.basic.image.html
+++ b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.basic.image.html
@@ -10,38 +10,24 @@
 
 
 <script>
-var t = async_test("");
-var t_pass = t.done.bind(t);
-var t_fail = t.step_func(function(reason) {
-    throw reason;
-});
-t.step(function() {
+promise_test(async t => {
 
   var canvas = new OffscreenCanvas(100, 50);
   var ctx = canvas.getContext('2d');
 
   ctx.fillStyle = '#f00';
   ctx.fillRect(0, 0, 100, 50);
-  var promise = new Promise(function(resolve, reject) {
-      var xhr = new XMLHttpRequest();
-      xhr.open("GET", '/images/green.png');
-      xhr.responseType = 'blob';
-      xhr.send();
-      xhr.onload = function() {
-          resolve(xhr.response);
-      };
-  });
-  promise.then(function(response) {
-      return createImageBitmap(response).then(bitmap => {
-          var pattern = ctx.createPattern(bitmap, 'no-repeat');
-          ctx.fillStyle = pattern;
-          ctx.fillRect(0, 0, 100, 50);
-          _assertPixel(canvas, 1,1, 0,255,0,255);
-          _assertPixel(canvas, 98,1, 0,255,0,255);
-          _assertPixel(canvas, 1,48, 0,255,0,255);
-          _assertPixel(canvas, 98,48, 0,255,0,255);
-      });
-  }).then(t_pass, t_fail);
+  var response = await fetch('/images/green.png')
+  var blob = await response.blob();
+  var img = await createImageBitmap(blob);
+  var pattern = ctx.createPattern(img, 'no-repeat');
+  ctx.fillStyle = pattern;
+  ctx.fillRect(0, 0, 100, 50);
 
-});
+  _assertPixel(canvas, 1,1, 0,255,0,255);
+  _assertPixel(canvas, 98,1, 0,255,0,255);
+  _assertPixel(canvas, 1,48, 0,255,0,255);
+  _assertPixel(canvas, 98,48, 0,255,0,255);
+
+}, "");
 </script>
diff --git a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.basic.image.worker.js b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.basic.image.worker.js
index 776f1dba..14cfcbc 100644
--- a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.basic.image.worker.js
+++ b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.basic.image.worker.js
@@ -6,37 +6,24 @@
 importScripts("/resources/testharness.js");
 importScripts("/html/canvas/resources/canvas-tests.js");
 
-var t = async_test("");
-var t_pass = t.done.bind(t);
-var t_fail = t.step_func(function(reason) {
-    throw reason;
-});
-t.step(function() {
+promise_test(async t => {
 
   var canvas = new OffscreenCanvas(100, 50);
   var ctx = canvas.getContext('2d');
 
   ctx.fillStyle = '#f00';
   ctx.fillRect(0, 0, 100, 50);
-  var promise = new Promise(function(resolve, reject) {
-      var xhr = new XMLHttpRequest();
-      xhr.open("GET", '/images/green.png');
-      xhr.responseType = 'blob';
-      xhr.send();
-      xhr.onload = function() {
-          resolve(xhr.response);
-      };
-  });
-  promise.then(function(response) {
-      return createImageBitmap(response).then(bitmap => {
-          var pattern = ctx.createPattern(bitmap, 'no-repeat');
-          ctx.fillStyle = pattern;
-          ctx.fillRect(0, 0, 100, 50);
-          _assertPixel(canvas, 1,1, 0,255,0,255);
-          _assertPixel(canvas, 98,1, 0,255,0,255);
-          _assertPixel(canvas, 1,48, 0,255,0,255);
-          _assertPixel(canvas, 98,48, 0,255,0,255);
-      });
-  }).then(t_pass, t_fail);
-});
+  var response = await fetch('/images/green.png')
+  var blob = await response.blob();
+  var img = await createImageBitmap(blob);
+  var pattern = ctx.createPattern(img, 'no-repeat');
+  ctx.fillStyle = pattern;
+  ctx.fillRect(0, 0, 100, 50);
+
+  _assertPixel(canvas, 1,1, 0,255,0,255);
+  _assertPixel(canvas, 98,1, 0,255,0,255);
+  _assertPixel(canvas, 1,48, 0,255,0,255);
+  _assertPixel(canvas, 98,48, 0,255,0,255);
+  t.done();
+}, "");
 done();
diff --git a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.basic.nocontext.html b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.basic.nocontext.html
index 9fa124277..66e301a0 100644
--- a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.basic.nocontext.html
+++ b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.basic.nocontext.html
@@ -20,13 +20,15 @@
   var canvas = new OffscreenCanvas(100, 50);
   var ctx = canvas.getContext('2d');
 
-  var offscreenCanvas2 = new OffscreenCanvas(100, 50);
-  var pattern = ctx.createPattern(offscreenCanvas2, 'no-repeat');
+  var canvas2 = new OffscreenCanvas(100, 50);
+  var pattern = ctx.createPattern(canvas2, 'no-repeat');
+
   ctx.fillStyle = '#0f0';
   ctx.fillRect(0, 0, 100, 50);
   ctx.fillStyle = '#f00';
   ctx.fillStyle = pattern;
   ctx.fillRect(0, 0, 100, 50);
+
   _assertPixel(canvas, 1,1, 0,255,0,255);
   _assertPixel(canvas, 98,1, 0,255,0,255);
   _assertPixel(canvas, 1,48, 0,255,0,255);
diff --git a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.basic.nocontext.worker.js b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.basic.nocontext.worker.js
index 11c30765..f6c24c82 100644
--- a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.basic.nocontext.worker.js
+++ b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.basic.nocontext.worker.js
@@ -16,13 +16,15 @@
   var canvas = new OffscreenCanvas(100, 50);
   var ctx = canvas.getContext('2d');
 
-  var offscreenCanvas2 = new OffscreenCanvas(100, 50);
-  var pattern = ctx.createPattern(offscreenCanvas2, 'no-repeat');
+  var canvas2 = new OffscreenCanvas(100, 50);
+  var pattern = ctx.createPattern(canvas2, 'no-repeat');
+
   ctx.fillStyle = '#0f0';
   ctx.fillRect(0, 0, 100, 50);
   ctx.fillStyle = '#f00';
   ctx.fillStyle = pattern;
   ctx.fillRect(0, 0, 100, 50);
+
   _assertPixel(canvas, 1,1, 0,255,0,255);
   _assertPixel(canvas, 98,1, 0,255,0,255);
   _assertPixel(canvas, 1,48, 0,255,0,255);
diff --git a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.basic.type.html b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.basic.type.html
new file mode 100644
index 0000000..1edb4fd
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.basic.type.html
@@ -0,0 +1,29 @@
+<!DOCTYPE html>
+<!-- DO NOT EDIT! This test has been generated by /html/canvas/tools/gentest.py. -->
+<title>OffscreenCanvas test: 2d.pattern.basic.type</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/html/canvas/resources/canvas-tests.js"></script>
+
+<h1>2d.pattern.basic.type</h1>
+<p class="desc"></p>
+
+
+<script>
+promise_test(async t => {
+
+  var canvas = new OffscreenCanvas(100, 50);
+  var ctx = canvas.getContext('2d');
+
+  _assertDifferent(window.CanvasPattern, undefined, "window.CanvasPattern", "undefined");
+
+  window.CanvasPattern.prototype.thisImplementsCanvasPattern = true;
+
+  var response = await fetch('/images/green.png')
+  var blob = await response.blob();
+  var img = await createImageBitmap(blob);
+  var pattern = ctx.createPattern(img, 'no-repeat');
+  _assert(pattern.thisImplementsCanvasPattern, "pattern.thisImplementsCanvasPattern");
+
+}, "");
+</script>
diff --git a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.basic.type.worker.js b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.basic.type.worker.js
new file mode 100644
index 0000000..56a8a7d2
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.basic.type.worker.js
@@ -0,0 +1,25 @@
+// DO NOT EDIT! This test has been generated by /html/canvas/tools/gentest.py.
+// OffscreenCanvas test in a worker:2d.pattern.basic.type
+// Description:
+// Note:
+
+importScripts("/resources/testharness.js");
+importScripts("/html/canvas/resources/canvas-tests.js");
+
+promise_test(async t => {
+
+  var canvas = new OffscreenCanvas(100, 50);
+  var ctx = canvas.getContext('2d');
+
+  _assertDifferent(self.CanvasPattern, undefined, "self.CanvasPattern", "undefined");
+
+  self.CanvasPattern.prototype.thisImplementsCanvasPattern = true;
+
+  var response = await fetch('/images/green.png')
+  var blob = await response.blob();
+  var img = await createImageBitmap(blob);
+  var pattern = ctx.createPattern(img, 'no-repeat');
+  _assert(pattern.thisImplementsCanvasPattern, "pattern.thisImplementsCanvasPattern");
+  t.done();
+}, "");
+done();
diff --git a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.basic.zerocanvas.html b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.basic.zerocanvas.html
index c7f6188..7100c83 100644
--- a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.basic.zerocanvas.html
+++ b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.basic.zerocanvas.html
@@ -25,11 +25,13 @@
   _assertSame(canvas.width, 0, "canvas.width", "0");
   _assertSame(canvas.height, 10, "canvas.height", "10");
   assert_throws_dom("INVALID_STATE_ERR", function() { ctx.createPattern(canvas, 'repeat'); });
+
   canvas.width = 10;
   canvas.height = 0;
   _assertSame(canvas.width, 10, "canvas.width", "10");
   _assertSame(canvas.height, 0, "canvas.height", "0");
   assert_throws_dom("INVALID_STATE_ERR", function() { ctx.createPattern(canvas, 'repeat'); });
+
   canvas.width = 0;
   canvas.height = 0;
   _assertSame(canvas.width, 0, "canvas.width", "0");
diff --git a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.basic.zerocanvas.worker.js b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.basic.zerocanvas.worker.js
index caeff7c..9bf487c 100644
--- a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.basic.zerocanvas.worker.js
+++ b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.basic.zerocanvas.worker.js
@@ -21,11 +21,13 @@
   _assertSame(canvas.width, 0, "canvas.width", "0");
   _assertSame(canvas.height, 10, "canvas.height", "10");
   assert_throws_dom("INVALID_STATE_ERR", function() { ctx.createPattern(canvas, 'repeat'); });
+
   canvas.width = 10;
   canvas.height = 0;
   _assertSame(canvas.width, 10, "canvas.width", "10");
   _assertSame(canvas.height, 0, "canvas.height", "0");
   assert_throws_dom("INVALID_STATE_ERR", function() { ctx.createPattern(canvas, 'repeat'); });
+
   canvas.width = 0;
   canvas.height = 0;
   _assertSame(canvas.width, 0, "canvas.width", "0");
diff --git a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.crosscanvas.html b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.crosscanvas.html
index fbb7e52..bdf89021 100644
--- a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.crosscanvas.html
+++ b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.crosscanvas.html
@@ -10,36 +10,22 @@
 
 
 <script>
-var t = async_test("");
-var t_pass = t.done.bind(t);
-var t_fail = t.step_func(function(reason) {
-    throw reason;
-});
-t.step(function() {
+promise_test(async t => {
 
   var canvas = new OffscreenCanvas(100, 50);
   var ctx = canvas.getContext('2d');
 
-  var promise = new Promise(function(resolve, reject) {
-      var xhr = new XMLHttpRequest();
-      xhr.open("GET", '/images/green.png');
-      xhr.responseType = 'blob';
-      xhr.send();
-      xhr.onload = function() {
-          resolve(xhr.response);
-      };
-  });
-  promise.then(function(response) {
-      return createImageBitmap(response).then(bitmap => {
-          var offscreenCanvas2 = new OffscreenCanvas(100, 50);
-          var pattern = offscreenCanvas2.getContext('2d').createPattern(bitmap, 'no-repeat');
-          ctx.fillStyle = '#f00';
-          ctx.fillRect(0, 0, 100, 50);
-          ctx.fillStyle = pattern;
-          ctx.fillRect(0, 0, 100, 50);
-          _assertPixel(canvas, 50,25, 0,255,0,255);
-      });
-  }).then(t_pass, t_fail);
+  var response = await fetch('/images/green.png')
+  var blob = await response.blob();
+  var img = await createImageBitmap(blob);
 
-});
+  var pattern = new OffscreenCanvas(100, 50).getContext('2d').createPattern(img, 'no-repeat');
+  ctx.fillStyle = '#f00';
+  ctx.fillRect(0, 0, 100, 50);
+  ctx.fillStyle = pattern;
+  ctx.fillRect(0, 0, 100, 50);
+
+  _assertPixel(canvas, 50,25, 0,255,0,255);
+
+}, "");
 </script>
diff --git a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.crosscanvas.worker.js b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.crosscanvas.worker.js
index bd24b44..d9004fc0 100644
--- a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.crosscanvas.worker.js
+++ b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.crosscanvas.worker.js
@@ -6,35 +6,22 @@
 importScripts("/resources/testharness.js");
 importScripts("/html/canvas/resources/canvas-tests.js");
 
-var t = async_test("");
-var t_pass = t.done.bind(t);
-var t_fail = t.step_func(function(reason) {
-    throw reason;
-});
-t.step(function() {
+promise_test(async t => {
 
   var canvas = new OffscreenCanvas(100, 50);
   var ctx = canvas.getContext('2d');
 
-  var promise = new Promise(function(resolve, reject) {
-      var xhr = new XMLHttpRequest();
-      xhr.open("GET", '/images/green.png');
-      xhr.responseType = 'blob';
-      xhr.send();
-      xhr.onload = function() {
-          resolve(xhr.response);
-      };
-  });
-  promise.then(function(response) {
-      return createImageBitmap(response).then(bitmap => {
-          var offscreenCanvas2 = new OffscreenCanvas(100, 50);
-          var pattern = offscreenCanvas2.getContext('2d').createPattern(bitmap, 'no-repeat');
-          ctx.fillStyle = '#f00';
-          ctx.fillRect(0, 0, 100, 50);
-          ctx.fillStyle = pattern;
-          ctx.fillRect(0, 0, 100, 50);
-          _assertPixel(canvas, 50,25, 0,255,0,255);
-      });
-  }).then(t_pass, t_fail);
-});
+  var response = await fetch('/images/green.png')
+  var blob = await response.blob();
+  var img = await createImageBitmap(blob);
+
+  var pattern = new OffscreenCanvas(100, 50).getContext('2d').createPattern(img, 'no-repeat');
+  ctx.fillStyle = '#f00';
+  ctx.fillRect(0, 0, 100, 50);
+  ctx.fillStyle = pattern;
+  ctx.fillRect(0, 0, 100, 50);
+
+  _assertPixel(canvas, 50,25, 0,255,0,255);
+  t.done();
+}, "");
 done();
diff --git a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.image.null.html b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.image.null.html
index 4297814b..fbec2586 100644
--- a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.image.null.html
+++ b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.image.null.html
@@ -8,7 +8,7 @@
 <h1>2d.pattern.image.null</h1>
 <p class="desc"></p>
 
-
+<p class="notes">Defined in "Web IDL" (draft)
 <script>
 var t = async_test("");
 var t_pass = t.done.bind(t);
diff --git a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.image.null.worker.js b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.image.null.worker.js
index c7d99696..a68ddfdb 100644
--- a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.image.null.worker.js
+++ b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.image.null.worker.js
@@ -1,7 +1,7 @@
 // DO NOT EDIT! This test has been generated by /html/canvas/tools/gentest.py.
 // OffscreenCanvas test in a worker:2d.pattern.image.null
 // Description:
-// Note:
+// Note:<p class="notes">Defined in "Web IDL" (draft)
 
 importScripts("/resources/testharness.js");
 importScripts("/html/canvas/resources/canvas-tests.js");
diff --git a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.image.string.html b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.image.string.html
index 469f223..ff7355d 100644
--- a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.image.string.html
+++ b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.image.string.html
@@ -8,7 +8,7 @@
 <h1>2d.pattern.image.string</h1>
 <p class="desc"></p>
 
-
+<p class="notes">Defined in "Web IDL" (draft)
 <script>
 var t = async_test("");
 var t_pass = t.done.bind(t);
diff --git a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.image.string.worker.js b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.image.string.worker.js
index f8525e4..dc53d822 100644
--- a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.image.string.worker.js
+++ b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.image.string.worker.js
@@ -1,7 +1,7 @@
 // DO NOT EDIT! This test has been generated by /html/canvas/tools/gentest.py.
 // OffscreenCanvas test in a worker:2d.pattern.image.string
 // Description:
-// Note:
+// Note:<p class="notes">Defined in "Web IDL" (draft)
 
 importScripts("/resources/testharness.js");
 importScripts("/html/canvas/resources/canvas-tests.js");
diff --git a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.image.undefined.html b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.image.undefined.html
index 475d8dd..845f823e 100644
--- a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.image.undefined.html
+++ b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.image.undefined.html
@@ -8,7 +8,7 @@
 <h1>2d.pattern.image.undefined</h1>
 <p class="desc"></p>
 
-
+<p class="notes">Defined in "Web IDL" (draft)
 <script>
 var t = async_test("");
 var t_pass = t.done.bind(t);
diff --git a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.image.undefined.worker.js b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.image.undefined.worker.js
index dbfdbe9..32f5749 100644
--- a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.image.undefined.worker.js
+++ b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.image.undefined.worker.js
@@ -1,7 +1,7 @@
 // DO NOT EDIT! This test has been generated by /html/canvas/tools/gentest.py.
 // OffscreenCanvas test in a worker:2d.pattern.image.undefined
 // Description:
-// Note:
+// Note:<p class="notes">Defined in "Web IDL" (draft)
 
 importScripts("/resources/testharness.js");
 importScripts("/html/canvas/resources/canvas-tests.js");
diff --git a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.modify.canvas1.html b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.modify.canvas1.html
index 11586f5a..ebeb92c7f 100644
--- a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.modify.canvas1.html
+++ b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.modify.canvas1.html
@@ -20,15 +20,19 @@
   var canvas = new OffscreenCanvas(100, 50);
   var ctx = canvas.getContext('2d');
 
-  var offscreenCanvas2 = new OffscreenCanvas(100, 50);
-  var ctx2 = offscreenCanvas2.getContext('2d');
+  var canvas2 = new OffscreenCanvas(100, 50);
+  var ctx2 = canvas2.getContext('2d');
   ctx2.fillStyle = '#0f0';
   ctx2.fillRect(0, 0, 100, 50);
-  var pattern = ctx.createPattern(offscreenCanvas2, 'no-repeat');
+
+  var pattern = ctx.createPattern(canvas2, 'no-repeat');
+
   ctx2.fillStyle = '#f00';
   ctx2.fillRect(0, 0, 100, 50);
+
   ctx.fillStyle = pattern;
   ctx.fillRect(0, 0, 100, 50);
+
   _assertPixel(canvas, 1,1, 0,255,0,255);
   _assertPixel(canvas, 98,1, 0,255,0,255);
   _assertPixel(canvas, 1,48, 0,255,0,255);
diff --git a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.modify.canvas1.worker.js b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.modify.canvas1.worker.js
index 3dde0ef9..8e4f9ed 100644
--- a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.modify.canvas1.worker.js
+++ b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.modify.canvas1.worker.js
@@ -16,15 +16,19 @@
   var canvas = new OffscreenCanvas(100, 50);
   var ctx = canvas.getContext('2d');
 
-  var offscreenCanvas2 = new OffscreenCanvas(100, 50);
-  var ctx2 = offscreenCanvas2.getContext('2d');
+  var canvas2 = new OffscreenCanvas(100, 50);
+  var ctx2 = canvas2.getContext('2d');
   ctx2.fillStyle = '#0f0';
   ctx2.fillRect(0, 0, 100, 50);
-  var pattern = ctx.createPattern(offscreenCanvas2, 'no-repeat');
+
+  var pattern = ctx.createPattern(canvas2, 'no-repeat');
+
   ctx2.fillStyle = '#f00';
   ctx2.fillRect(0, 0, 100, 50);
+
   ctx.fillStyle = pattern;
   ctx.fillRect(0, 0, 100, 50);
+
   _assertPixel(canvas, 1,1, 0,255,0,255);
   _assertPixel(canvas, 98,1, 0,255,0,255);
   _assertPixel(canvas, 1,48, 0,255,0,255);
diff --git a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.modify.canvas2.html b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.modify.canvas2.html
index 568140a..042ebba 100644
--- a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.modify.canvas2.html
+++ b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.modify.canvas2.html
@@ -20,19 +20,23 @@
   var canvas = new OffscreenCanvas(100, 50);
   var ctx = canvas.getContext('2d');
 
-  var offscreenCanvas2 = new OffscreenCanvas(100, 50);
-  var ctx2 = offscreenCanvas2.getContext('2d');
+  var canvas2 = new OffscreenCanvas(100, 50);
+  var ctx2 = canvas2.getContext('2d');
   ctx2.fillStyle = '#0f0';
   ctx2.fillRect(0, 0, 100, 50);
-  var pattern = ctx.createPattern(offscreenCanvas2, 'no-repeat');
+
+  var pattern = ctx.createPattern(canvas2, 'no-repeat');
   ctx.fillStyle = pattern;
   ctx.fillRect(0, 0, 100, 50);
   ctx.fillStyle = '#f00';
   ctx.fillRect(0, 0, 100, 50);
+
   ctx2.fillStyle = '#f00';
   ctx2.fillRect(0, 0, 100, 50);
+
   ctx.fillStyle = pattern;
   ctx.fillRect(0, 0, 100, 50);
+
   _assertPixel(canvas, 1,1, 0,255,0,255);
   _assertPixel(canvas, 98,1, 0,255,0,255);
   _assertPixel(canvas, 1,48, 0,255,0,255);
diff --git a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.modify.canvas2.worker.js b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.modify.canvas2.worker.js
index fd70a2fc..ed17db8 100644
--- a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.modify.canvas2.worker.js
+++ b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.modify.canvas2.worker.js
@@ -16,19 +16,23 @@
   var canvas = new OffscreenCanvas(100, 50);
   var ctx = canvas.getContext('2d');
 
-  var offscreenCanvas2 = new OffscreenCanvas(100, 50);
-  var ctx2 = offscreenCanvas2.getContext('2d');
+  var canvas2 = new OffscreenCanvas(100, 50);
+  var ctx2 = canvas2.getContext('2d');
   ctx2.fillStyle = '#0f0';
   ctx2.fillRect(0, 0, 100, 50);
-  var pattern = ctx.createPattern(offscreenCanvas2, 'no-repeat');
+
+  var pattern = ctx.createPattern(canvas2, 'no-repeat');
   ctx.fillStyle = pattern;
   ctx.fillRect(0, 0, 100, 50);
   ctx.fillStyle = '#f00';
   ctx.fillRect(0, 0, 100, 50);
+
   ctx2.fillStyle = '#f00';
   ctx2.fillRect(0, 0, 100, 50);
+
   ctx.fillStyle = pattern;
   ctx.fillRect(0, 0, 100, 50);
+
   _assertPixel(canvas, 1,1, 0,255,0,255);
   _assertPixel(canvas, 98,1, 0,255,0,255);
   _assertPixel(canvas, 1,48, 0,255,0,255);
diff --git a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.paint.norepeat.basic.html b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.paint.norepeat.basic.html
index a60a61e..8ab1c8e 100644
--- a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.paint.norepeat.basic.html
+++ b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.paint.norepeat.basic.html
@@ -10,38 +10,25 @@
 
 
 <script>
-var t = async_test("");
-var t_pass = t.done.bind(t);
-var t_fail = t.step_func(function(reason) {
-    throw reason;
-});
-t.step(function() {
+promise_test(async t => {
 
   var canvas = new OffscreenCanvas(100, 50);
   var ctx = canvas.getContext('2d');
 
   ctx.fillStyle = '#f00';
   ctx.fillRect(0, 0, 100, 50);
-  var promise = new Promise(function(resolve, reject) {
-      var xhr = new XMLHttpRequest();
-      xhr.open("GET", '/images/green.png');
-      xhr.responseType = 'blob';
-      xhr.send();
-      xhr.onload = function() {
-          resolve(xhr.response);
-      };
-  });
-  promise.then(function(response) {
-      return createImageBitmap(response).then(bitmap => {
-          var pattern = ctx.createPattern(bitmap, 'no-repeat');
-          ctx.fillStyle = pattern;
-          ctx.fillRect(0, 0, 100, 50);
-          _assertPixel(canvas, 1,1, 0,255,0,255);
-          _assertPixel(canvas, 98,1, 0,255,0,255);
-          _assertPixel(canvas, 1,48, 0,255,0,255);
-          _assertPixel(canvas, 98,48, 0,255,0,255);
-      });
-  }).then(t_pass, t_fail);
 
-});
+  var response = await fetch('/images/green.png')
+  var blob = await response.blob();
+  var img = await createImageBitmap(blob);
+  var pattern = ctx.createPattern(img, 'no-repeat');
+  ctx.fillStyle = pattern;
+  ctx.fillRect(0, 0, 100, 50);
+
+  _assertPixel(canvas, 1,1, 0,255,0,255);
+  _assertPixel(canvas, 98,1, 0,255,0,255);
+  _assertPixel(canvas, 1,48, 0,255,0,255);
+  _assertPixel(canvas, 98,48, 0,255,0,255);
+
+}, "");
 </script>
diff --git a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.paint.norepeat.basic.worker.js b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.paint.norepeat.basic.worker.js
index 13c5dd2..9ac4e16 100644
--- a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.paint.norepeat.basic.worker.js
+++ b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.paint.norepeat.basic.worker.js
@@ -6,37 +6,25 @@
 importScripts("/resources/testharness.js");
 importScripts("/html/canvas/resources/canvas-tests.js");
 
-var t = async_test("");
-var t_pass = t.done.bind(t);
-var t_fail = t.step_func(function(reason) {
-    throw reason;
-});
-t.step(function() {
+promise_test(async t => {
 
   var canvas = new OffscreenCanvas(100, 50);
   var ctx = canvas.getContext('2d');
 
   ctx.fillStyle = '#f00';
   ctx.fillRect(0, 0, 100, 50);
-  var promise = new Promise(function(resolve, reject) {
-      var xhr = new XMLHttpRequest();
-      xhr.open("GET", '/images/green.png');
-      xhr.responseType = 'blob';
-      xhr.send();
-      xhr.onload = function() {
-          resolve(xhr.response);
-      };
-  });
-  promise.then(function(response) {
-      return createImageBitmap(response).then(bitmap => {
-          var pattern = ctx.createPattern(bitmap, 'no-repeat');
-          ctx.fillStyle = pattern;
-          ctx.fillRect(0, 0, 100, 50);
-          _assertPixel(canvas, 1,1, 0,255,0,255);
-          _assertPixel(canvas, 98,1, 0,255,0,255);
-          _assertPixel(canvas, 1,48, 0,255,0,255);
-          _assertPixel(canvas, 98,48, 0,255,0,255);
-      });
-  }).then(t_pass, t_fail);
-});
+
+  var response = await fetch('/images/green.png')
+  var blob = await response.blob();
+  var img = await createImageBitmap(blob);
+  var pattern = ctx.createPattern(img, 'no-repeat');
+  ctx.fillStyle = pattern;
+  ctx.fillRect(0, 0, 100, 50);
+
+  _assertPixel(canvas, 1,1, 0,255,0,255);
+  _assertPixel(canvas, 98,1, 0,255,0,255);
+  _assertPixel(canvas, 1,48, 0,255,0,255);
+  _assertPixel(canvas, 98,48, 0,255,0,255);
+  t.done();
+}, "");
 done();
diff --git a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.paint.norepeat.coord1.html b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.paint.norepeat.coord1.html
index 4a254b3..4bdd356 100644
--- a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.paint.norepeat.coord1.html
+++ b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.paint.norepeat.coord1.html
@@ -10,12 +10,7 @@
 
 
 <script>
-var t = async_test("");
-var t_pass = t.done.bind(t);
-var t_fail = t.step_func(function(reason) {
-    throw reason;
-});
-t.step(function() {
+promise_test(async t => {
 
   var canvas = new OffscreenCanvas(100, 50);
   var ctx = canvas.getContext('2d');
@@ -24,27 +19,19 @@
   ctx.fillRect(0, 0, 50, 50);
   ctx.fillStyle = '#f00';
   ctx.fillRect(50, 0, 50, 50);
-  var promise = new Promise(function(resolve, reject) {
-      var xhr = new XMLHttpRequest();
-      xhr.open("GET", '/images/green.png');
-      xhr.responseType = 'blob';
-      xhr.send();
-      xhr.onload = function() {
-          resolve(xhr.response);
-      };
-  });
-  promise.then(function(response) {
-      return createImageBitmap(response).then(bitmap => {
-          var pattern = ctx.createPattern(bitmap, 'no-repeat');
-          ctx.fillStyle = pattern;
-          ctx.translate(50, 0);
-          ctx.fillRect(-50, 0, 100, 50);
-          _assertPixel(canvas, 1,1, 0,255,0,255);
-          _assertPixel(canvas, 98,1, 0,255,0,255);
-          _assertPixel(canvas, 1,48, 0,255,0,255);
-          _assertPixel(canvas, 98,48, 0,255,0,255);
-      });
-  }).then(t_pass, t_fail);
 
-});
+  var response = await fetch('/images/green.png')
+  var blob = await response.blob();
+  var img = await createImageBitmap(blob);
+  var pattern = ctx.createPattern(img, 'no-repeat');
+  ctx.fillStyle = pattern;
+  ctx.translate(50, 0);
+  ctx.fillRect(-50, 0, 100, 50);
+
+  _assertPixel(canvas, 1,1, 0,255,0,255);
+  _assertPixel(canvas, 98,1, 0,255,0,255);
+  _assertPixel(canvas, 1,48, 0,255,0,255);
+  _assertPixel(canvas, 98,48, 0,255,0,255);
+
+}, "");
 </script>
diff --git a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.paint.norepeat.coord1.worker.js b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.paint.norepeat.coord1.worker.js
index 948da3a..6c4a140 100644
--- a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.paint.norepeat.coord1.worker.js
+++ b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.paint.norepeat.coord1.worker.js
@@ -6,12 +6,7 @@
 importScripts("/resources/testharness.js");
 importScripts("/html/canvas/resources/canvas-tests.js");
 
-var t = async_test("");
-var t_pass = t.done.bind(t);
-var t_fail = t.step_func(function(reason) {
-    throw reason;
-});
-t.step(function() {
+promise_test(async t => {
 
   var canvas = new OffscreenCanvas(100, 50);
   var ctx = canvas.getContext('2d');
@@ -20,26 +15,19 @@
   ctx.fillRect(0, 0, 50, 50);
   ctx.fillStyle = '#f00';
   ctx.fillRect(50, 0, 50, 50);
-  var promise = new Promise(function(resolve, reject) {
-      var xhr = new XMLHttpRequest();
-      xhr.open("GET", '/images/green.png');
-      xhr.responseType = 'blob';
-      xhr.send();
-      xhr.onload = function() {
-          resolve(xhr.response);
-      };
-  });
-  promise.then(function(response) {
-      return createImageBitmap(response).then(bitmap => {
-          var pattern = ctx.createPattern(bitmap, 'no-repeat');
-          ctx.fillStyle = pattern;
-          ctx.translate(50, 0);
-          ctx.fillRect(-50, 0, 100, 50);
-          _assertPixel(canvas, 1,1, 0,255,0,255);
-          _assertPixel(canvas, 98,1, 0,255,0,255);
-          _assertPixel(canvas, 1,48, 0,255,0,255);
-          _assertPixel(canvas, 98,48, 0,255,0,255);
-      });
-  }).then(t_pass, t_fail);
-});
+
+  var response = await fetch('/images/green.png')
+  var blob = await response.blob();
+  var img = await createImageBitmap(blob);
+  var pattern = ctx.createPattern(img, 'no-repeat');
+  ctx.fillStyle = pattern;
+  ctx.translate(50, 0);
+  ctx.fillRect(-50, 0, 100, 50);
+
+  _assertPixel(canvas, 1,1, 0,255,0,255);
+  _assertPixel(canvas, 98,1, 0,255,0,255);
+  _assertPixel(canvas, 1,48, 0,255,0,255);
+  _assertPixel(canvas, 98,48, 0,255,0,255);
+  t.done();
+}, "");
 done();
diff --git a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.paint.norepeat.coord2.html b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.paint.norepeat.coord2.html
index 306428a6..b9164ec6 100644
--- a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.paint.norepeat.coord2.html
+++ b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.paint.norepeat.coord2.html
@@ -10,41 +10,29 @@
 
 
 <script>
-var t = async_test("");
-var t_pass = t.done.bind(t);
-var t_fail = t.step_func(function(reason) {
-    throw reason;
-});
-t.step(function() {
+promise_test(async t => {
 
   var canvas = new OffscreenCanvas(100, 50);
   var ctx = canvas.getContext('2d');
 
-  var promise = new Promise(function(resolve, reject) {
-      var xhr = new XMLHttpRequest();
-      xhr.open("GET", '/images/green.png');
-      xhr.responseType = 'blob';
-      xhr.send();
-      xhr.onload = function() {
-          resolve(xhr.response);
-      };
-  });
-  promise.then(function(response) {
-      return createImageBitmap(response).then(bitmap => {
-          var pattern = ctx.createPattern(bitmap, 'no-repeat');
-          ctx.fillStyle = pattern;
-          ctx.fillRect(0, 0, 50, 50);
-          ctx.fillStyle = '#f00';
-          ctx.fillRect(50, 0, 50, 50);
-          ctx.fillStyle = pattern;
-          ctx.translate(50, 0);
-          ctx.fillRect(-50, 0, 100, 50);
-          _assertPixel(canvas, 1,1, 0,255,0,255);
-          _assertPixel(canvas, 98,1, 0,255,0,255);
-          _assertPixel(canvas, 1,48, 0,255,0,255);
-          _assertPixel(canvas, 98,48, 0,255,0,255);
-      });
-  }).then(t_pass, t_fail);
+  var response = await fetch('/images/green.png')
+  var blob = await response.blob();
+  var img = await createImageBitmap(blob);
+  var pattern = ctx.createPattern(img, 'no-repeat');
+  ctx.fillStyle = pattern;
+  ctx.fillRect(0, 0, 50, 50);
 
-});
+  ctx.fillStyle = '#f00';
+  ctx.fillRect(50, 0, 50, 50);
+
+  ctx.fillStyle = pattern;
+  ctx.translate(50, 0);
+  ctx.fillRect(-50, 0, 100, 50);
+
+  _assertPixel(canvas, 1,1, 0,255,0,255);
+  _assertPixel(canvas, 98,1, 0,255,0,255);
+  _assertPixel(canvas, 1,48, 0,255,0,255);
+  _assertPixel(canvas, 98,48, 0,255,0,255);
+
+}, "");
 </script>
diff --git a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.paint.norepeat.coord2.worker.js b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.paint.norepeat.coord2.worker.js
index fa90ff65..d2fdd86 100644
--- a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.paint.norepeat.coord2.worker.js
+++ b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.paint.norepeat.coord2.worker.js
@@ -6,40 +6,29 @@
 importScripts("/resources/testharness.js");
 importScripts("/html/canvas/resources/canvas-tests.js");
 
-var t = async_test("");
-var t_pass = t.done.bind(t);
-var t_fail = t.step_func(function(reason) {
-    throw reason;
-});
-t.step(function() {
+promise_test(async t => {
 
   var canvas = new OffscreenCanvas(100, 50);
   var ctx = canvas.getContext('2d');
 
-  var promise = new Promise(function(resolve, reject) {
-      var xhr = new XMLHttpRequest();
-      xhr.open("GET", '/images/green.png');
-      xhr.responseType = 'blob';
-      xhr.send();
-      xhr.onload = function() {
-          resolve(xhr.response);
-      };
-  });
-  promise.then(function(response) {
-      return createImageBitmap(response).then(bitmap => {
-          var pattern = ctx.createPattern(bitmap, 'no-repeat');
-          ctx.fillStyle = pattern;
-          ctx.fillRect(0, 0, 50, 50);
-          ctx.fillStyle = '#f00';
-          ctx.fillRect(50, 0, 50, 50);
-          ctx.fillStyle = pattern;
-          ctx.translate(50, 0);
-          ctx.fillRect(-50, 0, 100, 50);
-          _assertPixel(canvas, 1,1, 0,255,0,255);
-          _assertPixel(canvas, 98,1, 0,255,0,255);
-          _assertPixel(canvas, 1,48, 0,255,0,255);
-          _assertPixel(canvas, 98,48, 0,255,0,255);
-      });
-  }).then(t_pass, t_fail);
-});
+  var response = await fetch('/images/green.png')
+  var blob = await response.blob();
+  var img = await createImageBitmap(blob);
+  var pattern = ctx.createPattern(img, 'no-repeat');
+  ctx.fillStyle = pattern;
+  ctx.fillRect(0, 0, 50, 50);
+
+  ctx.fillStyle = '#f00';
+  ctx.fillRect(50, 0, 50, 50);
+
+  ctx.fillStyle = pattern;
+  ctx.translate(50, 0);
+  ctx.fillRect(-50, 0, 100, 50);
+
+  _assertPixel(canvas, 1,1, 0,255,0,255);
+  _assertPixel(canvas, 98,1, 0,255,0,255);
+  _assertPixel(canvas, 1,48, 0,255,0,255);
+  _assertPixel(canvas, 98,48, 0,255,0,255);
+  t.done();
+}, "");
 done();
diff --git a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.paint.norepeat.coord3.html b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.paint.norepeat.coord3.html
index 556fca6b..e2983948 100644
--- a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.paint.norepeat.coord3.html
+++ b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.paint.norepeat.coord3.html
@@ -10,41 +10,29 @@
 
 
 <script>
-var t = async_test("");
-var t_pass = t.done.bind(t);
-var t_fail = t.step_func(function(reason) {
-    throw reason;
-});
-t.step(function() {
+promise_test(async t => {
 
   var canvas = new OffscreenCanvas(100, 50);
   var ctx = canvas.getContext('2d');
 
   ctx.fillStyle = '#0f0';
   ctx.fillRect(0, 0, 100, 50);
-  var promise = new Promise(function(resolve, reject) {
-      var xhr = new XMLHttpRequest();
-      xhr.open("GET", '/images/red.png');
-      xhr.responseType = 'blob';
-      xhr.send();
-      xhr.onload = function() {
-          resolve(xhr.response);
-      };
-  });
-  promise.then(function(response) {
-      return createImageBitmap(response).then(bitmap => {
-          var pattern = ctx.createPattern(bitmap, 'no-repeat');
-          ctx.fillStyle = pattern;
-          ctx.translate(50, 25);
-          ctx.fillRect(-50, -25, 100, 50);
-          ctx.fillStyle = '#0f0';
-          ctx.fillRect(0, 0, 50, 25);
-          _assertPixel(canvas, 1,1, 0,255,0,255);
-          _assertPixel(canvas, 98,1, 0,255,0,255);
-          _assertPixel(canvas, 1,48, 0,255,0,255);
-          _assertPixel(canvas, 98,48, 0,255,0,255);
-      });
-  }).then(t_pass, t_fail);
 
-});
+  var response = await fetch('/images/red.png')
+  var blob = await response.blob();
+  var img = await createImageBitmap(blob);
+  var pattern = ctx.createPattern(img, 'no-repeat');
+  ctx.fillStyle = pattern;
+  ctx.translate(50, 25);
+  ctx.fillRect(-50, -25, 100, 50);
+
+  ctx.fillStyle = '#0f0';
+  ctx.fillRect(0, 0, 50, 25);
+
+  _assertPixel(canvas, 1,1, 0,255,0,255);
+  _assertPixel(canvas, 98,1, 0,255,0,255);
+  _assertPixel(canvas, 1,48, 0,255,0,255);
+  _assertPixel(canvas, 98,48, 0,255,0,255);
+
+}, "");
 </script>
diff --git a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.paint.norepeat.coord3.worker.js b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.paint.norepeat.coord3.worker.js
index 834516e..584a5d6 100644
--- a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.paint.norepeat.coord3.worker.js
+++ b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.paint.norepeat.coord3.worker.js
@@ -6,40 +6,29 @@
 importScripts("/resources/testharness.js");
 importScripts("/html/canvas/resources/canvas-tests.js");
 
-var t = async_test("");
-var t_pass = t.done.bind(t);
-var t_fail = t.step_func(function(reason) {
-    throw reason;
-});
-t.step(function() {
+promise_test(async t => {
 
   var canvas = new OffscreenCanvas(100, 50);
   var ctx = canvas.getContext('2d');
 
   ctx.fillStyle = '#0f0';
   ctx.fillRect(0, 0, 100, 50);
-  var promise = new Promise(function(resolve, reject) {
-      var xhr = new XMLHttpRequest();
-      xhr.open("GET", '/images/red.png');
-      xhr.responseType = 'blob';
-      xhr.send();
-      xhr.onload = function() {
-          resolve(xhr.response);
-      };
-  });
-  promise.then(function(response) {
-      return createImageBitmap(response).then(bitmap => {
-          var pattern = ctx.createPattern(bitmap, 'no-repeat');
-          ctx.fillStyle = pattern;
-          ctx.translate(50, 25);
-          ctx.fillRect(-50, -25, 100, 50);
-          ctx.fillStyle = '#0f0';
-          ctx.fillRect(0, 0, 50, 25);
-          _assertPixel(canvas, 1,1, 0,255,0,255);
-          _assertPixel(canvas, 98,1, 0,255,0,255);
-          _assertPixel(canvas, 1,48, 0,255,0,255);
-          _assertPixel(canvas, 98,48, 0,255,0,255);
-      });
-  }).then(t_pass, t_fail);
-});
+
+  var response = await fetch('/images/red.png')
+  var blob = await response.blob();
+  var img = await createImageBitmap(blob);
+  var pattern = ctx.createPattern(img, 'no-repeat');
+  ctx.fillStyle = pattern;
+  ctx.translate(50, 25);
+  ctx.fillRect(-50, -25, 100, 50);
+
+  ctx.fillStyle = '#0f0';
+  ctx.fillRect(0, 0, 50, 25);
+
+  _assertPixel(canvas, 1,1, 0,255,0,255);
+  _assertPixel(canvas, 98,1, 0,255,0,255);
+  _assertPixel(canvas, 1,48, 0,255,0,255);
+  _assertPixel(canvas, 98,48, 0,255,0,255);
+  t.done();
+}, "");
 done();
diff --git a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.paint.norepeat.outside.html b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.paint.norepeat.outside.html
index c049b79..43a718d 100644
--- a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.paint.norepeat.outside.html
+++ b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.paint.norepeat.outside.html
@@ -10,43 +10,31 @@
 
 
 <script>
-var t = async_test("");
-var t_pass = t.done.bind(t);
-var t_fail = t.step_func(function(reason) {
-    throw reason;
-});
-t.step(function() {
+promise_test(async t => {
 
   var canvas = new OffscreenCanvas(100, 50);
   var ctx = canvas.getContext('2d');
 
   ctx.fillStyle = '#f00';
   ctx.fillRect(0, 0, 100, 50);
-  var promise = new Promise(function(resolve, reject) {
-      var xhr = new XMLHttpRequest();
-      xhr.open("GET", '/images/red.png');
-      xhr.responseType = 'blob';
-      xhr.send();
-      xhr.onload = function() {
-          resolve(xhr.response);
-      };
-  });
-  promise.then(function(response) {
-      return createImageBitmap(response).then(bitmap => {
-          var pattern = ctx.createPattern(bitmap, 'no-repeat');
-          ctx.fillStyle = '#0f0';
-          ctx.fillRect(0, 0, 100, 50);
-          ctx.fillStyle = pattern;
-          ctx.fillRect(0, -50, 100, 50);
-          ctx.fillRect(-100, 0, 100, 50);
-          ctx.fillRect(0, 50, 100, 50);
-          ctx.fillRect(100, 0, 100, 50);
-          _assertPixel(canvas, 1,1, 0,255,0,255);
-          _assertPixel(canvas, 98,1, 0,255,0,255);
-          _assertPixel(canvas, 1,48, 0,255,0,255);
-          _assertPixel(canvas, 98,48, 0,255,0,255);
-      });
-  }).then(t_pass, t_fail);
 
-});
+  var response = await fetch('/images/red.png')
+  var blob = await response.blob();
+  var img = await createImageBitmap(blob);
+  var pattern = ctx.createPattern(img, 'no-repeat');
+  ctx.fillStyle = '#0f0';
+  ctx.fillRect(0, 0, 100, 50);
+
+  ctx.fillStyle = pattern;
+  ctx.fillRect(0, -50, 100, 50);
+  ctx.fillRect(-100, 0, 100, 50);
+  ctx.fillRect(0, 50, 100, 50);
+  ctx.fillRect(100, 0, 100, 50);
+
+  _assertPixel(canvas, 1,1, 0,255,0,255);
+  _assertPixel(canvas, 98,1, 0,255,0,255);
+  _assertPixel(canvas, 1,48, 0,255,0,255);
+  _assertPixel(canvas, 98,48, 0,255,0,255);
+
+}, "");
 </script>
diff --git a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.paint.norepeat.outside.worker.js b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.paint.norepeat.outside.worker.js
index 93e7a312..0b5fef9 100644
--- a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.paint.norepeat.outside.worker.js
+++ b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.paint.norepeat.outside.worker.js
@@ -6,42 +6,31 @@
 importScripts("/resources/testharness.js");
 importScripts("/html/canvas/resources/canvas-tests.js");
 
-var t = async_test("");
-var t_pass = t.done.bind(t);
-var t_fail = t.step_func(function(reason) {
-    throw reason;
-});
-t.step(function() {
+promise_test(async t => {
 
   var canvas = new OffscreenCanvas(100, 50);
   var ctx = canvas.getContext('2d');
 
   ctx.fillStyle = '#f00';
   ctx.fillRect(0, 0, 100, 50);
-  var promise = new Promise(function(resolve, reject) {
-      var xhr = new XMLHttpRequest();
-      xhr.open("GET", '/images/red.png');
-      xhr.responseType = 'blob';
-      xhr.send();
-      xhr.onload = function() {
-          resolve(xhr.response);
-      };
-  });
-  promise.then(function(response) {
-      return createImageBitmap(response).then(bitmap => {
-          var pattern = ctx.createPattern(bitmap, 'no-repeat');
-          ctx.fillStyle = '#0f0';
-          ctx.fillRect(0, 0, 100, 50);
-          ctx.fillStyle = pattern;
-          ctx.fillRect(0, -50, 100, 50);
-          ctx.fillRect(-100, 0, 100, 50);
-          ctx.fillRect(0, 50, 100, 50);
-          ctx.fillRect(100, 0, 100, 50);
-          _assertPixel(canvas, 1,1, 0,255,0,255);
-          _assertPixel(canvas, 98,1, 0,255,0,255);
-          _assertPixel(canvas, 1,48, 0,255,0,255);
-          _assertPixel(canvas, 98,48, 0,255,0,255);
-      });
-  }).then(t_pass, t_fail);
-});
+
+  var response = await fetch('/images/red.png')
+  var blob = await response.blob();
+  var img = await createImageBitmap(blob);
+  var pattern = ctx.createPattern(img, 'no-repeat');
+  ctx.fillStyle = '#0f0';
+  ctx.fillRect(0, 0, 100, 50);
+
+  ctx.fillStyle = pattern;
+  ctx.fillRect(0, -50, 100, 50);
+  ctx.fillRect(-100, 0, 100, 50);
+  ctx.fillRect(0, 50, 100, 50);
+  ctx.fillRect(100, 0, 100, 50);
+
+  _assertPixel(canvas, 1,1, 0,255,0,255);
+  _assertPixel(canvas, 98,1, 0,255,0,255);
+  _assertPixel(canvas, 1,48, 0,255,0,255);
+  _assertPixel(canvas, 98,48, 0,255,0,255);
+  t.done();
+}, "");
 done();
diff --git a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.paint.orientation.canvas.html b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.paint.orientation.canvas.html
index f730557..b63535c 100644
--- a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.paint.orientation.canvas.html
+++ b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.paint.orientation.canvas.html
@@ -22,17 +22,20 @@
 
   ctx.fillStyle = '#f00';
   ctx.fillRect(0, 0, 100, 50);
-  var offscreenCanvas2 = new OffscreenCanvas(100, 50);
-  var ctx2 = offscreenCanvas2.getContext('2d');
+
+  var canvas2 = new OffscreenCanvas(100, 50);
+  var ctx2 = canvas2.getContext('2d');
   ctx2.fillStyle = '#f00';
   ctx2.fillRect(0, 0, 100, 25);
   ctx2.fillStyle = '#0f0';
   ctx2.fillRect(0, 25, 100, 25);
-  var pattern = ctx.createPattern(offscreenCanvas2, 'no-repeat');
+
+  var pattern = ctx.createPattern(canvas2, 'no-repeat');
   ctx.fillStyle = pattern;
   ctx.fillRect(0, 0, 100, 50);
   ctx.fillStyle = '#0f0';
   ctx.fillRect(0, 0, 100, 25);
+
   _assertPixel(canvas, 1,1, 0,255,0,255);
   _assertPixel(canvas, 98,1, 0,255,0,255);
   _assertPixel(canvas, 1,48, 0,255,0,255);
diff --git a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.paint.orientation.canvas.worker.js b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.paint.orientation.canvas.worker.js
index 7896c70..2fbe52a 100644
--- a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.paint.orientation.canvas.worker.js
+++ b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.paint.orientation.canvas.worker.js
@@ -18,17 +18,20 @@
 
   ctx.fillStyle = '#f00';
   ctx.fillRect(0, 0, 100, 50);
-  var offscreenCanvas2 = new OffscreenCanvas(100, 50);
-  var ctx2 = offscreenCanvas2.getContext('2d');
+
+  var canvas2 = new OffscreenCanvas(100, 50);
+  var ctx2 = canvas2.getContext('2d');
   ctx2.fillStyle = '#f00';
   ctx2.fillRect(0, 0, 100, 25);
   ctx2.fillStyle = '#0f0';
   ctx2.fillRect(0, 25, 100, 25);
-  var pattern = ctx.createPattern(offscreenCanvas2, 'no-repeat');
+
+  var pattern = ctx.createPattern(canvas2, 'no-repeat');
   ctx.fillStyle = pattern;
   ctx.fillRect(0, 0, 100, 50);
   ctx.fillStyle = '#0f0';
   ctx.fillRect(0, 0, 100, 25);
+
   _assertPixel(canvas, 1,1, 0,255,0,255);
   _assertPixel(canvas, 98,1, 0,255,0,255);
   _assertPixel(canvas, 1,48, 0,255,0,255);
diff --git a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.paint.orientation.image.html b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.paint.orientation.image.html
index c5dc17441..0b36459 100644
--- a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.paint.orientation.image.html
+++ b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.paint.orientation.image.html
@@ -10,43 +10,31 @@
 
 
 <script>
-var t = async_test("Image patterns do not get flipped when painted");
-var t_pass = t.done.bind(t);
-var t_fail = t.step_func(function(reason) {
-    throw reason;
-});
-t.step(function() {
+promise_test(async t => {
 
   var canvas = new OffscreenCanvas(100, 50);
   var ctx = canvas.getContext('2d');
 
   ctx.fillStyle = '#f00';
   ctx.fillRect(0, 0, 100, 50);
-  var promise = new Promise(function(resolve, reject) {
-      var xhr = new XMLHttpRequest();
-      xhr.open("GET", '/images/rrgg-256x256.png');
-      xhr.responseType = 'blob';
-      xhr.send();
-      xhr.onload = function() {
-          resolve(xhr.response);
-      };
-  });
-  promise.then(function(response) {
-      return createImageBitmap(response).then(bitmap => {
-          var pattern = ctx.createPattern(bitmap, 'no-repeat');
-          ctx.fillStyle = pattern;
-          ctx.save();
-          ctx.translate(0, -103);
-          ctx.fillRect(0, 103, 100, 50);
-          ctx.restore();
-          ctx.fillStyle = '#0f0';
-          ctx.fillRect(0, 0, 100, 25);
-          _assertPixel(canvas, 1,1, 0,255,0,255);
-          _assertPixel(canvas, 98,1, 0,255,0,255);
-          _assertPixel(canvas, 1,48, 0,255,0,255);
-          _assertPixel(canvas, 98,48, 0,255,0,255);
-      });
-  }).then(t_pass, t_fail);
 
-});
+  var response = await fetch('/images/rrgg-256x256.png')
+  var blob = await response.blob();
+  var img = await createImageBitmap(blob);
+  var pattern = ctx.createPattern(img, 'no-repeat');
+  ctx.fillStyle = pattern;
+  ctx.save();
+  ctx.translate(0, -103);
+  ctx.fillRect(0, 103, 100, 50);
+  ctx.restore();
+
+  ctx.fillStyle = '#0f0';
+  ctx.fillRect(0, 0, 100, 25);
+
+  _assertPixel(canvas, 1,1, 0,255,0,255);
+  _assertPixel(canvas, 98,1, 0,255,0,255);
+  _assertPixel(canvas, 1,48, 0,255,0,255);
+  _assertPixel(canvas, 98,48, 0,255,0,255);
+
+}, "Image patterns do not get flipped when painted");
 </script>
diff --git a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.paint.orientation.image.worker.js b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.paint.orientation.image.worker.js
index 56b072b..b303b2d8 100644
--- a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.paint.orientation.image.worker.js
+++ b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.paint.orientation.image.worker.js
@@ -6,42 +6,31 @@
 importScripts("/resources/testharness.js");
 importScripts("/html/canvas/resources/canvas-tests.js");
 
-var t = async_test("Image patterns do not get flipped when painted");
-var t_pass = t.done.bind(t);
-var t_fail = t.step_func(function(reason) {
-    throw reason;
-});
-t.step(function() {
+promise_test(async t => {
 
   var canvas = new OffscreenCanvas(100, 50);
   var ctx = canvas.getContext('2d');
 
   ctx.fillStyle = '#f00';
   ctx.fillRect(0, 0, 100, 50);
-  var promise = new Promise(function(resolve, reject) {
-      var xhr = new XMLHttpRequest();
-      xhr.open("GET", '/images/rrgg-256x256.png');
-      xhr.responseType = 'blob';
-      xhr.send();
-      xhr.onload = function() {
-          resolve(xhr.response);
-      };
-  });
-  promise.then(function(response) {
-      return createImageBitmap(response).then(bitmap => {
-          var pattern = ctx.createPattern(bitmap, 'no-repeat');
-          ctx.fillStyle = pattern;
-          ctx.save();
-          ctx.translate(0, -103);
-          ctx.fillRect(0, 103, 100, 50);
-          ctx.restore();
-          ctx.fillStyle = '#0f0';
-          ctx.fillRect(0, 0, 100, 25);
-          _assertPixel(canvas, 1,1, 0,255,0,255);
-          _assertPixel(canvas, 98,1, 0,255,0,255);
-          _assertPixel(canvas, 1,48, 0,255,0,255);
-          _assertPixel(canvas, 98,48, 0,255,0,255);
-      });
-  }).then(t_pass, t_fail);
-});
+
+  var response = await fetch('/images/rrgg-256x256.png')
+  var blob = await response.blob();
+  var img = await createImageBitmap(blob);
+  var pattern = ctx.createPattern(img, 'no-repeat');
+  ctx.fillStyle = pattern;
+  ctx.save();
+  ctx.translate(0, -103);
+  ctx.fillRect(0, 103, 100, 50);
+  ctx.restore();
+
+  ctx.fillStyle = '#0f0';
+  ctx.fillRect(0, 0, 100, 25);
+
+  _assertPixel(canvas, 1,1, 0,255,0,255);
+  _assertPixel(canvas, 98,1, 0,255,0,255);
+  _assertPixel(canvas, 1,48, 0,255,0,255);
+  _assertPixel(canvas, 98,48, 0,255,0,255);
+  t.done();
+}, "Image patterns do not get flipped when painted");
 done();
diff --git a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.paint.repeat.basic-expected.txt b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.paint.repeat.basic-expected.txt
deleted file mode 100644
index 41cd629..0000000
--- a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.paint.repeat.basic-expected.txt
+++ /dev/null
@@ -1,4 +0,0 @@
-This is a testharness.js-based test.
-FAIL OffscreenCanvas test: 2d.pattern.paint.repeat.basic assert_equals: Red channel of the pixel at (98, 1) expected 0 but got 255
-Harness: the test ran to completion.
-
diff --git a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.paint.repeat.basic.html b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.paint.repeat.basic.html
index 9327111e..b6647f7 100644
--- a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.paint.repeat.basic.html
+++ b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.paint.repeat.basic.html
@@ -10,38 +10,25 @@
 
 
 <script>
-var t = async_test("");
-var t_pass = t.done.bind(t);
-var t_fail = t.step_func(function(reason) {
-    throw reason;
-});
-t.step(function() {
+promise_test(async t => {
 
   var canvas = new OffscreenCanvas(100, 50);
   var ctx = canvas.getContext('2d');
 
   ctx.fillStyle = '#f00';
   ctx.fillRect(0, 0, 100, 50);
-  var promise = new Promise(function(resolve, reject) {
-      var xhr = new XMLHttpRequest();
-      xhr.open("GET", '/images/green-16x16.png');
-      xhr.responseType = 'blob';
-      xhr.send();
-      xhr.onload = function() {
-          resolve(xhr.response);
-      };
-  });
-  promise.then(function(response) {
-      return createImageBitmap(response).then(bitmap => {
-          var pattern = ctx.createPattern(bitmap, 'no-repeat');
-          ctx.fillStyle = pattern;
-          ctx.fillRect(0, 0, 100, 50);
-          _assertPixel(canvas, 1,1, 0,255,0,255);
-          _assertPixel(canvas, 98,1, 0,255,0,255);
-          _assertPixel(canvas, 1,48, 0,255,0,255);
-          _assertPixel(canvas, 98,48, 0,255,0,255);
-      });
-  }).then(t_pass, t_fail);
 
-});
+  var response = await fetch('/images/green-16x16.png')
+  var blob = await response.blob();
+  var img = await createImageBitmap(blob);
+  var pattern = ctx.createPattern(img, 'repeat');
+  ctx.fillStyle = pattern;
+  ctx.fillRect(0, 0, 100, 50);
+
+  _assertPixel(canvas, 1,1, 0,255,0,255);
+  _assertPixel(canvas, 98,1, 0,255,0,255);
+  _assertPixel(canvas, 1,48, 0,255,0,255);
+  _assertPixel(canvas, 98,48, 0,255,0,255);
+
+}, "");
 </script>
diff --git a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.paint.repeat.basic.html.ini b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.paint.repeat.basic.html.ini
deleted file mode 100644
index 5cc91906..0000000
--- a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.paint.repeat.basic.html.ini
+++ /dev/null
@@ -1,3 +0,0 @@
-[2d.pattern.paint.repeat.basic.html]
-  [OffscreenCanvas test: 2d.pattern.paint.repeat.basic]
-    expected: FAIL
diff --git a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.paint.repeat.basic.worker-expected.txt b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.paint.repeat.basic.worker-expected.txt
deleted file mode 100644
index f3c6b87..0000000
--- a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.paint.repeat.basic.worker-expected.txt
+++ /dev/null
@@ -1,4 +0,0 @@
-This is a testharness.js-based test.
-FAIL 2d assert_equals: Red channel of the pixel at (98, 1) expected 0 but got 255
-Harness: the test ran to completion.
-
diff --git a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.paint.repeat.basic.worker.js b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.paint.repeat.basic.worker.js
index 6e77679..37f4c042 100644
--- a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.paint.repeat.basic.worker.js
+++ b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.paint.repeat.basic.worker.js
@@ -6,37 +6,25 @@
 importScripts("/resources/testharness.js");
 importScripts("/html/canvas/resources/canvas-tests.js");
 
-var t = async_test("");
-var t_pass = t.done.bind(t);
-var t_fail = t.step_func(function(reason) {
-    throw reason;
-});
-t.step(function() {
+promise_test(async t => {
 
   var canvas = new OffscreenCanvas(100, 50);
   var ctx = canvas.getContext('2d');
 
   ctx.fillStyle = '#f00';
   ctx.fillRect(0, 0, 100, 50);
-  var promise = new Promise(function(resolve, reject) {
-      var xhr = new XMLHttpRequest();
-      xhr.open("GET", '/images/green-16x16.png');
-      xhr.responseType = 'blob';
-      xhr.send();
-      xhr.onload = function() {
-          resolve(xhr.response);
-      };
-  });
-  promise.then(function(response) {
-      return createImageBitmap(response).then(bitmap => {
-          var pattern = ctx.createPattern(bitmap, 'no-repeat');
-          ctx.fillStyle = pattern;
-          ctx.fillRect(0, 0, 100, 50);
-          _assertPixel(canvas, 1,1, 0,255,0,255);
-          _assertPixel(canvas, 98,1, 0,255,0,255);
-          _assertPixel(canvas, 1,48, 0,255,0,255);
-          _assertPixel(canvas, 98,48, 0,255,0,255);
-      });
-  }).then(t_pass, t_fail);
-});
+
+  var response = await fetch('/images/green-16x16.png')
+  var blob = await response.blob();
+  var img = await createImageBitmap(blob);
+  var pattern = ctx.createPattern(img, 'repeat');
+  ctx.fillStyle = pattern;
+  ctx.fillRect(0, 0, 100, 50);
+
+  _assertPixel(canvas, 1,1, 0,255,0,255);
+  _assertPixel(canvas, 98,1, 0,255,0,255);
+  _assertPixel(canvas, 1,48, 0,255,0,255);
+  _assertPixel(canvas, 98,48, 0,255,0,255);
+  t.done();
+}, "");
 done();
diff --git a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.paint.repeat.basic.worker.js.ini b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.paint.repeat.basic.worker.js.ini
deleted file mode 100644
index 8775b671..0000000
--- a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.paint.repeat.basic.worker.js.ini
+++ /dev/null
@@ -1,3 +0,0 @@
-[2d.pattern.paint.repeat.basic.worker.html]
-  [2d]
-    expected: FAIL
diff --git a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.paint.repeat.coord1.html b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.paint.repeat.coord1.html
index 098abc7..54b24ac 100644
--- a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.paint.repeat.coord1.html
+++ b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.paint.repeat.coord1.html
@@ -10,39 +10,26 @@
 
 
 <script>
-var t = async_test("");
-var t_pass = t.done.bind(t);
-var t_fail = t.step_func(function(reason) {
-    throw reason;
-});
-t.step(function() {
+promise_test(async t => {
 
   var canvas = new OffscreenCanvas(100, 50);
   var ctx = canvas.getContext('2d');
 
   ctx.fillStyle = '#f00';
   ctx.fillRect(0, 0, 100, 50);
-  var promise = new Promise(function(resolve, reject) {
-      var xhr = new XMLHttpRequest();
-      xhr.open("GET", '/images/rgrg-256x256.png');
-      xhr.responseType = 'blob';
-      xhr.send();
-      xhr.onload = function() {
-          resolve(xhr.response);
-      };
-  });
-  promise.then(function(response) {
-      return createImageBitmap(response).then(bitmap => {
-          var pattern = ctx.createPattern(bitmap, 'no-repeat');
-          ctx.fillStyle = pattern;
-          ctx.translate(-128, -78);
-          ctx.fillRect(128, 78, 100, 50);
-          _assertPixel(canvas, 1,1, 0,255,0,255);
-          _assertPixel(canvas, 98,1, 0,255,0,255);
-          _assertPixel(canvas, 1,48, 0,255,0,255);
-          _assertPixel(canvas, 98,48, 0,255,0,255);
-      });
-  }).then(t_pass, t_fail);
 
-});
+  var response = await fetch('/images/rgrg-256x256.png')
+  var blob = await response.blob();
+  var img = await createImageBitmap(blob);
+  var pattern = ctx.createPattern(img, 'repeat');
+  ctx.fillStyle = pattern;
+  ctx.translate(-128, -78);
+  ctx.fillRect(128, 78, 100, 50);
+
+  _assertPixel(canvas, 1,1, 0,255,0,255);
+  _assertPixel(canvas, 98,1, 0,255,0,255);
+  _assertPixel(canvas, 1,48, 0,255,0,255);
+  _assertPixel(canvas, 98,48, 0,255,0,255);
+
+}, "");
 </script>
diff --git a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.paint.repeat.coord1.worker.js b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.paint.repeat.coord1.worker.js
index 28c8d07b..efbd1c58 100644
--- a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.paint.repeat.coord1.worker.js
+++ b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.paint.repeat.coord1.worker.js
@@ -6,38 +6,26 @@
 importScripts("/resources/testharness.js");
 importScripts("/html/canvas/resources/canvas-tests.js");
 
-var t = async_test("");
-var t_pass = t.done.bind(t);
-var t_fail = t.step_func(function(reason) {
-    throw reason;
-});
-t.step(function() {
+promise_test(async t => {
 
   var canvas = new OffscreenCanvas(100, 50);
   var ctx = canvas.getContext('2d');
 
   ctx.fillStyle = '#f00';
   ctx.fillRect(0, 0, 100, 50);
-  var promise = new Promise(function(resolve, reject) {
-      var xhr = new XMLHttpRequest();
-      xhr.open("GET", '/images/rgrg-256x256.png');
-      xhr.responseType = 'blob';
-      xhr.send();
-      xhr.onload = function() {
-          resolve(xhr.response);
-      };
-  });
-  promise.then(function(response) {
-      return createImageBitmap(response).then(bitmap => {
-          var pattern = ctx.createPattern(bitmap, 'no-repeat');
-          ctx.fillStyle = pattern;
-          ctx.translate(-128, -78);
-          ctx.fillRect(128, 78, 100, 50);
-          _assertPixel(canvas, 1,1, 0,255,0,255);
-          _assertPixel(canvas, 98,1, 0,255,0,255);
-          _assertPixel(canvas, 1,48, 0,255,0,255);
-          _assertPixel(canvas, 98,48, 0,255,0,255);
-      });
-  }).then(t_pass, t_fail);
-});
+
+  var response = await fetch('/images/rgrg-256x256.png')
+  var blob = await response.blob();
+  var img = await createImageBitmap(blob);
+  var pattern = ctx.createPattern(img, 'repeat');
+  ctx.fillStyle = pattern;
+  ctx.translate(-128, -78);
+  ctx.fillRect(128, 78, 100, 50);
+
+  _assertPixel(canvas, 1,1, 0,255,0,255);
+  _assertPixel(canvas, 98,1, 0,255,0,255);
+  _assertPixel(canvas, 1,48, 0,255,0,255);
+  _assertPixel(canvas, 98,48, 0,255,0,255);
+  t.done();
+}, "");
 done();
diff --git a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.paint.repeat.coord2.html b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.paint.repeat.coord2.html
index 4ee7aac..a77ce2c 100644
--- a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.paint.repeat.coord2.html
+++ b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.paint.repeat.coord2.html
@@ -10,36 +10,22 @@
 
 
 <script>
-var t = async_test("");
-var t_pass = t.done.bind(t);
-var t_fail = t.step_func(function(reason) {
-    throw reason;
-});
-t.step(function() {
+promise_test(async t => {
 
   var canvas = new OffscreenCanvas(100, 50);
   var ctx = canvas.getContext('2d');
 
-  var promise = new Promise(function(resolve, reject) {
-      var xhr = new XMLHttpRequest();
-      xhr.open("GET", '/images/grgr-256x256.png');
-      xhr.responseType = 'blob';
-      xhr.send();
-      xhr.onload = function() {
-          resolve(xhr.response);
-      };
-  });
-  promise.then(function(response) {
-      return createImageBitmap(response).then(bitmap => {
-          var pattern = ctx.createPattern(bitmap, 'no-repeat');
-          ctx.fillStyle = pattern;
-          ctx.fillRect(0, 0, 100, 50);
-          _assertPixel(canvas, 1,1, 0,255,0,255);
-          _assertPixel(canvas, 98,1, 0,255,0,255);
-          _assertPixel(canvas, 1,48, 0,255,0,255);
-          _assertPixel(canvas, 98,48, 0,255,0,255);
-      });
-  }).then(t_pass, t_fail);
+  var response = await fetch('/images/ggrr-256x256.png')
+  var blob = await response.blob();
+  var img = await createImageBitmap(blob);
+  var pattern = ctx.createPattern(img, 'repeat');
+  ctx.fillStyle = pattern;
+  ctx.fillRect(0, 0, 100, 50);
 
-});
+  _assertPixel(canvas, 1,1, 0,255,0,255);
+  _assertPixel(canvas, 98,1, 0,255,0,255);
+  _assertPixel(canvas, 1,48, 0,255,0,255);
+  _assertPixel(canvas, 98,48, 0,255,0,255);
+
+}, "");
 </script>
diff --git a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.paint.repeat.coord2.worker.js b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.paint.repeat.coord2.worker.js
index 6b2228e..db707ec 100644
--- a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.paint.repeat.coord2.worker.js
+++ b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.paint.repeat.coord2.worker.js
@@ -6,35 +6,22 @@
 importScripts("/resources/testharness.js");
 importScripts("/html/canvas/resources/canvas-tests.js");
 
-var t = async_test("");
-var t_pass = t.done.bind(t);
-var t_fail = t.step_func(function(reason) {
-    throw reason;
-});
-t.step(function() {
+promise_test(async t => {
 
   var canvas = new OffscreenCanvas(100, 50);
   var ctx = canvas.getContext('2d');
 
-  var promise = new Promise(function(resolve, reject) {
-      var xhr = new XMLHttpRequest();
-      xhr.open("GET", '/images/grgr-256x256.png');
-      xhr.responseType = 'blob';
-      xhr.send();
-      xhr.onload = function() {
-          resolve(xhr.response);
-      };
-  });
-  promise.then(function(response) {
-      return createImageBitmap(response).then(bitmap => {
-          var pattern = ctx.createPattern(bitmap, 'no-repeat');
-          ctx.fillStyle = pattern;
-          ctx.fillRect(0, 0, 100, 50);
-          _assertPixel(canvas, 1,1, 0,255,0,255);
-          _assertPixel(canvas, 98,1, 0,255,0,255);
-          _assertPixel(canvas, 1,48, 0,255,0,255);
-          _assertPixel(canvas, 98,48, 0,255,0,255);
-      });
-  }).then(t_pass, t_fail);
-});
+  var response = await fetch('/images/ggrr-256x256.png')
+  var blob = await response.blob();
+  var img = await createImageBitmap(blob);
+  var pattern = ctx.createPattern(img, 'repeat');
+  ctx.fillStyle = pattern;
+  ctx.fillRect(0, 0, 100, 50);
+
+  _assertPixel(canvas, 1,1, 0,255,0,255);
+  _assertPixel(canvas, 98,1, 0,255,0,255);
+  _assertPixel(canvas, 1,48, 0,255,0,255);
+  _assertPixel(canvas, 98,48, 0,255,0,255);
+  t.done();
+}, "");
 done();
diff --git a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.paint.repeat.coord3.html b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.paint.repeat.coord3.html
index ed1bb42..10f380f 100644
--- a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.paint.repeat.coord3.html
+++ b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.paint.repeat.coord3.html
@@ -10,38 +10,25 @@
 
 
 <script>
-var t = async_test("");
-var t_pass = t.done.bind(t);
-var t_fail = t.step_func(function(reason) {
-    throw reason;
-});
-t.step(function() {
+promise_test(async t => {
 
   var canvas = new OffscreenCanvas(100, 50);
   var ctx = canvas.getContext('2d');
 
-  var promise = new Promise(function(resolve, reject) {
-      var xhr = new XMLHttpRequest();
-      xhr.open("GET", '/images/rgrg-256x256.png');
-      xhr.responseType = 'blob';
-      xhr.send();
-      xhr.onload = function() {
-          resolve(xhr.response);
-      };
-  });
-  promise.then(function(response) {
-      return createImageBitmap(response).then(bitmap => {
-          var pattern = ctx.createPattern(bitmap, 'no-repeat');
-          ctx.fillStyle = pattern;
-          ctx.fillRect(0, 0, 100, 50);
-          ctx.translate(-128, -78);
-          ctx.fillRect(128, 78, 100, 50);
-          _assertPixel(canvas, 1,1, 0,255,0,255);
-          _assertPixel(canvas, 98,1, 0,255,0,255);
-          _assertPixel(canvas, 1,48, 0,255,0,255);
-          _assertPixel(canvas, 98,48, 0,255,0,255);
-      });
-  }).then(t_pass, t_fail);
+  var response = await fetch('/images/rgrg-256x256.png')
+  var blob = await response.blob();
+  var img = await createImageBitmap(blob);
+  var pattern = ctx.createPattern(img, 'repeat');
+  ctx.fillStyle = pattern;
+  ctx.fillRect(0, 0, 100, 50);
 
-});
+  ctx.translate(-128, -78);
+  ctx.fillRect(128, 78, 100, 50);
+
+  _assertPixel(canvas, 1,1, 0,255,0,255);
+  _assertPixel(canvas, 98,1, 0,255,0,255);
+  _assertPixel(canvas, 1,48, 0,255,0,255);
+  _assertPixel(canvas, 98,48, 0,255,0,255);
+
+}, "");
 </script>
diff --git a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.paint.repeat.coord3.worker.js b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.paint.repeat.coord3.worker.js
index fc3911f..e58cddc 100644
--- a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.paint.repeat.coord3.worker.js
+++ b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.paint.repeat.coord3.worker.js
@@ -6,37 +6,25 @@
 importScripts("/resources/testharness.js");
 importScripts("/html/canvas/resources/canvas-tests.js");
 
-var t = async_test("");
-var t_pass = t.done.bind(t);
-var t_fail = t.step_func(function(reason) {
-    throw reason;
-});
-t.step(function() {
+promise_test(async t => {
 
   var canvas = new OffscreenCanvas(100, 50);
   var ctx = canvas.getContext('2d');
 
-  var promise = new Promise(function(resolve, reject) {
-      var xhr = new XMLHttpRequest();
-      xhr.open("GET", '/images/rgrg-256x256.png');
-      xhr.responseType = 'blob';
-      xhr.send();
-      xhr.onload = function() {
-          resolve(xhr.response);
-      };
-  });
-  promise.then(function(response) {
-      return createImageBitmap(response).then(bitmap => {
-          var pattern = ctx.createPattern(bitmap, 'no-repeat');
-          ctx.fillStyle = pattern;
-          ctx.fillRect(0, 0, 100, 50);
-          ctx.translate(-128, -78);
-          ctx.fillRect(128, 78, 100, 50);
-          _assertPixel(canvas, 1,1, 0,255,0,255);
-          _assertPixel(canvas, 98,1, 0,255,0,255);
-          _assertPixel(canvas, 1,48, 0,255,0,255);
-          _assertPixel(canvas, 98,48, 0,255,0,255);
-      });
-  }).then(t_pass, t_fail);
-});
+  var response = await fetch('/images/rgrg-256x256.png')
+  var blob = await response.blob();
+  var img = await createImageBitmap(blob);
+  var pattern = ctx.createPattern(img, 'repeat');
+  ctx.fillStyle = pattern;
+  ctx.fillRect(0, 0, 100, 50);
+
+  ctx.translate(-128, -78);
+  ctx.fillRect(128, 78, 100, 50);
+
+  _assertPixel(canvas, 1,1, 0,255,0,255);
+  _assertPixel(canvas, 98,1, 0,255,0,255);
+  _assertPixel(canvas, 1,48, 0,255,0,255);
+  _assertPixel(canvas, 98,48, 0,255,0,255);
+  t.done();
+}, "");
 done();
diff --git a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.paint.repeat.outside-expected.txt b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.paint.repeat.outside-expected.txt
deleted file mode 100644
index 8a76ced..0000000
--- a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.paint.repeat.outside-expected.txt
+++ /dev/null
@@ -1,4 +0,0 @@
-This is a testharness.js-based test.
-FAIL OffscreenCanvas test: 2d.pattern.paint.repeat.outside assert_equals: Red channel of the pixel at (1, 1) expected 0 but got 255
-Harness: the test ran to completion.
-
diff --git a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.paint.repeat.outside.html b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.paint.repeat.outside.html
index 420562c5..6558e07 100644
--- a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.paint.repeat.outside.html
+++ b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.paint.repeat.outside.html
@@ -10,39 +10,26 @@
 
 
 <script>
-var t = async_test("");
-var t_pass = t.done.bind(t);
-var t_fail = t.step_func(function(reason) {
-    throw reason;
-});
-t.step(function() {
+promise_test(async t => {
 
   var canvas = new OffscreenCanvas(100, 50);
   var ctx = canvas.getContext('2d');
 
   ctx.fillStyle = '#f00';
   ctx.fillRect(0, 0, 100, 50);
-  var promise = new Promise(function(resolve, reject) {
-      var xhr = new XMLHttpRequest();
-      xhr.open("GET", '/images/green-16x16.png');
-      xhr.responseType = 'blob';
-      xhr.send();
-      xhr.onload = function() {
-          resolve(xhr.response);
-      };
-  });
-  promise.then(function(response) {
-      return createImageBitmap(response).then(bitmap => {
-          var pattern = ctx.createPattern(bitmap, 'no-repeat');
-          ctx.fillStyle = pattern;
-          ctx.translate(50, 25);
-          ctx.fillRect(-50, -25, 100, 50);
-          _assertPixel(canvas, 1,1, 0,255,0,255);
-          _assertPixel(canvas, 98,1, 0,255,0,255);
-          _assertPixel(canvas, 1,48, 0,255,0,255);
-          _assertPixel(canvas, 98,48, 0,255,0,255);
-      });
-  }).then(t_pass, t_fail);
 
-});
+  var response = await fetch('/images/green-16x16.png')
+  var blob = await response.blob();
+  var img = await createImageBitmap(blob);
+  var pattern = ctx.createPattern(img, 'repeat');
+  ctx.fillStyle = pattern;
+  ctx.translate(50, 25);
+  ctx.fillRect(-50, -25, 100, 50);
+
+  _assertPixel(canvas, 1,1, 0,255,0,255);
+  _assertPixel(canvas, 98,1, 0,255,0,255);
+  _assertPixel(canvas, 1,48, 0,255,0,255);
+  _assertPixel(canvas, 98,48, 0,255,0,255);
+
+}, "");
 </script>
diff --git a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.paint.repeat.outside.html.ini b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.paint.repeat.outside.html.ini
deleted file mode 100644
index b4b3d5da..0000000
--- a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.paint.repeat.outside.html.ini
+++ /dev/null
@@ -1,3 +0,0 @@
-[2d.pattern.paint.repeat.outside.html]
-  [OffscreenCanvas test: 2d.pattern.paint.repeat.outside]
-    expected: FAIL
diff --git a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.paint.repeat.outside.worker-expected.txt b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.paint.repeat.outside.worker-expected.txt
deleted file mode 100644
index c599277..0000000
--- a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.paint.repeat.outside.worker-expected.txt
+++ /dev/null
@@ -1,4 +0,0 @@
-This is a testharness.js-based test.
-FAIL 2d assert_equals: Red channel of the pixel at (1, 1) expected 0 but got 255
-Harness: the test ran to completion.
-
diff --git a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.paint.repeat.outside.worker.js b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.paint.repeat.outside.worker.js
index b4d6774e..4a173af 100644
--- a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.paint.repeat.outside.worker.js
+++ b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.paint.repeat.outside.worker.js
@@ -6,38 +6,26 @@
 importScripts("/resources/testharness.js");
 importScripts("/html/canvas/resources/canvas-tests.js");
 
-var t = async_test("");
-var t_pass = t.done.bind(t);
-var t_fail = t.step_func(function(reason) {
-    throw reason;
-});
-t.step(function() {
+promise_test(async t => {
 
   var canvas = new OffscreenCanvas(100, 50);
   var ctx = canvas.getContext('2d');
 
   ctx.fillStyle = '#f00';
   ctx.fillRect(0, 0, 100, 50);
-  var promise = new Promise(function(resolve, reject) {
-      var xhr = new XMLHttpRequest();
-      xhr.open("GET", '/images/green-16x16.png');
-      xhr.responseType = 'blob';
-      xhr.send();
-      xhr.onload = function() {
-          resolve(xhr.response);
-      };
-  });
-  promise.then(function(response) {
-      return createImageBitmap(response).then(bitmap => {
-          var pattern = ctx.createPattern(bitmap, 'no-repeat');
-          ctx.fillStyle = pattern;
-          ctx.translate(50, 25);
-          ctx.fillRect(-50, -25, 100, 50);
-          _assertPixel(canvas, 1,1, 0,255,0,255);
-          _assertPixel(canvas, 98,1, 0,255,0,255);
-          _assertPixel(canvas, 1,48, 0,255,0,255);
-          _assertPixel(canvas, 98,48, 0,255,0,255);
-      });
-  }).then(t_pass, t_fail);
-});
+
+  var response = await fetch('/images/green-16x16.png')
+  var blob = await response.blob();
+  var img = await createImageBitmap(blob);
+  var pattern = ctx.createPattern(img, 'repeat');
+  ctx.fillStyle = pattern;
+  ctx.translate(50, 25);
+  ctx.fillRect(-50, -25, 100, 50);
+
+  _assertPixel(canvas, 1,1, 0,255,0,255);
+  _assertPixel(canvas, 98,1, 0,255,0,255);
+  _assertPixel(canvas, 1,48, 0,255,0,255);
+  _assertPixel(canvas, 98,48, 0,255,0,255);
+  t.done();
+}, "");
 done();
diff --git a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.paint.repeat.outside.worker.js.ini b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.paint.repeat.outside.worker.js.ini
deleted file mode 100644
index acc7668..0000000
--- a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.paint.repeat.outside.worker.js.ini
+++ /dev/null
@@ -1,3 +0,0 @@
-[2d.pattern.paint.repeat.outside.worker.html]
-  [2d]
-    expected: FAIL
diff --git a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.paint.repeatx.basic.html b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.paint.repeatx.basic.html
index ee2c477..494b72c 100644
--- a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.paint.repeatx.basic.html
+++ b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.paint.repeatx.basic.html
@@ -10,12 +10,7 @@
 
 
 <script>
-var t = async_test("");
-var t_pass = t.done.bind(t);
-var t_fail = t.step_func(function(reason) {
-    throw reason;
-});
-t.step(function() {
+promise_test(async t => {
 
   var canvas = new OffscreenCanvas(100, 50);
   var ctx = canvas.getContext('2d');
@@ -24,26 +19,18 @@
   ctx.fillRect(0, 0, 100, 50);
   ctx.fillStyle = '#f00';
   ctx.fillRect(0, 0, 100, 16);
-  var promise = new Promise(function(resolve, reject) {
-      var xhr = new XMLHttpRequest();
-      xhr.open("GET", '/images/green-16x16.png');
-      xhr.responseType = 'blob';
-      xhr.send();
-      xhr.onload = function() {
-          resolve(xhr.response);
-      };
-  });
-  promise.then(function(response) {
-      return createImageBitmap(response).then(bitmap => {
-          var pattern = ctx.createPattern(bitmap, 'repeat-x');
-          ctx.fillStyle = pattern;
-          ctx.fillRect(0, 0, 100, 50);
-          _assertPixel(canvas, 1,1, 0,255,0,255);
-          _assertPixel(canvas, 98,1, 0,255,0,255);
-          _assertPixel(canvas, 1,48, 0,255,0,255);
-          _assertPixel(canvas, 98,48, 0,255,0,255);
-      });
-  }).then(t_pass, t_fail);
 
-});
+  var response = await fetch('/images/green-16x16.png')
+  var blob = await response.blob();
+  var img = await createImageBitmap(blob);
+  var pattern = ctx.createPattern(img, 'repeat-x');
+  ctx.fillStyle = pattern;
+  ctx.fillRect(0, 0, 100, 50);
+
+  _assertPixel(canvas, 1,1, 0,255,0,255);
+  _assertPixel(canvas, 98,1, 0,255,0,255);
+  _assertPixel(canvas, 1,48, 0,255,0,255);
+  _assertPixel(canvas, 98,48, 0,255,0,255);
+
+}, "");
 </script>
diff --git a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.paint.repeatx.basic.worker.js b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.paint.repeatx.basic.worker.js
index 157207a7..e776f7a 100644
--- a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.paint.repeatx.basic.worker.js
+++ b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.paint.repeatx.basic.worker.js
@@ -6,12 +6,7 @@
 importScripts("/resources/testharness.js");
 importScripts("/html/canvas/resources/canvas-tests.js");
 
-var t = async_test("");
-var t_pass = t.done.bind(t);
-var t_fail = t.step_func(function(reason) {
-    throw reason;
-});
-t.step(function() {
+promise_test(async t => {
 
   var canvas = new OffscreenCanvas(100, 50);
   var ctx = canvas.getContext('2d');
@@ -20,25 +15,18 @@
   ctx.fillRect(0, 0, 100, 50);
   ctx.fillStyle = '#f00';
   ctx.fillRect(0, 0, 100, 16);
-  var promise = new Promise(function(resolve, reject) {
-      var xhr = new XMLHttpRequest();
-      xhr.open("GET", '/images/green-16x16.png');
-      xhr.responseType = 'blob';
-      xhr.send();
-      xhr.onload = function() {
-          resolve(xhr.response);
-      };
-  });
-  promise.then(function(response) {
-      return createImageBitmap(response).then(bitmap => {
-          var pattern = ctx.createPattern(bitmap, 'repeat-x');
-          ctx.fillStyle = pattern;
-          ctx.fillRect(0, 0, 100, 50);
-          _assertPixel(canvas, 1,1, 0,255,0,255);
-          _assertPixel(canvas, 98,1, 0,255,0,255);
-          _assertPixel(canvas, 1,48, 0,255,0,255);
-          _assertPixel(canvas, 98,48, 0,255,0,255);
-      });
-  }).then(t_pass, t_fail);
-});
+
+  var response = await fetch('/images/green-16x16.png')
+  var blob = await response.blob();
+  var img = await createImageBitmap(blob);
+  var pattern = ctx.createPattern(img, 'repeat-x');
+  ctx.fillStyle = pattern;
+  ctx.fillRect(0, 0, 100, 50);
+
+  _assertPixel(canvas, 1,1, 0,255,0,255);
+  _assertPixel(canvas, 98,1, 0,255,0,255);
+  _assertPixel(canvas, 1,48, 0,255,0,255);
+  _assertPixel(canvas, 98,48, 0,255,0,255);
+  t.done();
+}, "");
 done();
diff --git a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.paint.repeatx.coord1.html b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.paint.repeatx.coord1.html
index 0e9a92e..6caef0c 100644
--- a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.paint.repeatx.coord1.html
+++ b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.paint.repeatx.coord1.html
@@ -10,43 +10,31 @@
 
 
 <script>
-var t = async_test("");
-var t_pass = t.done.bind(t);
-var t_fail = t.step_func(function(reason) {
-    throw reason;
-});
-t.step(function() {
+promise_test(async t => {
 
   var canvas = new OffscreenCanvas(100, 50);
   var ctx = canvas.getContext('2d');
 
   ctx.fillStyle = '#0f0';
   ctx.fillRect(0, 0, 100, 50);
-  var promise = new Promise(function(resolve, reject) {
-      var xhr = new XMLHttpRequest();
-      xhr.open("GET", '/images/red-16x16.png');
-      xhr.responseType = 'blob';
-      xhr.send();
-      xhr.onload = function() {
-          resolve(xhr.response);
-      };
-  });
-  promise.then(function(response) {
-      return createImageBitmap(response).then(bitmap => {
-          var pattern = ctx.createPattern(bitmap, 'repeat-x');
-          ctx.fillStyle = pattern;
-          ctx.translate(0, 16);
-          ctx.fillRect(0, -16, 100, 50);
-          ctx.fillStyle = '#0f0';
-          ctx.fillRect(0, 0, 100, 16);
-          _assertPixel(canvas, 1,1, 0,255,0,255);
-          _assertPixel(canvas, 98,1, 0,255,0,255);
-          _assertPixel(canvas, 1,25, 0,255,0,255);
-          _assertPixel(canvas, 98,25, 0,255,0,255);
-          _assertPixel(canvas, 1,48, 0,255,0,255);
-          _assertPixel(canvas, 98,48, 0,255,0,255);
-      });
-  }).then(t_pass, t_fail);
 
-});
+  var response = await fetch('/images/red-16x16.png')
+  var blob = await response.blob();
+  var img = await createImageBitmap(blob);
+  var pattern = ctx.createPattern(img, 'repeat-x');
+  ctx.fillStyle = pattern;
+  ctx.translate(0, 16);
+  ctx.fillRect(0, -16, 100, 50);
+
+  ctx.fillStyle = '#0f0';
+  ctx.fillRect(0, 0, 100, 16);
+
+  _assertPixel(canvas, 1,1, 0,255,0,255);
+  _assertPixel(canvas, 98,1, 0,255,0,255);
+  _assertPixel(canvas, 1,25, 0,255,0,255);
+  _assertPixel(canvas, 98,25, 0,255,0,255);
+  _assertPixel(canvas, 1,48, 0,255,0,255);
+  _assertPixel(canvas, 98,48, 0,255,0,255);
+
+}, "");
 </script>
diff --git a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.paint.repeatx.coord1.worker.js b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.paint.repeatx.coord1.worker.js
index 684d2b7..57c2102 100644
--- a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.paint.repeatx.coord1.worker.js
+++ b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.paint.repeatx.coord1.worker.js
@@ -6,42 +6,31 @@
 importScripts("/resources/testharness.js");
 importScripts("/html/canvas/resources/canvas-tests.js");
 
-var t = async_test("");
-var t_pass = t.done.bind(t);
-var t_fail = t.step_func(function(reason) {
-    throw reason;
-});
-t.step(function() {
+promise_test(async t => {
 
   var canvas = new OffscreenCanvas(100, 50);
   var ctx = canvas.getContext('2d');
 
   ctx.fillStyle = '#0f0';
   ctx.fillRect(0, 0, 100, 50);
-  var promise = new Promise(function(resolve, reject) {
-      var xhr = new XMLHttpRequest();
-      xhr.open("GET", '/images/red-16x16.png');
-      xhr.responseType = 'blob';
-      xhr.send();
-      xhr.onload = function() {
-          resolve(xhr.response);
-      };
-  });
-  promise.then(function(response) {
-      return createImageBitmap(response).then(bitmap => {
-          var pattern = ctx.createPattern(bitmap, 'repeat-x');
-          ctx.fillStyle = pattern;
-          ctx.translate(0, 16);
-          ctx.fillRect(0, -16, 100, 50);
-          ctx.fillStyle = '#0f0';
-          ctx.fillRect(0, 0, 100, 16);
-          _assertPixel(canvas, 1,1, 0,255,0,255);
-          _assertPixel(canvas, 98,1, 0,255,0,255);
-          _assertPixel(canvas, 1,25, 0,255,0,255);
-          _assertPixel(canvas, 98,25, 0,255,0,255);
-          _assertPixel(canvas, 1,48, 0,255,0,255);
-          _assertPixel(canvas, 98,48, 0,255,0,255);
-      });
-  }).then(t_pass, t_fail);
-});
+
+  var response = await fetch('/images/red-16x16.png')
+  var blob = await response.blob();
+  var img = await createImageBitmap(blob);
+  var pattern = ctx.createPattern(img, 'repeat-x');
+  ctx.fillStyle = pattern;
+  ctx.translate(0, 16);
+  ctx.fillRect(0, -16, 100, 50);
+
+  ctx.fillStyle = '#0f0';
+  ctx.fillRect(0, 0, 100, 16);
+
+  _assertPixel(canvas, 1,1, 0,255,0,255);
+  _assertPixel(canvas, 98,1, 0,255,0,255);
+  _assertPixel(canvas, 1,25, 0,255,0,255);
+  _assertPixel(canvas, 98,25, 0,255,0,255);
+  _assertPixel(canvas, 1,48, 0,255,0,255);
+  _assertPixel(canvas, 98,48, 0,255,0,255);
+  t.done();
+}, "");
 done();
diff --git a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.paint.repeatx.outside.html b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.paint.repeatx.outside.html
index 83e8ef37..b3229c8 100644
--- a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.paint.repeatx.outside.html
+++ b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.paint.repeatx.outside.html
@@ -10,40 +10,28 @@
 
 
 <script>
-var t = async_test("");
-var t_pass = t.done.bind(t);
-var t_fail = t.step_func(function(reason) {
-    throw reason;
-});
-t.step(function() {
+promise_test(async t => {
 
   var canvas = new OffscreenCanvas(100, 50);
   var ctx = canvas.getContext('2d');
 
   ctx.fillStyle = '#0f0';
   ctx.fillRect(0, 0, 100, 50);
-  var promise = new Promise(function(resolve, reject) {
-      var xhr = new XMLHttpRequest();
-      xhr.open("GET", '/images/red-16x16.png');
-      xhr.responseType = 'blob';
-      xhr.send();
-      xhr.onload = function() {
-          resolve(xhr.response);
-      };
-  });
-  promise.then(function(response) {
-      return createImageBitmap(response).then(bitmap => {
-          var pattern = ctx.createPattern(bitmap, 'repeat-x');
-          ctx.fillStyle = pattern;
-          ctx.fillRect(0, 0, 100, 50);
-          ctx.fillStyle = '#0f0';
-          ctx.fillRect(0, 0, 100, 16);
-          _assertPixel(canvas, 1,1, 0,255,0,255);
-          _assertPixel(canvas, 98,1, 0,255,0,255);
-          _assertPixel(canvas, 1,48, 0,255,0,255);
-          _assertPixel(canvas, 98,48, 0,255,0,255);
-      });
-  }).then(t_pass, t_fail);
 
-});
+  var response = await fetch('/images/red-16x16.png')
+  var blob = await response.blob();
+  var img = await createImageBitmap(blob);
+  var pattern = ctx.createPattern(img, 'repeat-x');
+  ctx.fillStyle = pattern;
+  ctx.fillRect(0, 0, 100, 50);
+
+  ctx.fillStyle = '#0f0';
+  ctx.fillRect(0, 0, 100, 16);
+
+  _assertPixel(canvas, 1,1, 0,255,0,255);
+  _assertPixel(canvas, 98,1, 0,255,0,255);
+  _assertPixel(canvas, 1,48, 0,255,0,255);
+  _assertPixel(canvas, 98,48, 0,255,0,255);
+
+}, "");
 </script>
diff --git a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.paint.repeatx.outside.worker.js b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.paint.repeatx.outside.worker.js
index bbb7b90..e40f6aa2 100644
--- a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.paint.repeatx.outside.worker.js
+++ b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.paint.repeatx.outside.worker.js
@@ -6,39 +6,28 @@
 importScripts("/resources/testharness.js");
 importScripts("/html/canvas/resources/canvas-tests.js");
 
-var t = async_test("");
-var t_pass = t.done.bind(t);
-var t_fail = t.step_func(function(reason) {
-    throw reason;
-});
-t.step(function() {
+promise_test(async t => {
 
   var canvas = new OffscreenCanvas(100, 50);
   var ctx = canvas.getContext('2d');
 
   ctx.fillStyle = '#0f0';
   ctx.fillRect(0, 0, 100, 50);
-  var promise = new Promise(function(resolve, reject) {
-      var xhr = new XMLHttpRequest();
-      xhr.open("GET", '/images/red-16x16.png');
-      xhr.responseType = 'blob';
-      xhr.send();
-      xhr.onload = function() {
-          resolve(xhr.response);
-      };
-  });
-  promise.then(function(response) {
-      return createImageBitmap(response).then(bitmap => {
-          var pattern = ctx.createPattern(bitmap, 'repeat-x');
-          ctx.fillStyle = pattern;
-          ctx.fillRect(0, 0, 100, 50);
-          ctx.fillStyle = '#0f0';
-          ctx.fillRect(0, 0, 100, 16);
-          _assertPixel(canvas, 1,1, 0,255,0,255);
-          _assertPixel(canvas, 98,1, 0,255,0,255);
-          _assertPixel(canvas, 1,48, 0,255,0,255);
-          _assertPixel(canvas, 98,48, 0,255,0,255);
-      });
-  }).then(t_pass, t_fail);
-});
+
+  var response = await fetch('/images/red-16x16.png')
+  var blob = await response.blob();
+  var img = await createImageBitmap(blob);
+  var pattern = ctx.createPattern(img, 'repeat-x');
+  ctx.fillStyle = pattern;
+  ctx.fillRect(0, 0, 100, 50);
+
+  ctx.fillStyle = '#0f0';
+  ctx.fillRect(0, 0, 100, 16);
+
+  _assertPixel(canvas, 1,1, 0,255,0,255);
+  _assertPixel(canvas, 98,1, 0,255,0,255);
+  _assertPixel(canvas, 1,48, 0,255,0,255);
+  _assertPixel(canvas, 98,48, 0,255,0,255);
+  t.done();
+}, "");
 done();
diff --git a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.paint.repeaty.basic.html b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.paint.repeaty.basic.html
index 4894845..d88f3a0 100644
--- a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.paint.repeaty.basic.html
+++ b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.paint.repeaty.basic.html
@@ -10,12 +10,7 @@
 
 
 <script>
-var t = async_test("");
-var t_pass = t.done.bind(t);
-var t_fail = t.step_func(function(reason) {
-    throw reason;
-});
-t.step(function() {
+promise_test(async t => {
 
   var canvas = new OffscreenCanvas(100, 50);
   var ctx = canvas.getContext('2d');
@@ -24,26 +19,18 @@
   ctx.fillRect(0, 0, 100, 50);
   ctx.fillStyle = '#f00';
   ctx.fillRect(0, 0, 16, 50);
-  var promise = new Promise(function(resolve, reject) {
-      var xhr = new XMLHttpRequest();
-      xhr.open("GET", '/images/green-16x16.png');
-      xhr.responseType = 'blob';
-      xhr.send();
-      xhr.onload = function() {
-          resolve(xhr.response);
-      };
-  });
-  promise.then(function(response) {
-      return createImageBitmap(response).then(bitmap => {
-          var pattern = ctx.createPattern(bitmap, 'repeat-y');
-          ctx.fillStyle = pattern;
-          ctx.fillRect(0, 0, 100, 50);
-          _assertPixel(canvas, 1,1, 0,255,0,255);
-          _assertPixel(canvas, 98,1, 0,255,0,255);
-          _assertPixel(canvas, 1,48, 0,255,0,255);
-          _assertPixel(canvas, 98,48, 0,255,0,255);
-      });
-  }).then(t_pass, t_fail);
 
-});
+  var response = await fetch('/images/green-16x16.png')
+  var blob = await response.blob();
+  var img = await createImageBitmap(blob);
+  var pattern = ctx.createPattern(img, 'repeat-y');
+  ctx.fillStyle = pattern;
+  ctx.fillRect(0, 0, 100, 50);
+
+  _assertPixel(canvas, 1,1, 0,255,0,255);
+  _assertPixel(canvas, 98,1, 0,255,0,255);
+  _assertPixel(canvas, 1,48, 0,255,0,255);
+  _assertPixel(canvas, 98,48, 0,255,0,255);
+
+}, "");
 </script>
diff --git a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.paint.repeaty.basic.worker.js b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.paint.repeaty.basic.worker.js
index 0eec87c..6a53b5dc 100644
--- a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.paint.repeaty.basic.worker.js
+++ b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.paint.repeaty.basic.worker.js
@@ -6,12 +6,7 @@
 importScripts("/resources/testharness.js");
 importScripts("/html/canvas/resources/canvas-tests.js");
 
-var t = async_test("");
-var t_pass = t.done.bind(t);
-var t_fail = t.step_func(function(reason) {
-    throw reason;
-});
-t.step(function() {
+promise_test(async t => {
 
   var canvas = new OffscreenCanvas(100, 50);
   var ctx = canvas.getContext('2d');
@@ -20,25 +15,18 @@
   ctx.fillRect(0, 0, 100, 50);
   ctx.fillStyle = '#f00';
   ctx.fillRect(0, 0, 16, 50);
-  var promise = new Promise(function(resolve, reject) {
-      var xhr = new XMLHttpRequest();
-      xhr.open("GET", '/images/green-16x16.png');
-      xhr.responseType = 'blob';
-      xhr.send();
-      xhr.onload = function() {
-          resolve(xhr.response);
-      };
-  });
-  promise.then(function(response) {
-      return createImageBitmap(response).then(bitmap => {
-          var pattern = ctx.createPattern(bitmap, 'repeat-y');
-          ctx.fillStyle = pattern;
-          ctx.fillRect(0, 0, 100, 50);
-          _assertPixel(canvas, 1,1, 0,255,0,255);
-          _assertPixel(canvas, 98,1, 0,255,0,255);
-          _assertPixel(canvas, 1,48, 0,255,0,255);
-          _assertPixel(canvas, 98,48, 0,255,0,255);
-      });
-  }).then(t_pass, t_fail);
-});
+
+  var response = await fetch('/images/green-16x16.png')
+  var blob = await response.blob();
+  var img = await createImageBitmap(blob);
+  var pattern = ctx.createPattern(img, 'repeat-y');
+  ctx.fillStyle = pattern;
+  ctx.fillRect(0, 0, 100, 50);
+
+  _assertPixel(canvas, 1,1, 0,255,0,255);
+  _assertPixel(canvas, 98,1, 0,255,0,255);
+  _assertPixel(canvas, 1,48, 0,255,0,255);
+  _assertPixel(canvas, 98,48, 0,255,0,255);
+  t.done();
+}, "");
 done();
diff --git a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.paint.repeaty.coord1.html b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.paint.repeaty.coord1.html
index 79ca841..59bfeb2a 100644
--- a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.paint.repeaty.coord1.html
+++ b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.paint.repeaty.coord1.html
@@ -10,43 +10,31 @@
 
 
 <script>
-var t = async_test("");
-var t_pass = t.done.bind(t);
-var t_fail = t.step_func(function(reason) {
-    throw reason;
-});
-t.step(function() {
+promise_test(async t => {
 
   var canvas = new OffscreenCanvas(100, 50);
   var ctx = canvas.getContext('2d');
 
   ctx.fillStyle = '#0f0';
   ctx.fillRect(0, 0, 100, 50);
-  var promise = new Promise(function(resolve, reject) {
-      var xhr = new XMLHttpRequest();
-      xhr.open("GET", '/images/red-16x16.png');
-      xhr.responseType = 'blob';
-      xhr.send();
-      xhr.onload = function() {
-          resolve(xhr.response);
-      };
-  });
-  promise.then(function(response) {
-      return createImageBitmap(response).then(bitmap => {
-          var pattern = ctx.createPattern(bitmap, 'repeat-y');
-          ctx.fillStyle = pattern;
-          ctx.translate(48, 0);
-          ctx.fillRect(-48, 0, 100, 50);
-          ctx.fillStyle = '#0f0';
-          ctx.fillRect(0, 0, 16, 50);
-          _assertPixel(canvas, 1,1, 0,255,0,255);
-          _assertPixel(canvas, 50,1, 0,255,0,255);
-          _assertPixel(canvas, 98,1, 0,255,0,255);
-          _assertPixel(canvas, 1,48, 0,255,0,255);
-          _assertPixel(canvas, 50,48, 0,255,0,255);
-          _assertPixel(canvas, 98,48, 0,255,0,255);
-      });
-  }).then(t_pass, t_fail);
 
-});
+  var response = await fetch('/images/red-16x16.png')
+  var blob = await response.blob();
+  var img = await createImageBitmap(blob);
+  var pattern = ctx.createPattern(img, 'repeat-y');
+  ctx.fillStyle = pattern;
+  ctx.translate(48, 0);
+  ctx.fillRect(-48, 0, 100, 50);
+
+  ctx.fillStyle = '#0f0';
+  ctx.fillRect(0, 0, 16, 50);
+
+  _assertPixel(canvas, 1,1, 0,255,0,255);
+  _assertPixel(canvas, 50,1, 0,255,0,255);
+  _assertPixel(canvas, 98,1, 0,255,0,255);
+  _assertPixel(canvas, 1,48, 0,255,0,255);
+  _assertPixel(canvas, 50,48, 0,255,0,255);
+  _assertPixel(canvas, 98,48, 0,255,0,255);
+
+}, "");
 </script>
diff --git a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.paint.repeaty.coord1.worker.js b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.paint.repeaty.coord1.worker.js
index c5f0f4b..d59abb6 100644
--- a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.paint.repeaty.coord1.worker.js
+++ b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.paint.repeaty.coord1.worker.js
@@ -6,42 +6,31 @@
 importScripts("/resources/testharness.js");
 importScripts("/html/canvas/resources/canvas-tests.js");
 
-var t = async_test("");
-var t_pass = t.done.bind(t);
-var t_fail = t.step_func(function(reason) {
-    throw reason;
-});
-t.step(function() {
+promise_test(async t => {
 
   var canvas = new OffscreenCanvas(100, 50);
   var ctx = canvas.getContext('2d');
 
   ctx.fillStyle = '#0f0';
   ctx.fillRect(0, 0, 100, 50);
-  var promise = new Promise(function(resolve, reject) {
-      var xhr = new XMLHttpRequest();
-      xhr.open("GET", '/images/red-16x16.png');
-      xhr.responseType = 'blob';
-      xhr.send();
-      xhr.onload = function() {
-          resolve(xhr.response);
-      };
-  });
-  promise.then(function(response) {
-      return createImageBitmap(response).then(bitmap => {
-          var pattern = ctx.createPattern(bitmap, 'repeat-y');
-          ctx.fillStyle = pattern;
-          ctx.translate(48, 0);
-          ctx.fillRect(-48, 0, 100, 50);
-          ctx.fillStyle = '#0f0';
-          ctx.fillRect(0, 0, 16, 50);
-          _assertPixel(canvas, 1,1, 0,255,0,255);
-          _assertPixel(canvas, 50,1, 0,255,0,255);
-          _assertPixel(canvas, 98,1, 0,255,0,255);
-          _assertPixel(canvas, 1,48, 0,255,0,255);
-          _assertPixel(canvas, 50,48, 0,255,0,255);
-          _assertPixel(canvas, 98,48, 0,255,0,255);
-      });
-  }).then(t_pass, t_fail);
-});
+
+  var response = await fetch('/images/red-16x16.png')
+  var blob = await response.blob();
+  var img = await createImageBitmap(blob);
+  var pattern = ctx.createPattern(img, 'repeat-y');
+  ctx.fillStyle = pattern;
+  ctx.translate(48, 0);
+  ctx.fillRect(-48, 0, 100, 50);
+
+  ctx.fillStyle = '#0f0';
+  ctx.fillRect(0, 0, 16, 50);
+
+  _assertPixel(canvas, 1,1, 0,255,0,255);
+  _assertPixel(canvas, 50,1, 0,255,0,255);
+  _assertPixel(canvas, 98,1, 0,255,0,255);
+  _assertPixel(canvas, 1,48, 0,255,0,255);
+  _assertPixel(canvas, 50,48, 0,255,0,255);
+  _assertPixel(canvas, 98,48, 0,255,0,255);
+  t.done();
+}, "");
 done();
diff --git a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.paint.repeaty.outside.html b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.paint.repeaty.outside.html
index f7fcc3e..ad2bd338 100644
--- a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.paint.repeaty.outside.html
+++ b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.paint.repeaty.outside.html
@@ -10,40 +10,28 @@
 
 
 <script>
-var t = async_test("");
-var t_pass = t.done.bind(t);
-var t_fail = t.step_func(function(reason) {
-    throw reason;
-});
-t.step(function() {
+promise_test(async t => {
 
   var canvas = new OffscreenCanvas(100, 50);
   var ctx = canvas.getContext('2d');
 
   ctx.fillStyle = '#0f0';
   ctx.fillRect(0, 0, 100, 50);
-  var promise = new Promise(function(resolve, reject) {
-      var xhr = new XMLHttpRequest();
-      xhr.open("GET", '/images/red-16x16.png');
-      xhr.responseType = 'blob';
-      xhr.send();
-      xhr.onload = function() {
-          resolve(xhr.response);
-      };
-  });
-  promise.then(function(response) {
-      return createImageBitmap(response).then(bitmap => {
-          var pattern = ctx.createPattern(bitmap, 'repeat-y');
-          ctx.fillStyle = pattern;
-          ctx.fillRect(0, 0, 100, 50);
-          ctx.fillStyle = '#0f0';
-          ctx.fillRect(0, 0, 16, 50);
-          _assertPixel(canvas, 1,1, 0,255,0,255);
-          _assertPixel(canvas, 98,1, 0,255,0,255);
-          _assertPixel(canvas, 1,48, 0,255,0,255);
-          _assertPixel(canvas, 98,48, 0,255,0,255);
-      });
-  }).then(t_pass, t_fail);
 
-});
+  var response = await fetch('/images/red-16x16.png')
+  var blob = await response.blob();
+  var img = await createImageBitmap(blob);
+  var pattern = ctx.createPattern(img, 'repeat-y');
+  ctx.fillStyle = pattern;
+  ctx.fillRect(0, 0, 100, 50);
+
+  ctx.fillStyle = '#0f0';
+  ctx.fillRect(0, 0, 16, 50);
+
+  _assertPixel(canvas, 1,1, 0,255,0,255);
+  _assertPixel(canvas, 98,1, 0,255,0,255);
+  _assertPixel(canvas, 1,48, 0,255,0,255);
+  _assertPixel(canvas, 98,48, 0,255,0,255);
+
+}, "");
 </script>
diff --git a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.paint.repeaty.outside.worker.js b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.paint.repeaty.outside.worker.js
index 7201c3f1..c4feba1 100644
--- a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.paint.repeaty.outside.worker.js
+++ b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.paint.repeaty.outside.worker.js
@@ -6,39 +6,28 @@
 importScripts("/resources/testharness.js");
 importScripts("/html/canvas/resources/canvas-tests.js");
 
-var t = async_test("");
-var t_pass = t.done.bind(t);
-var t_fail = t.step_func(function(reason) {
-    throw reason;
-});
-t.step(function() {
+promise_test(async t => {
 
   var canvas = new OffscreenCanvas(100, 50);
   var ctx = canvas.getContext('2d');
 
   ctx.fillStyle = '#0f0';
   ctx.fillRect(0, 0, 100, 50);
-  var promise = new Promise(function(resolve, reject) {
-      var xhr = new XMLHttpRequest();
-      xhr.open("GET", '/images/red-16x16.png');
-      xhr.responseType = 'blob';
-      xhr.send();
-      xhr.onload = function() {
-          resolve(xhr.response);
-      };
-  });
-  promise.then(function(response) {
-      return createImageBitmap(response).then(bitmap => {
-          var pattern = ctx.createPattern(bitmap, 'repeat-y');
-          ctx.fillStyle = pattern;
-          ctx.fillRect(0, 0, 100, 50);
-          ctx.fillStyle = '#0f0';
-          ctx.fillRect(0, 0, 16, 50);
-          _assertPixel(canvas, 1,1, 0,255,0,255);
-          _assertPixel(canvas, 98,1, 0,255,0,255);
-          _assertPixel(canvas, 1,48, 0,255,0,255);
-          _assertPixel(canvas, 98,48, 0,255,0,255);
-      });
-  }).then(t_pass, t_fail);
-});
+
+  var response = await fetch('/images/red-16x16.png')
+  var blob = await response.blob();
+  var img = await createImageBitmap(blob);
+  var pattern = ctx.createPattern(img, 'repeat-y');
+  ctx.fillStyle = pattern;
+  ctx.fillRect(0, 0, 100, 50);
+
+  ctx.fillStyle = '#0f0';
+  ctx.fillRect(0, 0, 16, 50);
+
+  _assertPixel(canvas, 1,1, 0,255,0,255);
+  _assertPixel(canvas, 98,1, 0,255,0,255);
+  _assertPixel(canvas, 1,48, 0,255,0,255);
+  _assertPixel(canvas, 98,48, 0,255,0,255);
+  t.done();
+}, "");
 done();
diff --git a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.repeat.empty.html b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.repeat.empty.html
index 550edec..c219789 100644
--- a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.repeat.empty.html
+++ b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.repeat.empty.html
@@ -10,38 +10,24 @@
 
 
 <script>
-var t = async_test("");
-var t_pass = t.done.bind(t);
-var t_fail = t.step_func(function(reason) {
-    throw reason;
-});
-t.step(function() {
+promise_test(async t => {
 
   var canvas = new OffscreenCanvas(100, 50);
   var ctx = canvas.getContext('2d');
 
   ctx.fillStyle = '#f00';
   ctx.fillRect(0, 0, 100, 50);
-  var promise = new Promise(function(resolve, reject) {
-      var xhr = new XMLHttpRequest();
-      xhr.open("GET", '/images/green-1x1.png');
-      xhr.responseType = 'blob';
-      xhr.send();
-      xhr.onload = function() {
-          resolve(xhr.response);
-      };
-  });
-  promise.then(function(response) {
-      return createImageBitmap(response).then(bitmap => {
-          var pattern = ctx.createPattern(bitmap, "");
-          ctx.fillStyle = pattern;
-          ctx.fillRect(0, 0, 200, 50);
-          _assertPixel(canvas, 1,1, 0,255,0,255);
-          _assertPixel(canvas, 98,1, 0,255,0,255);
-          _assertPixel(canvas, 1,48, 0,255,0,255);
-          _assertPixel(canvas, 98,48, 0,255,0,255);
-      });
-  }).then(t_pass, t_fail);
+  var response = await fetch('/images/green-1x1.png')
+  var blob = await response.blob();
+  var img = await createImageBitmap(blob);
+  var pattern = ctx.createPattern(img, "");
+  ctx.fillStyle = pattern;
+  ctx.fillRect(0, 0, 200, 50);
 
-});
+  _assertPixel(canvas, 1,1, 0,255,0,255);
+  _assertPixel(canvas, 98,1, 0,255,0,255);
+  _assertPixel(canvas, 1,48, 0,255,0,255);
+  _assertPixel(canvas, 98,48, 0,255,0,255);
+
+}, "");
 </script>
diff --git a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.repeat.empty.worker.js b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.repeat.empty.worker.js
index 9777e18..5aefc0da 100644
--- a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.repeat.empty.worker.js
+++ b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.repeat.empty.worker.js
@@ -6,37 +6,24 @@
 importScripts("/resources/testharness.js");
 importScripts("/html/canvas/resources/canvas-tests.js");
 
-var t = async_test("");
-var t_pass = t.done.bind(t);
-var t_fail = t.step_func(function(reason) {
-    throw reason;
-});
-t.step(function() {
+promise_test(async t => {
 
   var canvas = new OffscreenCanvas(100, 50);
   var ctx = canvas.getContext('2d');
 
   ctx.fillStyle = '#f00';
   ctx.fillRect(0, 0, 100, 50);
-  var promise = new Promise(function(resolve, reject) {
-      var xhr = new XMLHttpRequest();
-      xhr.open("GET", '/images/green-1x1.png');
-      xhr.responseType = 'blob';
-      xhr.send();
-      xhr.onload = function() {
-          resolve(xhr.response);
-      };
-  });
-  promise.then(function(response) {
-      return createImageBitmap(response).then(bitmap => {
-          var pattern = ctx.createPattern(bitmap, "");
-          ctx.fillStyle = pattern;
-          ctx.fillRect(0, 0, 200, 50);
-          _assertPixel(canvas, 1,1, 0,255,0,255);
-          _assertPixel(canvas, 98,1, 0,255,0,255);
-          _assertPixel(canvas, 1,48, 0,255,0,255);
-          _assertPixel(canvas, 98,48, 0,255,0,255);
-      });
-  }).then(t_pass, t_fail);
-});
+  var response = await fetch('/images/green-1x1.png')
+  var blob = await response.blob();
+  var img = await createImageBitmap(blob);
+  var pattern = ctx.createPattern(img, "");
+  ctx.fillStyle = pattern;
+  ctx.fillRect(0, 0, 200, 50);
+
+  _assertPixel(canvas, 1,1, 0,255,0,255);
+  _assertPixel(canvas, 98,1, 0,255,0,255);
+  _assertPixel(canvas, 1,48, 0,255,0,255);
+  _assertPixel(canvas, 98,48, 0,255,0,255);
+  t.done();
+}, "");
 done();
diff --git a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.transform.identity.html b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.transform.identity.html
new file mode 100644
index 0000000..2d73296
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.transform.identity.html
@@ -0,0 +1,40 @@
+<!DOCTYPE html>
+<!-- DO NOT EDIT! This test has been generated by /html/canvas/tools/gentest.py. -->
+<title>OffscreenCanvas test: 2d.pattern.transform.identity</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/html/canvas/resources/canvas-tests.js"></script>
+
+<h1>2d.pattern.transform.identity</h1>
+<p class="desc"></p>
+
+
+<script>
+var t = async_test("");
+var t_pass = t.done.bind(t);
+var t_fail = t.step_func(function(reason) {
+    throw reason;
+});
+t.step(function() {
+
+  var canvas = new OffscreenCanvas(100, 50);
+  var ctx = canvas.getContext('2d');
+
+  var canvas2 = new OffscreenCanvas(100, 50);
+  var pattern = ctx.createPattern(canvas2, 'no-repeat');
+  pattern.setTransform(new DOMMatrix());
+
+  ctx.fillStyle = '#0f0';
+  ctx.fillRect(0, 0, 100, 50);
+  ctx.fillStyle = '#f00';
+  ctx.fillStyle = pattern;
+  ctx.fillRect(0, 0, 100, 50);
+
+  _assertPixel(canvas, 1,1, 0,255,0,255);
+  _assertPixel(canvas, 98,1, 0,255,0,255);
+  _assertPixel(canvas, 1,48, 0,255,0,255);
+  _assertPixel(canvas, 98,48, 0,255,0,255);
+  t.done();
+
+});
+</script>
diff --git a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.transform.identity.worker.js b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.transform.identity.worker.js
new file mode 100644
index 0000000..a33c771
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.transform.identity.worker.js
@@ -0,0 +1,35 @@
+// DO NOT EDIT! This test has been generated by /html/canvas/tools/gentest.py.
+// OffscreenCanvas test in a worker:2d.pattern.transform.identity
+// Description:
+// Note:
+
+importScripts("/resources/testharness.js");
+importScripts("/html/canvas/resources/canvas-tests.js");
+
+var t = async_test("");
+var t_pass = t.done.bind(t);
+var t_fail = t.step_func(function(reason) {
+    throw reason;
+});
+t.step(function() {
+
+  var canvas = new OffscreenCanvas(100, 50);
+  var ctx = canvas.getContext('2d');
+
+  var canvas2 = new OffscreenCanvas(100, 50);
+  var pattern = ctx.createPattern(canvas2, 'no-repeat');
+  pattern.setTransform(new DOMMatrix());
+
+  ctx.fillStyle = '#0f0';
+  ctx.fillRect(0, 0, 100, 50);
+  ctx.fillStyle = '#f00';
+  ctx.fillStyle = pattern;
+  ctx.fillRect(0, 0, 100, 50);
+
+  _assertPixel(canvas, 1,1, 0,255,0,255);
+  _assertPixel(canvas, 98,1, 0,255,0,255);
+  _assertPixel(canvas, 1,48, 0,255,0,255);
+  _assertPixel(canvas, 98,48, 0,255,0,255);
+  t.done();
+});
+done();
diff --git a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.transform.infinity.html b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.transform.infinity.html
new file mode 100644
index 0000000..aa58ad11
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.transform.infinity.html
@@ -0,0 +1,40 @@
+<!DOCTYPE html>
+<!-- DO NOT EDIT! This test has been generated by /html/canvas/tools/gentest.py. -->
+<title>OffscreenCanvas test: 2d.pattern.transform.infinity</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/html/canvas/resources/canvas-tests.js"></script>
+
+<h1>2d.pattern.transform.infinity</h1>
+<p class="desc"></p>
+
+
+<script>
+var t = async_test("");
+var t_pass = t.done.bind(t);
+var t_fail = t.step_func(function(reason) {
+    throw reason;
+});
+t.step(function() {
+
+  var canvas = new OffscreenCanvas(100, 50);
+  var ctx = canvas.getContext('2d');
+
+  var canvas2 = new OffscreenCanvas(100, 50);
+  var pattern = ctx.createPattern(canvas2, 'no-repeat');
+  pattern.setTransform({a: Infinity});
+
+  ctx.fillStyle = '#0f0';
+  ctx.fillRect(0, 0, 100, 50);
+  ctx.fillStyle = '#f00';
+  ctx.fillStyle = pattern;
+  ctx.fillRect(0, 0, 100, 50);
+
+  _assertPixel(canvas, 1,1, 0,255,0,255);
+  _assertPixel(canvas, 98,1, 0,255,0,255);
+  _assertPixel(canvas, 1,48, 0,255,0,255);
+  _assertPixel(canvas, 98,48, 0,255,0,255);
+  t.done();
+
+});
+</script>
diff --git a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.transform.infinity.worker.js b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.transform.infinity.worker.js
new file mode 100644
index 0000000..999739b8
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.transform.infinity.worker.js
@@ -0,0 +1,35 @@
+// DO NOT EDIT! This test has been generated by /html/canvas/tools/gentest.py.
+// OffscreenCanvas test in a worker:2d.pattern.transform.infinity
+// Description:
+// Note:
+
+importScripts("/resources/testharness.js");
+importScripts("/html/canvas/resources/canvas-tests.js");
+
+var t = async_test("");
+var t_pass = t.done.bind(t);
+var t_fail = t.step_func(function(reason) {
+    throw reason;
+});
+t.step(function() {
+
+  var canvas = new OffscreenCanvas(100, 50);
+  var ctx = canvas.getContext('2d');
+
+  var canvas2 = new OffscreenCanvas(100, 50);
+  var pattern = ctx.createPattern(canvas2, 'no-repeat');
+  pattern.setTransform({a: Infinity});
+
+  ctx.fillStyle = '#0f0';
+  ctx.fillRect(0, 0, 100, 50);
+  ctx.fillStyle = '#f00';
+  ctx.fillStyle = pattern;
+  ctx.fillRect(0, 0, 100, 50);
+
+  _assertPixel(canvas, 1,1, 0,255,0,255);
+  _assertPixel(canvas, 98,1, 0,255,0,255);
+  _assertPixel(canvas, 1,48, 0,255,0,255);
+  _assertPixel(canvas, 98,48, 0,255,0,255);
+  t.done();
+});
+done();
diff --git a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.transform.invalid.html b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.transform.invalid.html
new file mode 100644
index 0000000..f2420d4
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.transform.invalid.html
@@ -0,0 +1,29 @@
+<!DOCTYPE html>
+<!-- DO NOT EDIT! This test has been generated by /html/canvas/tools/gentest.py. -->
+<title>OffscreenCanvas test: 2d.pattern.transform.invalid</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/html/canvas/resources/canvas-tests.js"></script>
+
+<h1>2d.pattern.transform.invalid</h1>
+<p class="desc"></p>
+
+
+<script>
+var t = async_test("");
+var t_pass = t.done.bind(t);
+var t_fail = t.step_func(function(reason) {
+    throw reason;
+});
+t.step(function() {
+
+  var canvas = new OffscreenCanvas(100, 50);
+  var ctx = canvas.getContext('2d');
+
+  var canvas2 = new OffscreenCanvas(100, 50);
+  var pattern = ctx.createPattern(canvas2, 'no-repeat');
+  assert_throws_js(TypeError, function() { pattern.setTransform({a: 1, m11: 2}); });
+  t.done();
+
+});
+</script>
diff --git a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.transform.invalid.worker.js b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.transform.invalid.worker.js
new file mode 100644
index 0000000..16395180
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/fill-and-stroke-styles/2d.pattern.transform.invalid.worker.js
@@ -0,0 +1,24 @@
+// DO NOT EDIT! This test has been generated by /html/canvas/tools/gentest.py.
+// OffscreenCanvas test in a worker:2d.pattern.transform.invalid
+// Description:
+// Note:
+
+importScripts("/resources/testharness.js");
+importScripts("/html/canvas/resources/canvas-tests.js");
+
+var t = async_test("");
+var t_pass = t.done.bind(t);
+var t_fail = t.step_func(function(reason) {
+    throw reason;
+});
+t.step(function() {
+
+  var canvas = new OffscreenCanvas(100, 50);
+  var ctx = canvas.getContext('2d');
+
+  var canvas2 = new OffscreenCanvas(100, 50);
+  var pattern = ctx.createPattern(canvas2, 'no-repeat');
+  assert_throws_js(TypeError, function() { pattern.setTransform({a: 1, m11: 2}); });
+  t.done();
+});
+done();
diff --git a/third_party/blink/web_tests/external/wpt/html/canvas/tools/gentestutilsunion.py b/third_party/blink/web_tests/external/wpt/html/canvas/tools/gentestutilsunion.py
index 0542e325..cf141f2f 100644
--- a/third_party/blink/web_tests/external/wpt/html/canvas/tools/gentestutilsunion.py
+++ b/third_party/blink/web_tests/external/wpt/html/canvas/tools/gentestutilsunion.py
@@ -500,7 +500,11 @@
         for variant_name, variant_params in variants.items():
             test = original_test.copy()
             if variant_name or variant_params:
-                test['name'] += '.' + variant_name
+                # Append variant name. Variant names starting with '_' are
+                # not appended, which is useful to create variants with the same
+                # name in different folders (element vs. offscreen).
+                if not variant_name.startswith('_'):
+                    test['name'] += '.' + variant_name
                 test.update(variant_params)
 
             name = test['name']
diff --git a/third_party/blink/web_tests/external/wpt/html/canvas/tools/yaml/element/fill-and-stroke-styles.yaml b/third_party/blink/web_tests/external/wpt/html/canvas/tools/yaml-new/fill-and-stroke-styles.yaml
similarity index 92%
rename from third_party/blink/web_tests/external/wpt/html/canvas/tools/yaml/element/fill-and-stroke-styles.yaml
rename to third_party/blink/web_tests/external/wpt/html/canvas/tools/yaml-new/fill-and-stroke-styles.yaml
index 9d35dde..e30eb01 100644
--- a/third_party/blink/web_tests/external/wpt/html/canvas/tools/yaml/element/fill-and-stroke-styles.yaml
+++ b/third_party/blink/web_tests/external/wpt/html/canvas/tools/yaml-new/fill-and-stroke-styles.yaml
@@ -1,5 +1,6 @@
 - name: 2d.fillStyle.parse.current.basic
   desc: currentColor is computed from the canvas element
+  canvasType: ['HtmlCanvas']
   code: |
     canvas.setAttribute('style', 'color: #0f0');
     ctx.fillStyle = '#f00';
@@ -10,6 +11,7 @@
 
 - name: 2d.fillStyle.parse.current.changed
   desc: currentColor is computed when the attribute is set, not when it is painted
+  canvasType: ['HtmlCanvas']
   code: |
     canvas.setAttribute('style', 'color: #0f0');
     ctx.fillStyle = '#f00';
@@ -21,6 +23,7 @@
 
 - name: 2d.fillStyle.parse.current.removed
   desc: currentColor is solid black when the canvas element is not in a document
+  canvasType: ['HtmlCanvas']
   code: |
     // Try not to let it undetectably incorrectly pick up opaque-black
     // from other parts of the document:
@@ -116,14 +119,16 @@
   desc: window.CanvasGradient exists and has the right properties
   notes: &bindings Defined in "Web IDL" (draft)
   code: |
-    @assert window.CanvasGradient !== undefined;
-    @assert window.CanvasGradient.prototype.addColorStop !== undefined;
+    {% set root = 'self' if canvas_type == 'worker' else 'window' %}
+    @assert {{ root }}.CanvasGradient !== undefined;
+    @assert {{ root }}.CanvasGradient.prototype.addColorStop !== undefined;
 
 - name: 2d.gradient.object.return
   desc: createLinearGradient() and createRadialGradient() returns objects implementing
     CanvasGradient
   code: |
-    window.CanvasGradient.prototype.thisImplementsCanvasGradient = true;
+    {% set root = 'self' if canvas_type == 'worker' else 'window' %}
+    {{ root }}.CanvasGradient.prototype.thisImplementsCanvasGradient = true;
 
     var g1 = ctx.createLinearGradient(0, 0, 100, 0);
     @assert g1.addColorStop !== undefined;
@@ -471,15 +476,23 @@
   code: |
     ctx.fillStyle = '#f00';
     ctx.fillRect(0, 0, 100, 50);
-    var g = document.createElement('canvas').getContext('2d').createLinearGradient(0, 0, 100, 0);
+    var g = {{ create_canvas }}.getContext('2d').createLinearGradient(0, 0, 100, 0);
     g.addColorStop(0, '#0f0');
     g.addColorStop(1, '#0f0');
     ctx.fillStyle = g;
     ctx.fillRect(0, 0, 100, 50);
     @assert pixel 50,25 ==~ 0,255,0,255;
   expected: green
+  variants:
+    _HtmlCanvas:
+      canvasType: ['HtmlCanvas']
+      create_canvas: document.createElement('canvas')
+    _OffscreenCanvas:
+      canvasType: ['OffscreenCanvas', 'Worker']
+      create_canvas: new OffscreenCanvas(100, 50)
 
 - name: 2d.gradient.object.current
+  canvasType: ['HtmlCanvas']
   code: |
     canvas.setAttribute('style', 'color: #f00');
 
@@ -1113,13 +1126,25 @@
   images:
   - green.png
   code: |
-    @assert window.CanvasPattern !== undefined;
+    {% set root = 'self' if canvas_type == 'worker' else 'window' %}
+    @assert {{ root }}.CanvasPattern !== undefined;
 
-    window.CanvasPattern.prototype.thisImplementsCanvasPattern = true;
+    {{ root }}.CanvasPattern.prototype.thisImplementsCanvasPattern = true;
 
-    var img = document.getElementById('green.png');
+    {{ load_image }}
     var pattern = ctx.createPattern(img, 'no-repeat');
     @assert pattern.thisImplementsCanvasPattern;
+  variants: &load-image-variant-definition
+    _HtmlCanvas:
+      canvasType: ['HtmlCanvas']
+      load_image: var img = document.getElementById('{{ (images or svgimages)[0] }}');
+    _OffscreenCanvas:
+      canvasType: ['OffscreenCanvas', 'Worker']
+      test_type: promise
+      load_image: |-
+        var response = await fetch('/images/{{ (images or svgimages)[0] }}')
+        var blob = await response.blob();
+        var img = await createImageBitmap(blob);
 
 - name: 2d.pattern.basic.image
   images:
@@ -1127,7 +1152,7 @@
   code: |
     ctx.fillStyle = '#f00';
     ctx.fillRect(0, 0, 100, 50);
-    var img = document.getElementById('green.png');
+    {{ load_image }}
     var pattern = ctx.createPattern(img, 'no-repeat');
     ctx.fillStyle = pattern;
     ctx.fillRect(0, 0, 100, 50);
@@ -1137,15 +1162,14 @@
     @assert pixel 1,48 == 0,255,0,255;
     @assert pixel 98,48 == 0,255,0,255;
   expected: green
+  variants: *load-image-variant-definition
 
 - name: 2d.pattern.basic.canvas
   code: |
     ctx.fillStyle = '#f00';
     ctx.fillRect(0, 0, 100, 50);
 
-    var canvas2 = document.createElement('canvas');
-    canvas2.width = 100;
-    canvas2.height = 50;
+    {{ create_canvas2 }}
     var ctx2 = canvas2.getContext('2d');
     ctx2.fillStyle = '#0f0';
     ctx2.fillRect(0, 0, 100, 50);
@@ -1164,6 +1188,17 @@
     @assert pixel 50,48 == 0,255,0,255;
     @assert pixel 98,48 == 0,255,0,255;
   expected: green
+  variants: &create-canvas2-variant-definition
+    _HtmlCanvas:
+      canvasType: ['HtmlCanvas']
+      create_canvas2: |-
+        var canvas2 = document.createElement('canvas');
+        canvas2.width = 100;
+        canvas2.height = 50;
+    _OffscreenCanvas:
+      canvasType: ['OffscreenCanvas', 'Worker']
+      create_canvas2: |-
+        var canvas2 = new OffscreenCanvas(100, 50);
 
 - name: 2d.pattern.basic.zerocanvas
   code: |
@@ -1187,9 +1222,7 @@
 
 - name: 2d.pattern.basic.nocontext
   code: |
-    var canvas2 = document.createElement('canvas');
-    canvas2.width = 100;
-    canvas2.height = 50;
+    {{ create_canvas2 }}
     var pattern = ctx.createPattern(canvas2, 'no-repeat');
 
     ctx.fillStyle = '#0f0';
@@ -1203,12 +1236,11 @@
     @assert pixel 1,48 == 0,255,0,255;
     @assert pixel 98,48 == 0,255,0,255;
   expected: green
+  variants: *create-canvas2-variant-definition
 
 - name: 2d.pattern.transform.identity
   code: |
-    var canvas2 = document.createElement('canvas');
-    canvas2.width = 100;
-    canvas2.height = 50;
+    {{ create_canvas2 }}
     var pattern = ctx.createPattern(canvas2, 'no-repeat');
     pattern.setTransform(new DOMMatrix());
 
@@ -1223,12 +1255,11 @@
     @assert pixel 1,48 == 0,255,0,255;
     @assert pixel 98,48 == 0,255,0,255;
   expected: green
+  variants: *create-canvas2-variant-definition
 
 - name: 2d.pattern.transform.infinity
   code: |
-    var canvas2 = document.createElement('canvas');
-    canvas2.width = 100;
-    canvas2.height = 50;
+    {{ create_canvas2 }}
     var pattern = ctx.createPattern(canvas2, 'no-repeat');
     pattern.setTransform({a: Infinity});
 
@@ -1243,14 +1274,14 @@
     @assert pixel 1,48 == 0,255,0,255;
     @assert pixel 98,48 == 0,255,0,255;
   expected: green
+  variants: *create-canvas2-variant-definition
 
 - name: 2d.pattern.transform.invalid
   code: |
-    var canvas2 = document.createElement('canvas');
-    canvas2.width = 100;
-    canvas2.height = 50;
+    {{ create_canvas2 }}
     var pattern = ctx.createPattern(canvas2, 'no-repeat');
     @assert throws TypeError pattern.setTransform({a: 1, m11: 2});
+  variants: *create-canvas2-variant-definition
 
 - name: 2d.pattern.image.undefined
   notes: *bindings
@@ -1268,11 +1299,13 @@
     @assert throws TypeError ctx.createPattern('../images/red.png', 'repeat');
 
 - name: 2d.pattern.image.incomplete.nosrc
+  canvasType: ['HtmlCanvas']
   code: |
     var img = new Image();
     @assert ctx.createPattern(img, 'repeat') === null;
 
 - name: 2d.pattern.image.incomplete.immediate
+  canvasType: ['HtmlCanvas']
   images:
   - red.png
   code: |
@@ -1285,6 +1318,7 @@
     @assert ctx.createPattern(img, 'repeat') === null; @moz-todo
 
 - name: 2d.pattern.image.incomplete.reload
+  canvasType: ['HtmlCanvas']
   images:
   - yellow.png
   - red.png
@@ -1299,6 +1333,7 @@
     @assert ctx.createPattern(img, 'repeat') === null; @moz-todo
 
 - name: 2d.pattern.image.incomplete.emptysrc
+  canvasType: ['HtmlCanvas']
   images:
   - red.png
   mozilla: {throws: !!null ''}
@@ -1308,6 +1343,7 @@
     @assert ctx.createPattern(img, 'repeat') === null;
 
 - name: 2d.pattern.image.incomplete.removedsrc
+  canvasType: ['HtmlCanvas']
   images:
   - red.png
   mozilla: {throws: !!null ''}
@@ -1317,6 +1353,7 @@
     @assert ctx.createPattern(img, 'repeat') === null;
 
 - name: 2d.pattern.image.broken
+  canvasType: ['HtmlCanvas']
   images:
   - broken.png
   code: |
@@ -1324,6 +1361,7 @@
     @assert throws INVALID_STATE_ERR ctx.createPattern(img, 'repeat');
 
 - name: 2d.pattern.image.nonexistent
+  canvasType: ['HtmlCanvas']
   images:
   - no-such-image-really.png
   code: |
@@ -1331,6 +1369,7 @@
     @assert throws INVALID_STATE_ERR ctx.createPattern(img, 'repeat');
 
 - name: 2d.pattern.svgimage.nonexistent
+  canvasType: ['HtmlCanvas']
   svgimages:
   - no-such-image-really.png
   code: |
@@ -1338,6 +1377,7 @@
     @assert throws INVALID_STATE_ERR ctx.createPattern(img, 'repeat');
 
 - name: 2d.pattern.image.nonexistent-but-loading
+  canvasType: ['HtmlCanvas']
   code: |
     var img = document.createElement("img");
     img.src = "/images/no-such-image-really.png";
@@ -1347,6 +1387,7 @@
     @assert ctx.createPattern(img, 'repeat') === null;
 
 - name: 2d.pattern.image.nosrc
+  canvasType: ['HtmlCanvas']
   code: |
     var img = document.createElement("img");
     @assert ctx.createPattern(img, 'repeat') === null;
@@ -1354,6 +1395,7 @@
     @assert ctx.createPattern(img, 'repeat') === null;
 
 - name: 2d.pattern.image.zerowidth
+  canvasType: ['HtmlCanvas']
   images:
   - red-zerowidth.svg
   code: |
@@ -1361,6 +1403,7 @@
     @assert ctx.createPattern(img, 'repeat') === null;
 
 - name: 2d.pattern.image.zeroheight
+  canvasType: ['HtmlCanvas']
   images:
   - red-zeroheight.svg
   code: |
@@ -1368,6 +1411,7 @@
     @assert ctx.createPattern(img, 'repeat') === null;
 
 - name: 2d.pattern.svgimage.zerowidth
+  canvasType: ['HtmlCanvas']
   svgimages:
   - red-zerowidth.svg
   code: |
@@ -1375,6 +1419,7 @@
     @assert ctx.createPattern(img, 'repeat') === null;
 
 - name: 2d.pattern.svgimage.zeroheight
+  canvasType: ['HtmlCanvas']
   svgimages:
   - red-zeroheight.svg
   code: |
@@ -1387,7 +1432,7 @@
   code: |
     ctx.fillStyle = '#f00';
     ctx.fillRect(0, 0, 100, 50);
-    var img = document.getElementById('green-1x1.png');
+    {{ load_image }}
     var pattern = ctx.createPattern(img, "");
     ctx.fillStyle = pattern;
     ctx.fillRect(0, 0, 200, 50);
@@ -1397,6 +1442,7 @@
     @assert pixel 1,48 == 0,255,0,255;
     @assert pixel 98,48 == 0,255,0,255;
   expected: green
+  variants: *load-image-variant-definition
 
 - name: 2d.pattern.repeat.null
   code: |
@@ -1423,6 +1469,7 @@
     @assert throws SYNTAX_ERR ctx.createPattern(canvas, "repeat\0");
 
 - name: 2d.pattern.modify.image1
+  canvasType: ['HtmlCanvas']
   images:
   - green.png
   code: |
@@ -1443,6 +1490,7 @@
   expected: green
 
 - name: 2d.pattern.modify.image2
+  canvasType: ['HtmlCanvas']
   images:
   - green.png
   code: |
@@ -1467,10 +1515,9 @@
   expected: green
 
 - name: 2d.pattern.modify.canvas1
+  canvasType: ['HtmlCanvas']
   code: |
-    var canvas2 = document.createElement('canvas');
-    canvas2.width = 100;
-    canvas2.height = 50;
+    {{ create_canvas2 }}
     var ctx2 = canvas2.getContext('2d');
     ctx2.fillStyle = '#0f0';
     ctx2.fillRect(0, 0, 100, 50);
@@ -1488,12 +1535,11 @@
     @assert pixel 1,48 == 0,255,0,255;
     @assert pixel 98,48 == 0,255,0,255;
   expected: green
+  variants: *create-canvas2-variant-definition
 
 - name: 2d.pattern.modify.canvas2
   code: |
-    var canvas2 = document.createElement('canvas');
-    canvas2.width = 100;
-    canvas2.height = 50;
+    {{ create_canvas2 }}
     var ctx2 = canvas2.getContext('2d');
     ctx2.fillStyle = '#0f0';
     ctx2.fillRect(0, 0, 100, 50);
@@ -1515,14 +1561,15 @@
     @assert pixel 1,48 == 0,255,0,255;
     @assert pixel 98,48 == 0,255,0,255;
   expected: green
+  variants: *create-canvas2-variant-definition
 
 - name: 2d.pattern.crosscanvas
   images:
   - green.png
   code: |
-    var img = document.getElementById('green.png');
+    {{ load_image }}
 
-    var pattern = document.createElement('canvas').getContext('2d').createPattern(img, 'no-repeat');
+    var pattern = {{ create_canvas }}.getContext('2d').createPattern(img, 'no-repeat');
     ctx.fillStyle = '#f00';
     ctx.fillRect(0, 0, 100, 50);
     ctx.fillStyle = pattern;
@@ -1530,6 +1577,19 @@
 
     @assert pixel 50,25 == 0,255,0,255;
   expected: green
+  variants:
+    _HtmlCanvas:
+      canvasType: ['HtmlCanvas']
+      load_image: var img = document.getElementById('{{ images[0] }}');
+      create_canvas: document.createElement('canvas')
+    _OffscreenCanvas:
+      canvasType: ['OffscreenCanvas', 'Worker']
+      test_type: promise
+      load_image: |-
+        var response = await fetch('/images/{{ images[0] }}')
+        var blob = await response.blob();
+        var img = await createImageBitmap(blob);
+      create_canvas: new OffscreenCanvas(100, 50)
 
 - name: 2d.pattern.paint.norepeat.basic
   images:
@@ -1538,7 +1598,7 @@
     ctx.fillStyle = '#f00';
     ctx.fillRect(0, 0, 100, 50);
 
-    var img = document.getElementById('green.png');
+    {{ load_image }}
     var pattern = ctx.createPattern(img, 'no-repeat');
     ctx.fillStyle = pattern;
     ctx.fillRect(0, 0, 100, 50);
@@ -1548,6 +1608,7 @@
     @assert pixel 1,48 == 0,255,0,255;
     @assert pixel 98,48 == 0,255,0,255;
   expected: green
+  variants: *load-image-variant-definition
 
 - name: 2d.pattern.paint.norepeat.outside
   images:
@@ -1556,7 +1617,7 @@
     ctx.fillStyle = '#f00';
     ctx.fillRect(0, 0, 100, 50);
 
-    var img = document.getElementById('red.png');
+    {{ load_image }}
     var pattern = ctx.createPattern(img, 'no-repeat');
     ctx.fillStyle = '#0f0';
     ctx.fillRect(0, 0, 100, 50);
@@ -1572,6 +1633,7 @@
     @assert pixel 1,48 == 0,255,0,255;
     @assert pixel 98,48 == 0,255,0,255;
   expected: green
+  variants: *load-image-variant-definition
 
 - name: 2d.pattern.paint.norepeat.coord1
   images:
@@ -1582,7 +1644,7 @@
     ctx.fillStyle = '#f00';
     ctx.fillRect(50, 0, 50, 50);
 
-    var img = document.getElementById('green.png');
+    {{ load_image }}
     var pattern = ctx.createPattern(img, 'no-repeat');
     ctx.fillStyle = pattern;
     ctx.translate(50, 0);
@@ -1593,12 +1655,13 @@
     @assert pixel 1,48 == 0,255,0,255;
     @assert pixel 98,48 == 0,255,0,255;
   expected: green
+  variants: *load-image-variant-definition
 
 - name: 2d.pattern.paint.norepeat.coord2
   images:
   - green.png
   code: |
-    var img = document.getElementById('green.png');
+    {{ load_image }}
     var pattern = ctx.createPattern(img, 'no-repeat');
     ctx.fillStyle = pattern;
     ctx.fillRect(0, 0, 50, 50);
@@ -1615,6 +1678,7 @@
     @assert pixel 1,48 == 0,255,0,255;
     @assert pixel 98,48 == 0,255,0,255;
   expected: green
+  variants: *load-image-variant-definition
 
 - name: 2d.pattern.paint.norepeat.coord3
   images:
@@ -1623,7 +1687,7 @@
     ctx.fillStyle = '#0f0';
     ctx.fillRect(0, 0, 100, 50);
 
-    var img = document.getElementById('red.png');
+    {{ load_image }}
     var pattern = ctx.createPattern(img, 'no-repeat');
     ctx.fillStyle = pattern;
     ctx.translate(50, 25);
@@ -1637,6 +1701,7 @@
     @assert pixel 1,48 == 0,255,0,255;
     @assert pixel 98,48 == 0,255,0,255;
   expected: green
+  variants: *load-image-variant-definition
 
 - name: 2d.pattern.paint.repeat.basic
   images:
@@ -1645,7 +1710,7 @@
     ctx.fillStyle = '#f00';
     ctx.fillRect(0, 0, 100, 50);
 
-    var img = document.getElementById('green-16x16.png');
+    {{ load_image }}
     var pattern = ctx.createPattern(img, 'repeat');
     ctx.fillStyle = pattern;
     ctx.fillRect(0, 0, 100, 50);
@@ -1655,6 +1720,7 @@
     @assert pixel 1,48 == 0,255,0,255;
     @assert pixel 98,48 == 0,255,0,255;
   expected: green
+  variants: *load-image-variant-definition
 
 - name: 2d.pattern.paint.repeat.outside
   images:
@@ -1663,7 +1729,7 @@
     ctx.fillStyle = '#f00';
     ctx.fillRect(0, 0, 100, 50);
 
-    var img = document.getElementById('green-16x16.png');
+    {{ load_image }}
     var pattern = ctx.createPattern(img, 'repeat');
     ctx.fillStyle = pattern;
     ctx.translate(50, 25);
@@ -1674,6 +1740,7 @@
     @assert pixel 1,48 == 0,255,0,255;
     @assert pixel 98,48 == 0,255,0,255;
   expected: green
+  variants: *load-image-variant-definition
 
 - name: 2d.pattern.paint.repeat.coord1
   images:
@@ -1682,7 +1749,7 @@
     ctx.fillStyle = '#f00';
     ctx.fillRect(0, 0, 100, 50);
 
-    var img = document.getElementById('rgrg-256x256.png');
+    {{ load_image }}
     var pattern = ctx.createPattern(img, 'repeat');
     ctx.fillStyle = pattern;
     ctx.translate(-128, -78);
@@ -1693,12 +1760,13 @@
     @assert pixel 1,48 == 0,255,0,255;
     @assert pixel 98,48 == 0,255,0,255;
   expected: green
+  variants: *load-image-variant-definition
 
 - name: 2d.pattern.paint.repeat.coord2
   images:
   - ggrr-256x256.png
   code: |
-    var img = document.getElementById('ggrr-256x256.png');
+    {{ load_image }}
     var pattern = ctx.createPattern(img, 'repeat');
     ctx.fillStyle = pattern;
     ctx.fillRect(0, 0, 100, 50);
@@ -1708,12 +1776,13 @@
     @assert pixel 1,48 == 0,255,0,255;
     @assert pixel 98,48 == 0,255,0,255;
   expected: green
+  variants: *load-image-variant-definition
 
 - name: 2d.pattern.paint.repeat.coord3
   images:
   - rgrg-256x256.png
   code: |
-    var img = document.getElementById('rgrg-256x256.png');
+    {{ load_image }}
     var pattern = ctx.createPattern(img, 'repeat');
     ctx.fillStyle = pattern;
     ctx.fillRect(0, 0, 100, 50);
@@ -1726,6 +1795,7 @@
     @assert pixel 1,48 == 0,255,0,255;
     @assert pixel 98,48 == 0,255,0,255;
   expected: green
+  variants: *load-image-variant-definition
 
 - name: 2d.pattern.paint.repeatx.basic
   images:
@@ -1736,7 +1806,7 @@
     ctx.fillStyle = '#f00';
     ctx.fillRect(0, 0, 100, 16);
 
-    var img = document.getElementById('green-16x16.png');
+    {{ load_image }}
     var pattern = ctx.createPattern(img, 'repeat-x');
     ctx.fillStyle = pattern;
     ctx.fillRect(0, 0, 100, 50);
@@ -1746,6 +1816,7 @@
     @assert pixel 1,48 == 0,255,0,255;
     @assert pixel 98,48 == 0,255,0,255;
   expected: green
+  variants: *load-image-variant-definition
 
 - name: 2d.pattern.paint.repeatx.outside
   images:
@@ -1754,7 +1825,7 @@
     ctx.fillStyle = '#0f0';
     ctx.fillRect(0, 0, 100, 50);
 
-    var img = document.getElementById('red-16x16.png');
+    {{ load_image }}
     var pattern = ctx.createPattern(img, 'repeat-x');
     ctx.fillStyle = pattern;
     ctx.fillRect(0, 0, 100, 50);
@@ -1767,6 +1838,7 @@
     @assert pixel 1,48 == 0,255,0,255;
     @assert pixel 98,48 == 0,255,0,255;
   expected: green
+  variants: *load-image-variant-definition
 
 - name: 2d.pattern.paint.repeatx.coord1
   images:
@@ -1775,7 +1847,7 @@
     ctx.fillStyle = '#0f0';
     ctx.fillRect(0, 0, 100, 50);
 
-    var img = document.getElementById('red-16x16.png');
+    {{ load_image }}
     var pattern = ctx.createPattern(img, 'repeat-x');
     ctx.fillStyle = pattern;
     ctx.translate(0, 16);
@@ -1791,6 +1863,7 @@
     @assert pixel 1,48 == 0,255,0,255;
     @assert pixel 98,48 == 0,255,0,255;
   expected: green
+  variants: *load-image-variant-definition
 
 - name: 2d.pattern.paint.repeaty.basic
   images:
@@ -1801,7 +1874,7 @@
     ctx.fillStyle = '#f00';
     ctx.fillRect(0, 0, 16, 50);
 
-    var img = document.getElementById('green-16x16.png');
+    {{ load_image }}
     var pattern = ctx.createPattern(img, 'repeat-y');
     ctx.fillStyle = pattern;
     ctx.fillRect(0, 0, 100, 50);
@@ -1811,6 +1884,7 @@
     @assert pixel 1,48 == 0,255,0,255;
     @assert pixel 98,48 == 0,255,0,255;
   expected: green
+  variants: *load-image-variant-definition
 
 - name: 2d.pattern.paint.repeaty.outside
   images:
@@ -1819,7 +1893,7 @@
     ctx.fillStyle = '#0f0';
     ctx.fillRect(0, 0, 100, 50);
 
-    var img = document.getElementById('red-16x16.png');
+    {{ load_image }}
     var pattern = ctx.createPattern(img, 'repeat-y');
     ctx.fillStyle = pattern;
     ctx.fillRect(0, 0, 100, 50);
@@ -1832,6 +1906,7 @@
     @assert pixel 1,48 == 0,255,0,255;
     @assert pixel 98,48 == 0,255,0,255;
   expected: green
+  variants: *load-image-variant-definition
 
 - name: 2d.pattern.paint.repeaty.coord1
   images:
@@ -1840,7 +1915,7 @@
     ctx.fillStyle = '#0f0';
     ctx.fillRect(0, 0, 100, 50);
 
-    var img = document.getElementById('red-16x16.png');
+    {{ load_image }}
     var pattern = ctx.createPattern(img, 'repeat-y');
     ctx.fillStyle = pattern;
     ctx.translate(48, 0);
@@ -1856,6 +1931,7 @@
     @assert pixel 50,48 == 0,255,0,255;
     @assert pixel 98,48 == 0,255,0,255;
   expected: green
+  variants: *load-image-variant-definition
 
 - name: 2d.pattern.paint.orientation.image
   desc: Image patterns do not get flipped when painted
@@ -1865,7 +1941,7 @@
     ctx.fillStyle = '#f00';
     ctx.fillRect(0, 0, 100, 50);
 
-    var img = document.getElementById('rrgg-256x256.png');
+    {{ load_image }}
     var pattern = ctx.createPattern(img, 'no-repeat');
     ctx.fillStyle = pattern;
     ctx.save();
@@ -1881,6 +1957,7 @@
     @assert pixel 1,48 == 0,255,0,255;
     @assert pixel 98,48 == 0,255,0,255;
   expected: green
+  variants: *load-image-variant-definition
 
 - name: 2d.pattern.paint.orientation.canvas
   desc: Canvas patterns do not get flipped when painted
@@ -1888,9 +1965,7 @@
     ctx.fillStyle = '#f00';
     ctx.fillRect(0, 0, 100, 50);
 
-    var canvas2 = document.createElement('canvas');
-    canvas2.width = 100;
-    canvas2.height = 50;
+    {{ create_canvas2 }}
     var ctx2 = canvas2.getContext('2d');
     ctx2.fillStyle = '#f00';
     ctx2.fillRect(0, 0, 100, 25);
@@ -1908,10 +1983,11 @@
     @assert pixel 1,48 == 0,255,0,255;
     @assert pixel 98,48 == 0,255,0,255;
   expected: green
-
+  variants: *create-canvas2-variant-definition
 
 - name: 2d.pattern.animated.gif
   desc: createPattern() of an animated GIF draws the first frame
+  canvasType: ['HtmlCanvas']
   images:
   - anim-gr.gif
   code: |
@@ -1930,6 +2006,7 @@
 
 - name: 2d.fillStyle.CSSRGB
   desc: CSSRGB works as color input
+  canvasType: ['HtmlCanvas', 'OffscreenCanvas']
   code: |
     ctx.fillStyle = new CSSRGB(1, 0, 1);
     @assert ctx.fillStyle === '#ff00ff';
@@ -1969,6 +2046,7 @@
 
 - name: 2d.fillStyle.CSSHSL
   desc: CSSHSL works as color input
+  canvasType: ['HtmlCanvas', 'OffscreenCanvas']
   code: |
     ctx.fillStyle = new CSSHSL(CSS.deg(180), 0.5, 0.5);
     ctx.fillRect(0, 0, 100, 50);
diff --git a/third_party/blink/web_tests/external/wpt/html/canvas/tools/yaml/offscreen/fill-and-stroke-styles.yaml b/third_party/blink/web_tests/external/wpt/html/canvas/tools/yaml/offscreen/fill-and-stroke-styles.yaml
deleted file mode 100644
index 5f794d6..0000000
--- a/third_party/blink/web_tests/external/wpt/html/canvas/tools/yaml/offscreen/fill-and-stroke-styles.yaml
+++ /dev/null
@@ -1,1586 +0,0 @@
-- name: 2d.fillStyle.invalidstring
-  code: |
-    ctx.fillStyle = '#f00';
-    ctx.fillRect(0, 0, 100, 50);
-    ctx.fillStyle = '#0f0';
-    ctx.fillStyle = 'invalid';
-    ctx.fillRect(0, 0, 100, 50);
-    @assert pixel 50,25 == 0,255,0,255;
-    t.done();
-
-- name: 2d.fillStyle.invalidtype
-  code: |
-    ctx.fillStyle = '#f00';
-    ctx.fillRect(0, 0, 100, 50);
-    ctx.fillStyle = '#0f0';
-    ctx.fillStyle = null;
-    ctx.fillRect(0, 0, 100, 50);
-    @assert pixel 50,25 == 0,255,0,255;
-    t.done();
-
-- name: 2d.fillStyle.get.solid
-  code: |
-    ctx.fillStyle = '#fa0';
-    @assert ctx.fillStyle === '#ffaa00';
-    t.done();
-
-- name: 2d.fillStyle.get.semitransparent
-  code: |
-    ctx.fillStyle = 'rgba(255,255,255,0.45)';
-    @assert ctx.fillStyle =~ /^rgba\(255, 255, 255, 0\.4\d+\)$/;
-    t.done();
-
-- name: 2d.fillStyle.get.halftransparent
-  code: |
-    ctx.fillStyle = 'rgba(255,255,255,0.5)';
-    @assert ctx.fillStyle === 'rgba(255, 255, 255, 0.5)';
-    t.done();
-
-- name: 2d.fillStyle.get.transparent
-  code: |
-    ctx.fillStyle = 'rgba(0,0,0,0)';
-    @assert ctx.fillStyle === 'rgba(0, 0, 0, 0)';
-    t.done();
-
-- name: 2d.fillStyle.default
-  code: |
-    @assert ctx.fillStyle === '#000000';
-    t.done();
-
-- name: 2d.strokeStyle.default
-  code: |
-    @assert ctx.strokeStyle === '#000000';
-    t.done();
-
-- name: 2d.fillStyle.toStringFunctionCallback
-  desc: Passing a function in to ctx.fillStyle or ctx.strokeStyle with a toString callback works as specified
-  code: |
-    ctx.fillStyle = { toString: function() { return "#008000"; } };
-    @assert ctx.fillStyle === "#008000";
-    ctx.fillStyle = {};
-    @assert ctx.fillStyle === "#008000";
-    ctx.fillStyle = 800000;
-    @assert ctx.fillStyle === "#008000";
-    @assert throws TypeError ctx.fillStyle = { toString: function() { throw new TypeError; } };
-    ctx.strokeStyle = { toString: function() { return "#008000"; } };
-    @assert ctx.strokeStyle === "#008000";
-    ctx.strokeStyle = {};
-    @assert ctx.strokeStyle === "#008000";
-    ctx.strokeStyle = 800000;
-    @assert ctx.strokeStyle === "#008000";
-    @assert throws TypeError ctx.strokeStyle = { toString: function() { throw new TypeError; } };
-    t.done();
-
-- name: 2d.gradient.interpolate.solid
-  code: |
-    var g = ctx.createLinearGradient(0, 0, 100, 0);
-    g.addColorStop(0, '#0f0');
-    g.addColorStop(1, '#0f0');
-    ctx.fillStyle = g;
-    ctx.fillRect(0, 0, 100, 50);
-    @assert pixel 50,25 == 0,255,0,255;
-    t.done();
-
-- name: 2d.gradient.interpolate.color
-  code: |
-    var g = ctx.createLinearGradient(0, 0, 100, 0);
-    g.addColorStop(0, '#ff0');
-    g.addColorStop(1, '#00f');
-    ctx.fillStyle = g;
-    ctx.fillRect(0, 0, 100, 50);
-    @assert pixel 25,25 ==~ 191,191,63,255 +/- 3;
-    @assert pixel 50,25 ==~ 127,127,127,255 +/- 3;
-    @assert pixel 75,25 ==~ 63,63,191,255 +/- 3;
-    t.done();
-
-- name: 2d.gradient.interpolate.alpha
-  code: |
-    ctx.fillStyle = '#ff0';
-    ctx.fillRect(0, 0, 100, 50);
-    var g = ctx.createLinearGradient(0, 0, 100, 0);
-    g.addColorStop(0, 'rgba(0,0,255, 0)');
-    g.addColorStop(1, 'rgba(0,0,255, 1)');
-    ctx.fillStyle = g;
-    ctx.fillRect(0, 0, 100, 50);
-    @assert pixel 25,25 ==~ 191,191,63,255 +/- 3;
-    @assert pixel 50,25 ==~ 127,127,127,255 +/- 3;
-    @assert pixel 75,25 ==~ 63,63,191,255 +/- 3;
-    t.done();
-
-- name: 2d.gradient.interpolate.coloralpha
-  code: |
-    var g = ctx.createLinearGradient(0, 0, 100, 0);
-    g.addColorStop(0, 'rgba(255,255,0, 0)');
-    g.addColorStop(1, 'rgba(0,0,255, 1)');
-    ctx.fillStyle = g;
-    ctx.fillRect(0, 0, 100, 50);
-    @assert pixel 25,25 ==~ 190,190,65,65 +/- 3;
-    @assert pixel 50,25 ==~ 126,126,128,128 +/- 3;
-    @assert pixel 75,25 ==~ 62,62,192,192 +/- 3;
-    t.done();
-
-- name: 2d.gradient.interpolate.outside
-  code: |
-    ctx.fillStyle = '#f00';
-    ctx.fillRect(0, 0, 100, 50);
-    var g = ctx.createLinearGradient(25, 0, 75, 0);
-    g.addColorStop(0.4, '#0f0');
-    g.addColorStop(0.6, '#0f0');
-    ctx.fillStyle = g;
-    ctx.fillRect(0, 0, 100, 50);
-    @assert pixel 20,25 ==~ 0,255,0,255;
-    @assert pixel 50,25 ==~ 0,255,0,255;
-    @assert pixel 80,25 ==~ 0,255,0,255;
-    t.done();
-
-- name: 2d.gradient.interpolate.zerosize.fill
-  code: |
-    ctx.fillStyle = '#0f0';
-    ctx.fillRect(0, 0, 100, 50);
-    var g = ctx.createLinearGradient(50, 25, 50, 25); // zero-length line (undefined direction)
-    g.addColorStop(0, '#f00');
-    g.addColorStop(1, '#f00');
-    ctx.fillStyle = g;
-    ctx.rect(0, 0, 100, 50);
-    ctx.fill();
-    @assert pixel 40,20 == 0,255,0,255;
-    t.done();
-
-- name: 2d.gradient.interpolate.zerosize.stroke
-  code: |
-    ctx.fillStyle = '#0f0';
-    ctx.fillRect(0, 0, 100, 50);
-    var g = ctx.createLinearGradient(50, 25, 50, 25); // zero-length line (undefined direction)
-    g.addColorStop(0, '#f00');
-    g.addColorStop(1, '#f00');
-    ctx.strokeStyle = g;
-    ctx.rect(20, 20, 60, 10);
-    ctx.stroke();
-    @assert pixel 19,19 == 0,255,0,255;
-    @assert pixel 20,19 == 0,255,0,255;
-    @assert pixel 21,19 == 0,255,0,255;
-    @assert pixel 19,20 == 0,255,0,255;
-    @assert pixel 20,20 == 0,255,0,255;
-    @assert pixel 21,20 == 0,255,0,255;
-    @assert pixel 19,21 == 0,255,0,255;
-    @assert pixel 20,21 == 0,255,0,255;
-    @assert pixel 21,21 == 0,255,0,255;
-    t.done();
-
-- name: 2d.gradient.interpolate.zerosize.fillRect
-  code: |
-    ctx.fillStyle = '#0f0';
-    ctx.fillRect(0, 0, 100, 50);
-    var g = ctx.createLinearGradient(50, 25, 50, 25); // zero-length line (undefined direction)
-    g.addColorStop(0, '#f00');
-    g.addColorStop(1, '#f00');
-    ctx.fillStyle = g;
-    ctx.fillRect(0, 0, 100, 50);
-    @assert pixel 40,20 == 0,255,0,255; @moz-todo
-    t.done();
-
-- name: 2d.gradient.interpolate.zerosize.strokeRect
-  code: |
-    ctx.fillStyle = '#0f0';
-    ctx.fillRect(0, 0, 100, 50);
-    var g = ctx.createLinearGradient(50, 25, 50, 25); // zero-length line (undefined direction)
-    g.addColorStop(0, '#f00');
-    g.addColorStop(1, '#f00');
-    ctx.strokeStyle = g;
-    ctx.strokeRect(20, 20, 60, 10);
-    @assert pixel 19,19 == 0,255,0,255;
-    @assert pixel 20,19 == 0,255,0,255;
-    @assert pixel 21,19 == 0,255,0,255;
-    @assert pixel 19,20 == 0,255,0,255;
-    @assert pixel 20,20 == 0,255,0,255;
-    @assert pixel 21,20 == 0,255,0,255;
-    @assert pixel 19,21 == 0,255,0,255;
-    @assert pixel 20,21 == 0,255,0,255;
-    @assert pixel 21,21 == 0,255,0,255;
-    t.done();
-
-
-- name: 2d.gradient.interpolate.vertical
-  code: |
-    var g = ctx.createLinearGradient(0, 0, 0, 50);
-    g.addColorStop(0, '#ff0');
-    g.addColorStop(1, '#00f');
-    ctx.fillStyle = g;
-    ctx.fillRect(0, 0, 100, 50);
-    @assert pixel 50,12 ==~ 191,191,63,255 +/- 10;
-    @assert pixel 50,25 ==~ 127,127,127,255 +/- 5;
-    @assert pixel 50,37 ==~ 63,63,191,255 +/- 10;
-    t.done();
-
-- name: 2d.gradient.interpolate.multiple
-  code: |
-    canvas.width = 200;
-    var g = ctx.createLinearGradient(0, 0, 200, 0);
-    g.addColorStop(0, '#ff0');
-    g.addColorStop(0.5, '#0ff');
-    g.addColorStop(1, '#f0f');
-    ctx.fillStyle = g;
-    ctx.fillRect(0, 0, 200, 50);
-    @assert pixel 50,25 ==~ 127,255,127,255 +/- 3;
-    @assert pixel 100,25 ==~ 0,255,255,255 +/- 3;
-    @assert pixel 150,25 ==~ 127,127,255,255 +/- 3;
-    t.done();
-
-- name: 2d.gradient.interpolate.overlap
-  code: |
-    canvas.width = 200;
-    var g = ctx.createLinearGradient(0, 0, 200, 0);
-    g.addColorStop(0, '#f00');
-    g.addColorStop(0, '#ff0');
-    g.addColorStop(0.25, '#00f');
-    g.addColorStop(0.25, '#0f0');
-    g.addColorStop(0.25, '#0f0');
-    g.addColorStop(0.25, '#0f0');
-    g.addColorStop(0.25, '#ff0');
-    g.addColorStop(0.5, '#00f');
-    g.addColorStop(0.5, '#0f0');
-    g.addColorStop(0.75, '#00f');
-    g.addColorStop(0.75, '#f00');
-    g.addColorStop(0.75, '#ff0');
-    g.addColorStop(0.5, '#0f0');
-    g.addColorStop(0.5, '#0f0');
-    g.addColorStop(0.5, '#ff0');
-    g.addColorStop(1, '#00f');
-    ctx.fillStyle = g;
-    ctx.fillRect(0, 0, 200, 50);
-    @assert pixel 49,25 ==~ 0,0,255,255 +/- 16;
-    @assert pixel 51,25 ==~ 255,255,0,255 +/- 16;
-    @assert pixel 99,25 ==~ 0,0,255,255 +/- 16;
-    @assert pixel 101,25 ==~ 255,255,0,255 +/- 16;
-    @assert pixel 149,25 ==~ 0,0,255,255 +/- 16;
-    @assert pixel 151,25 ==~ 255,255,0,255 +/- 16;
-    t.done();
-
-- name: 2d.gradient.interpolate.overlap2
-  code: |
-    var g = ctx.createLinearGradient(0, 0, 100, 0);
-    var ps = [ 0, 1/10, 1/4, 1/3, 1/2, 3/4, 1 ];
-    for (var p = 0; p < ps.length; ++p)
-    {
-            g.addColorStop(ps[p], '#0f0');
-            for (var i = 0; i < 15; ++i)
-                    g.addColorStop(ps[p], '#f00');
-            g.addColorStop(ps[p], '#0f0');
-    }
-    ctx.fillStyle = g;
-    ctx.fillRect(0, 0, 100, 50);
-    @assert pixel 1,25 == 0,255,0,255;
-    @assert pixel 30,25 == 0,255,0,255;
-    @assert pixel 40,25 == 0,255,0,255;
-    @assert pixel 60,25 == 0,255,0,255;
-    @assert pixel 80,25 == 0,255,0,255;
-    t.done();
-
-- name: 2d.gradient.empty
-  code: |
-    ctx.fillStyle = '#0f0';
-    ctx.fillRect(0, 0, 100, 50);
-    var g = ctx.createLinearGradient(0, 0, 0, 50);
-    ctx.fillStyle = g;
-    ctx.fillRect(0, 0, 100, 50);
-    @assert pixel 50,25 ==~ 0,255,0,255;
-    t.done();
-
-- name: 2d.gradient.object.update
-  code: |
-    var g = ctx.createLinearGradient(-100, 0, 200, 0);
-    g.addColorStop(0, '#f00');
-    g.addColorStop(1, '#f00');
-    ctx.fillStyle = g;
-    ctx.fillRect(0, 0, 100, 50);
-    g.addColorStop(0.1, '#0f0');
-    g.addColorStop(0.9, '#0f0');
-    ctx.fillRect(0, 0, 100, 50);
-    @assert pixel 50,25 ==~ 0,255,0,255;
-    t.done();
-
-- name: 2d.gradient.object.compare
-  code: |
-    var g1 = ctx.createLinearGradient(0, 0, 100, 0);
-    var g2 = ctx.createLinearGradient(0, 0, 100, 0);
-    @assert g1 !== g2;
-    ctx.fillStyle = g1;
-    @assert ctx.fillStyle === g1;
-    t.done();
-
-- name: 2d.gradient.object.crosscanvas
-  code: |
-    ctx.fillStyle = '#f00';
-    ctx.fillRect(0, 0, 100, 50);
-    var offscreenCanvas2 = new OffscreenCanvas(100, 50);
-    var g = offscreenCanvas2.getContext('2d').createLinearGradient(0, 0, 100, 0);
-    g.addColorStop(0, '#0f0');
-    g.addColorStop(1, '#0f0');
-    ctx.fillStyle = g;
-    ctx.fillRect(0, 0, 100, 50);
-    @assert pixel 50,25 ==~ 0,255,0,255;
-    t.done();
-
-- name: 2d.gradient.object.invalidoffset
-  code: |
-    var g = ctx.createLinearGradient(0, 0, 100, 0);
-    @assert throws INDEX_SIZE_ERR g.addColorStop(-1, '#000');
-    @assert throws INDEX_SIZE_ERR g.addColorStop(2, '#000');
-    @assert throws TypeError g.addColorStop(Infinity, '#000');
-    @assert throws TypeError g.addColorStop(-Infinity, '#000');
-    @assert throws TypeError g.addColorStop(NaN, '#000');
-    t.done();
-
-- name: 2d.gradient.object.invalidcolor
-  code: |
-    var g = ctx.createLinearGradient(0, 0, 100, 0);
-    @assert throws SYNTAX_ERR g.addColorStop(0, "");
-    @assert throws SYNTAX_ERR g.addColorStop(0, 'null');
-    @assert throws SYNTAX_ERR g.addColorStop(0, 'undefined');
-    @assert throws SYNTAX_ERR g.addColorStop(0, null);
-    @assert throws SYNTAX_ERR g.addColorStop(0, undefined);
-    t.done();
-
-- name: 2d.gradient.linear.nonfinite
-  desc: createLinearGradient() throws TypeError if arguments are not finite
-  code: |
-    @nonfinite @assert throws TypeError ctx.createLinearGradient(<0 Infinity -Infinity NaN>, <0 Infinity -Infinity NaN>, <1 Infinity -Infinity NaN>, <0 Infinity -Infinity NaN>);
-    t.done();
-
-- name: 2d.gradient.linear.transform.1
-  desc: Linear gradient coordinates are relative to the coordinate space at the time
-    of filling
-  code: |
-    var g = ctx.createLinearGradient(0, 0, 200, 0);
-    g.addColorStop(0, '#f00');
-    g.addColorStop(0.25, '#0f0');
-    g.addColorStop(0.75, '#0f0');
-    g.addColorStop(1, '#f00');
-    ctx.fillStyle = g;
-    ctx.translate(-50, 0);
-    ctx.fillRect(50, 0, 100, 50);
-    @assert pixel 25,25 == 0,255,0,255;
-    @assert pixel 50,25 == 0,255,0,255;
-    @assert pixel 75,25 == 0,255,0,255;
-    t.done();
-
-- name: 2d.gradient.linear.transform.2
-  desc: Linear gradient coordinates are relative to the coordinate space at the time
-    of filling
-  code: |
-    ctx.translate(100, 0);
-    var g = ctx.createLinearGradient(0, 0, 200, 0);
-    g.addColorStop(0, '#f00');
-    g.addColorStop(0.25, '#0f0');
-    g.addColorStop(0.75, '#0f0');
-    g.addColorStop(1, '#f00');
-    ctx.fillStyle = g;
-    ctx.translate(-150, 0);
-    ctx.fillRect(50, 0, 100, 50);
-    @assert pixel 25,25 == 0,255,0,255;
-    @assert pixel 50,25 == 0,255,0,255;
-    @assert pixel 75,25 == 0,255,0,255;
-    t.done();
-
-- name: 2d.gradient.linear.transform.3
-  desc: Linear gradient transforms do not experience broken caching effects
-  code: |
-    var g = ctx.createLinearGradient(0, 0, 200, 0);
-    g.addColorStop(0, '#f00');
-    g.addColorStop(0.25, '#0f0');
-    g.addColorStop(0.75, '#0f0');
-    g.addColorStop(1, '#f00');
-    ctx.fillStyle = g;
-    ctx.fillRect(0, 0, 100, 50);
-    ctx.translate(-50, 0);
-    ctx.fillRect(50, 0, 100, 50);
-    @assert pixel 25,25 == 0,255,0,255;
-    @assert pixel 50,25 == 0,255,0,255;
-    @assert pixel 75,25 == 0,255,0,255;
-    t.done();
-
-- name: 2d.gradient.radial.negative
-  desc: createRadialGradient() throws INDEX_SIZE_ERR if either radius is negative
-  code: |
-    @assert throws INDEX_SIZE_ERR ctx.createRadialGradient(0, 0, -0.1, 0, 0, 1);
-    @assert throws INDEX_SIZE_ERR ctx.createRadialGradient(0, 0, 1, 0, 0, -0.1);
-    @assert throws INDEX_SIZE_ERR ctx.createRadialGradient(0, 0, -0.1, 0, 0, -0.1);
-    t.done();
-- name: 2d.gradient.radial.nonfinite
-
-  desc: createRadialGradient() throws TypeError if arguments are not finite
-  code: |
-    @nonfinite @assert throws TypeError ctx.createRadialGradient(<0 Infinity -Infinity NaN>, <0 Infinity -Infinity NaN>, <1 Infinity -Infinity NaN>, <0 Infinity -Infinity NaN>, <0 Infinity -Infinity NaN>, <1 Infinity -Infinity NaN>);
-    t.done();
-
-- name: 2d.gradient.radial.inside1
-  code: |
-    ctx.fillStyle = '#f00';
-    ctx.fillRect(0, 0, 100, 50);
-    var g = ctx.createRadialGradient(50, 25, 100, 50, 25, 200);
-    g.addColorStop(0, '#0f0');
-    g.addColorStop(1, '#f00');
-    ctx.fillStyle = g;
-    ctx.fillRect(0, 0, 100, 50);
-    @assert pixel 1,1 == 0,255,0,255;
-    @assert pixel 50,1 == 0,255,0,255;
-    @assert pixel 98,1 == 0,255,0,255;
-    @assert pixel 1,25 == 0,255,0,255;
-    @assert pixel 50,25 == 0,255,0,255;
-    @assert pixel 98,25 == 0,255,0,255;
-    @assert pixel 1,48 == 0,255,0,255;
-    @assert pixel 50,48 == 0,255,0,255;
-    @assert pixel 98,48 == 0,255,0,255;
-    t.done();
-
-- name: 2d.gradient.radial.inside2
-  code: |
-    ctx.fillStyle = '#f00';
-    ctx.fillRect(0, 0, 100, 50);
-    var g = ctx.createRadialGradient(50, 25, 200, 50, 25, 100);
-    g.addColorStop(0, '#f00');
-    g.addColorStop(1, '#0f0');
-    ctx.fillStyle = g;
-    ctx.fillRect(0, 0, 100, 50);
-    @assert pixel 1,1 == 0,255,0,255;
-    @assert pixel 50,1 == 0,255,0,255;
-    @assert pixel 98,1 == 0,255,0,255;
-    @assert pixel 1,25 == 0,255,0,255;
-    @assert pixel 50,25 == 0,255,0,255;
-    @assert pixel 98,25 == 0,255,0,255;
-    @assert pixel 1,48 == 0,255,0,255;
-    @assert pixel 50,48 == 0,255,0,255;
-    @assert pixel 98,48 == 0,255,0,255;
-    t.done();
-
-- name: 2d.gradient.radial.inside3
-  code: |
-    ctx.fillStyle = '#f00';
-    ctx.fillRect(0, 0, 100, 50);
-    var g = ctx.createRadialGradient(50, 25, 200, 50, 25, 100);
-    g.addColorStop(0, '#f00');
-    g.addColorStop(0.993, '#f00');
-    g.addColorStop(1, '#0f0');
-    ctx.fillStyle = g;
-    ctx.fillRect(0, 0, 100, 50);
-    @assert pixel 1,1 == 0,255,0,255;
-    @assert pixel 50,1 == 0,255,0,255;
-    @assert pixel 98,1 == 0,255,0,255;
-    @assert pixel 1,25 == 0,255,0,255;
-    @assert pixel 50,25 == 0,255,0,255;
-    @assert pixel 98,25 == 0,255,0,255;
-    @assert pixel 1,48 == 0,255,0,255;
-    @assert pixel 50,48 == 0,255,0,255;
-    @assert pixel 98,48 == 0,255,0,255;
-    t.done();
-
-- name: 2d.gradient.radial.outside1
-  code: |
-    ctx.fillStyle = '#f00';
-    ctx.fillRect(0, 0, 100, 50);
-    var g = ctx.createRadialGradient(200, 25, 10, 200, 25, 20);
-    g.addColorStop(0, '#f00');
-    g.addColorStop(1, '#0f0');
-    ctx.fillStyle = g;
-    ctx.fillRect(0, 0, 100, 50);
-    @assert pixel 1,1 == 0,255,0,255;
-    @assert pixel 50,1 == 0,255,0,255;
-    @assert pixel 98,1 == 0,255,0,255;
-    @assert pixel 1,25 == 0,255,0,255;
-    @assert pixel 50,25 == 0,255,0,255;
-    @assert pixel 98,25 == 0,255,0,255;
-    @assert pixel 1,48 == 0,255,0,255;
-    @assert pixel 50,48 == 0,255,0,255;
-    @assert pixel 98,48 == 0,255,0,255;
-    t.done();
-
-- name: 2d.gradient.radial.outside2
-  code: |
-    ctx.fillStyle = '#f00';
-    ctx.fillRect(0, 0, 100, 50);
-    var g = ctx.createRadialGradient(200, 25, 20, 200, 25, 10);
-    g.addColorStop(0, '#0f0');
-    g.addColorStop(1, '#f00');
-    ctx.fillStyle = g;
-    ctx.fillRect(0, 0, 100, 50);
-    @assert pixel 1,1 == 0,255,0,255;
-    @assert pixel 50,1 == 0,255,0,255;
-    @assert pixel 98,1 == 0,255,0,255;
-    @assert pixel 1,25 == 0,255,0,255;
-    @assert pixel 50,25 == 0,255,0,255;
-    @assert pixel 98,25 == 0,255,0,255;
-    @assert pixel 1,48 == 0,255,0,255;
-    @assert pixel 50,48 == 0,255,0,255;
-    @assert pixel 98,48 == 0,255,0,255;
-    t.done();
-
-- name: 2d.gradient.radial.outside3
-  code: |
-    ctx.fillStyle = '#f00';
-    ctx.fillRect(0, 0, 100, 50);
-    var g = ctx.createRadialGradient(200, 25, 20, 200, 25, 10);
-    g.addColorStop(0, '#0f0');
-    g.addColorStop(0.001, '#f00');
-    g.addColorStop(1, '#f00');
-    ctx.fillStyle = g;
-    ctx.fillRect(0, 0, 100, 50);
-    @assert pixel 1,1 == 0,255,0,255;
-    @assert pixel 50,1 == 0,255,0,255;
-    @assert pixel 98,1 == 0,255,0,255;
-    @assert pixel 1,25 == 0,255,0,255;
-    @assert pixel 50,25 == 0,255,0,255;
-    @assert pixel 98,25 == 0,255,0,255;
-    @assert pixel 1,48 == 0,255,0,255;
-    @assert pixel 50,48 == 0,255,0,255;
-    @assert pixel 98,48 == 0,255,0,255;
-    t.done();
-
-- name: 2d.gradient.radial.touch1
-  code: |
-    ctx.fillStyle = '#0f0';
-    ctx.fillRect(0, 0, 100, 50);
-    var g = ctx.createRadialGradient(150, 25, 50, 200, 25, 100);
-    g.addColorStop(0, '#f00');
-    g.addColorStop(1, '#f00');
-    ctx.fillStyle = g;
-    ctx.fillRect(0, 0, 100, 50);
-    @assert pixel 1,1 == 0,255,0,255; @moz-todo
-    @assert pixel 50,1 == 0,255,0,255; @moz-todo
-    @assert pixel 98,1 == 0,255,0,255; @moz-todo
-    @assert pixel 1,25 == 0,255,0,255; @moz-todo
-    @assert pixel 50,25 == 0,255,0,255; @moz-todo
-    @assert pixel 98,25 == 0,255,0,255; @moz-todo
-    @assert pixel 1,48 == 0,255,0,255; @moz-todo
-    @assert pixel 50,48 == 0,255,0,255; @moz-todo
-    @assert pixel 98,48 == 0,255,0,255; @moz-todo
-    t.done();
-
-- name: 2d.gradient.radial.touch2
-  code: |
-    ctx.fillStyle = '#f00';
-    ctx.fillRect(0, 0, 100, 50);
-    var g = ctx.createRadialGradient(-80, 25, 70, 0, 25, 150);
-    g.addColorStop(0, '#f00');
-    g.addColorStop(0.01, '#0f0');
-    g.addColorStop(0.99, '#0f0');
-    g.addColorStop(1, '#f00');
-    ctx.fillStyle = g;
-    ctx.fillRect(0, 0, 100, 50);
-    @assert pixel 1,1 == 0,255,0,255;
-    @assert pixel 50,1 == 0,255,0,255;
-    @assert pixel 98,1 == 0,255,0,255;
-    @assert pixel 1,25 == 0,255,0,255;
-    @assert pixel 50,25 == 0,255,0,255;
-    @assert pixel 98,25 == 0,255,0,255;
-    @assert pixel 1,48 == 0,255,0,255;
-    @assert pixel 50,48 == 0,255,0,255;
-    @assert pixel 98,48 == 0,255,0,255;
-    t.done();
-
-- name: 2d.gradient.radial.touch3
-  code: |
-    ctx.fillStyle = '#0f0';
-    ctx.fillRect(0, 0, 100, 50);
-    var g = ctx.createRadialGradient(120, -15, 25, 140, -30, 50);
-    g.addColorStop(0, '#f00');
-    g.addColorStop(1, '#f00');
-    ctx.fillStyle = g;
-    ctx.fillRect(0, 0, 100, 50);
-    @assert pixel 1,1 == 0,255,0,255; @moz-todo
-    @assert pixel 50,1 == 0,255,0,255; @moz-todo
-    @assert pixel 98,1 == 0,255,0,255; @moz-todo
-    @assert pixel 1,25 == 0,255,0,255; @moz-todo
-    @assert pixel 50,25 == 0,255,0,255; @moz-todo
-    @assert pixel 98,25 == 0,255,0,255; @moz-todo
-    @assert pixel 1,48 == 0,255,0,255; @moz-todo
-    @assert pixel 50,48 == 0,255,0,255; @moz-todo
-    @assert pixel 98,48 == 0,255,0,255; @moz-todo
-    t.done();
-
-- name: 2d.gradient.radial.equal
-  code: |
-    ctx.fillStyle = '#0f0';
-    ctx.fillRect(0, 0, 100, 50);
-    var g = ctx.createRadialGradient(50, 25, 20, 50, 25, 20);
-    g.addColorStop(0, '#f00');
-    g.addColorStop(1, '#f00');
-    ctx.fillStyle = g;
-    ctx.fillRect(0, 0, 100, 50);
-    @assert pixel 1,1 == 0,255,0,255; @moz-todo
-    @assert pixel 50,1 == 0,255,0,255; @moz-todo
-    @assert pixel 98,1 == 0,255,0,255; @moz-todo
-    @assert pixel 1,25 == 0,255,0,255; @moz-todo
-    @assert pixel 50,25 == 0,255,0,255; @moz-todo
-    @assert pixel 98,25 == 0,255,0,255; @moz-todo
-    @assert pixel 1,48 == 0,255,0,255; @moz-todo
-    @assert pixel 50,48 == 0,255,0,255; @moz-todo
-    @assert pixel 98,48 == 0,255,0,255; @moz-todo
-    t.done();
-
-- name: 2d.gradient.radial.cone.behind
-  code: |
-    ctx.fillStyle = '#0f0';
-    ctx.fillRect(0, 0, 100, 50);
-    var g = ctx.createRadialGradient(120, 25, 10, 211, 25, 100);
-    g.addColorStop(0, '#f00');
-    g.addColorStop(1, '#f00');
-    ctx.fillStyle = g;
-    ctx.fillRect(0, 0, 100, 50);
-    @assert pixel 1,1 == 0,255,0,255; @moz-todo
-    @assert pixel 50,1 == 0,255,0,255; @moz-todo
-    @assert pixel 98,1 == 0,255,0,255; @moz-todo
-    @assert pixel 1,25 == 0,255,0,255; @moz-todo
-    @assert pixel 50,25 == 0,255,0,255; @moz-todo
-    @assert pixel 98,25 == 0,255,0,255; @moz-todo
-    @assert pixel 1,48 == 0,255,0,255; @moz-todo
-    @assert pixel 50,48 == 0,255,0,255; @moz-todo
-    @assert pixel 98,48 == 0,255,0,255; @moz-todo
-    t.done();
-
-- name: 2d.gradient.radial.cone.front
-  code: |
-    ctx.fillStyle = '#f00';
-    ctx.fillRect(0, 0, 100, 50);
-    var g = ctx.createRadialGradient(311, 25, 10, 210, 25, 100);
-    g.addColorStop(0, '#f00');
-    g.addColorStop(1, '#0f0');
-    ctx.fillStyle = g;
-    ctx.fillRect(0, 0, 100, 50);
-    @assert pixel 1,1 == 0,255,0,255;
-    @assert pixel 50,1 == 0,255,0,255;
-    @assert pixel 98,1 == 0,255,0,255;
-    @assert pixel 1,25 == 0,255,0,255;
-    @assert pixel 50,25 == 0,255,0,255;
-    @assert pixel 98,25 == 0,255,0,255;
-    @assert pixel 1,48 == 0,255,0,255;
-    @assert pixel 50,48 == 0,255,0,255;
-    @assert pixel 98,48 == 0,255,0,255;
-    t.done();
-
-- name: 2d.gradient.radial.cone.bottom
-  code: |
-    ctx.fillStyle = '#f00';
-    ctx.fillRect(0, 0, 100, 50);
-    var g = ctx.createRadialGradient(210, 25, 100, 230, 25, 101);
-    g.addColorStop(0, '#0f0');
-    g.addColorStop(1, '#f00');
-    ctx.fillStyle = g;
-    ctx.fillRect(0, 0, 100, 50);
-    @assert pixel 1,1 == 0,255,0,255;
-    @assert pixel 50,1 == 0,255,0,255;
-    @assert pixel 98,1 == 0,255,0,255;
-    @assert pixel 1,25 == 0,255,0,255;
-    @assert pixel 50,25 == 0,255,0,255;
-    @assert pixel 98,25 == 0,255,0,255;
-    @assert pixel 1,48 == 0,255,0,255;
-    @assert pixel 50,48 == 0,255,0,255;
-    @assert pixel 98,48 == 0,255,0,255;
-    t.done();
-
-- name: 2d.gradient.radial.cone.top
-  code: |
-    ctx.fillStyle = '#f00';
-    ctx.fillRect(0, 0, 100, 50);
-    var g = ctx.createRadialGradient(230, 25, 100, 100, 25, 101);
-    g.addColorStop(0, '#f00');
-    g.addColorStop(1, '#0f0');
-    ctx.fillStyle = g;
-    ctx.fillRect(0, 0, 100, 50);
-    @assert pixel 1,1 == 0,255,0,255;
-    @assert pixel 50,1 == 0,255,0,255;
-    @assert pixel 98,1 == 0,255,0,255;
-    @assert pixel 1,25 == 0,255,0,255;
-    @assert pixel 50,25 == 0,255,0,255;
-    @assert pixel 98,25 == 0,255,0,255;
-    @assert pixel 1,48 == 0,255,0,255;
-    @assert pixel 50,48 == 0,255,0,255;
-    @assert pixel 98,48 == 0,255,0,255;
-    t.done();
-
-- name: 2d.gradient.radial.cone.beside
-  code: |
-    ctx.fillStyle = '#0f0';
-    ctx.fillRect(0, 0, 100, 50);
-    var g = ctx.createRadialGradient(0, 100, 40, 100, 100, 50);
-    g.addColorStop(0, '#f00');
-    g.addColorStop(1, '#f00');
-    ctx.fillStyle = g;
-    ctx.fillRect(0, 0, 100, 50);
-    @assert pixel 1,1 == 0,255,0,255; @moz-todo
-    @assert pixel 50,1 == 0,255,0,255; @moz-todo
-    @assert pixel 98,1 == 0,255,0,255; @moz-todo
-    @assert pixel 1,25 == 0,255,0,255; @moz-todo
-    @assert pixel 50,25 == 0,255,0,255; @moz-todo
-    @assert pixel 98,25 == 0,255,0,255; @moz-todo
-    @assert pixel 1,48 == 0,255,0,255; @moz-todo
-    @assert pixel 50,48 == 0,255,0,255; @moz-todo
-    @assert pixel 98,48 == 0,255,0,255; @moz-todo
-    t.done();
-
-- name: 2d.gradient.radial.cone.cylinder
-  code: |
-    ctx.fillStyle = '#f00';
-    ctx.fillRect(0, 0, 100, 50);
-    var g = ctx.createRadialGradient(210, 25, 100, 230, 25, 100);
-    g.addColorStop(0, '#0f0');
-    g.addColorStop(1, '#f00');
-    ctx.fillStyle = g;
-    ctx.fillRect(0, 0, 100, 50);
-    @assert pixel 1,1 == 0,255,0,255;
-    @assert pixel 50,1 == 0,255,0,255;
-    @assert pixel 98,1 == 0,255,0,255;
-    @assert pixel 1,25 == 0,255,0,255;
-    @assert pixel 50,25 == 0,255,0,255;
-    @assert pixel 98,25 == 0,255,0,255;
-    @assert pixel 1,48 == 0,255,0,255;
-    @assert pixel 50,48 == 0,255,0,255;
-    @assert pixel 98,48 == 0,255,0,255;
-    t.done();
-
-- name: 2d.gradient.radial.cone.shape1
-  code: |
-    var tol = 1; // tolerance to avoid antialiasing artifacts
-    ctx.fillStyle = '#0f0';
-    ctx.fillRect(0, 0, 100, 50);
-    ctx.fillStyle = '#f00';
-    ctx.beginPath();
-    ctx.moveTo(30+tol, 40);
-    ctx.lineTo(110, -20+tol);
-    ctx.lineTo(110, 100-tol);
-    ctx.fill();
-    var g = ctx.createRadialGradient(30+10*5/2, 40, 10*3/2, 30+10*15/4, 40, 10*9/4);
-    g.addColorStop(0, '#0f0');
-    g.addColorStop(1, '#0f0');
-    ctx.fillStyle = g;
-    ctx.fillRect(0, 0, 100, 50);
-    @assert pixel 1,1 == 0,255,0,255;
-    @assert pixel 50,1 == 0,255,0,255;
-    @assert pixel 98,1 == 0,255,0,255;
-    @assert pixel 1,25 == 0,255,0,255;
-    @assert pixel 50,25 == 0,255,0,255;
-    @assert pixel 98,25 == 0,255,0,255;
-    @assert pixel 1,48 == 0,255,0,255;
-    @assert pixel 50,48 == 0,255,0,255;
-    @assert pixel 98,48 == 0,255,0,255;
-    t.done();
-
-- name: 2d.gradient.radial.cone.shape2
-  code: |
-    var tol = 1; // tolerance to avoid antialiasing artifacts
-    ctx.fillStyle = '#0f0';
-    ctx.fillRect(0, 0, 100, 50);
-    var g = ctx.createRadialGradient(30+10*5/2, 40, 10*3/2, 30+10*15/4, 40, 10*9/4);
-    g.addColorStop(0, '#f00');
-    g.addColorStop(1, '#f00');
-    ctx.fillStyle = g;
-    ctx.fillRect(0, 0, 100, 50);
-    ctx.fillStyle = '#0f0';
-    ctx.beginPath();
-    ctx.moveTo(30-tol, 40);
-    ctx.lineTo(110, -20-tol);
-    ctx.lineTo(110, 100+tol);
-    ctx.fill();
-    @assert pixel 1,1 == 0,255,0,255; @moz-todo
-    @assert pixel 50,1 == 0,255,0,255; @moz-todo
-    @assert pixel 98,1 == 0,255,0,255;
-    @assert pixel 1,25 == 0,255,0,255; @moz-todo
-    @assert pixel 50,25 == 0,255,0,255;
-    @assert pixel 98,25 == 0,255,0,255;
-    @assert pixel 1,48 == 0,255,0,255; @moz-todo
-    @assert pixel 50,48 == 0,255,0,255;
-    @assert pixel 98,48 == 0,255,0,255;
-    t.done();
-
-- name: 2d.gradient.radial.transform.1
-  desc: Radial gradient coordinates are relative to the coordinate space at the time
-    of filling
-  code: |
-    var g = ctx.createRadialGradient(0, 0, 0, 0, 0, 11.2);
-    g.addColorStop(0, '#0f0');
-    g.addColorStop(0.5, '#0f0');
-    g.addColorStop(0.51, '#f00');
-    g.addColorStop(1, '#f00');
-    ctx.fillStyle = g;
-    ctx.translate(50, 25);
-    ctx.scale(10, 10);
-    ctx.fillRect(-5, -2.5, 10, 5);
-    @assert pixel 25,25 == 0,255,0,255;
-    @assert pixel 50,25 == 0,255,0,255;
-    @assert pixel 75,25 == 0,255,0,255;
-    t.done();
-
-- name: 2d.gradient.radial.transform.2
-  desc: Radial gradient coordinates are relative to the coordinate space at the time
-    of filling
-  code: |
-    ctx.translate(100, 0);
-    var g = ctx.createRadialGradient(0, 0, 0, 0, 0, 11.2);
-    g.addColorStop(0, '#0f0');
-    g.addColorStop(0.5, '#0f0');
-    g.addColorStop(0.51, '#f00');
-    g.addColorStop(1, '#f00');
-    ctx.fillStyle = g;
-    ctx.translate(-50, 25);
-    ctx.scale(10, 10);
-    ctx.fillRect(-5, -2.5, 10, 5);
-    @assert pixel 25,25 == 0,255,0,255;
-    @assert pixel 50,25 == 0,255,0,255;
-    @assert pixel 75,25 == 0,255,0,255;
-    t.done();
-
-- name: 2d.gradient.radial.transform.3
-  desc: Radial gradient transforms do not experience broken caching effects
-  code: |
-    var g = ctx.createRadialGradient(0, 0, 0, 0, 0, 11.2);
-    g.addColorStop(0, '#0f0');
-    g.addColorStop(0.5, '#0f0');
-    g.addColorStop(0.51, '#f00');
-    g.addColorStop(1, '#f00');
-    ctx.fillStyle = g;
-    ctx.fillRect(0, 0, 100, 50);
-    ctx.translate(50, 25);
-    ctx.scale(10, 10);
-    ctx.fillRect(-5, -2.5, 10, 5);
-    @assert pixel 25,25 == 0,255,0,255;
-    @assert pixel 50,25 == 0,255,0,255;
-    @assert pixel 75,25 == 0,255,0,255;
-    t.done();
-
-- name: 2d.gradient.conic.positive.rotation
-  desc: Conic gradient with positive rotation
-  code: |
-    const g = ctx.createConicGradient(3*Math.PI/2, 50, 25);
-    // It's red in the upper right region and green on the lower left region
-    g.addColorStop(0, "#f00");
-    g.addColorStop(0.25, "#0f0");
-    g.addColorStop(0.50, "#0f0");
-    g.addColorStop(0.75, "#f00");
-    ctx.fillStyle = g;
-    ctx.fillRect(0, 0, 100, 50);
-    @assert pixel 25,15 ==~ 255,0,0,255 +/- 3;
-    @assert pixel 75,40 ==~ 0,255,0,255 +/- 3;
-    t.done();
-
-- name: 2d.gradient.conic.negative.rotation
-  desc: Conic gradient with negative rotation
-  code: |
-    const g = ctx.createConicGradient(-Math.PI/2, 50, 25);
-    // It's red in the upper right region and green on the lower left region
-    g.addColorStop(0, "#f00");
-    g.addColorStop(0.25, "#0f0");
-    g.addColorStop(0.50, "#0f0");
-    g.addColorStop(0.75, "#f00");
-    ctx.fillStyle = g;
-    ctx.fillRect(0, 0, 100, 50);
-    @assert pixel 25,15 ==~ 255,0,0,255 +/- 3;
-    @assert pixel 75,40 ==~ 0,255,0,255 +/- 3;
-    t.done();
-
-- name: 2d.gradient.conic.invalid.inputs
-  desc: Conic gradient function with invalid inputs
-  code: |
-    @nonfinite @assert throws TypeError ctx.createConicGradient(<0 Infinity -Infinity NaN>, <0 Infinity -Infinity NaN>, <1 Infinity -Infinity NaN>);
-
-    const g = ctx.createConicGradient(0, 0, 25);
-    @nonfinite @assert throws TypeError g.addColorStop(<Infinity -Infinity NaN>, <'#f00'>);
-    @nonfinite @assert throws SYNTAX_ERR g.addColorStop(<0>, <Infinity -Infinity NaN>);
-    t.done();
-
-- name: 2d.pattern.basic.image
-  code: |
-    ctx.fillStyle = '#f00';
-    ctx.fillRect(0, 0, 100, 50);
-    var promise = new Promise(function(resolve, reject) {
-        var xhr = new XMLHttpRequest();
-        xhr.open("GET", '/images/green.png');
-        xhr.responseType = 'blob';
-        xhr.send();
-        xhr.onload = function() {
-            resolve(xhr.response);
-        };
-    });
-    promise.then(function(response) {
-        return createImageBitmap(response).then(bitmap => {
-            var pattern = ctx.createPattern(bitmap, 'no-repeat');
-            ctx.fillStyle = pattern;
-            ctx.fillRect(0, 0, 100, 50);
-            @assert pixel 1,1 == 0,255,0,255;
-            @assert pixel 98,1 == 0,255,0,255;
-            @assert pixel 1,48 == 0,255,0,255;
-            @assert pixel 98,48 == 0,255,0,255;
-        });
-    }).then(t_pass, t_fail);
-
-- name: 2d.pattern.basic.canvas
-  code: |
-    ctx.fillStyle = '#f00';
-    ctx.fillRect(0, 0, 100, 50);
-    var offscreenCanvas2 = new OffscreenCanvas(100, 50);
-    var ctx2 = offscreenCanvas2.getContext('2d');
-    ctx2.fillStyle = '#0f0';
-    ctx2.fillRect(0, 0, 100, 50);
-    var pattern = ctx.createPattern(offscreenCanvas2, 'no-repeat');
-    ctx.fillStyle = pattern;
-    ctx.fillRect(0, 0, 100, 50);
-    @assert pixel 1,1 == 0,255,0,255;
-    @assert pixel 50,1 == 0,255,0,255;
-    @assert pixel 98,1 == 0,255,0,255;
-    @assert pixel 1,25 == 0,255,0,255;
-    @assert pixel 50,25 == 0,255,0,255;
-    @assert pixel 98,25 == 0,255,0,255;
-    @assert pixel 1,48 == 0,255,0,255;
-    @assert pixel 50,48 == 0,255,0,255;
-    @assert pixel 98,48 == 0,255,0,255;
-    t.done();
-
-- name: 2d.pattern.basic.zerocanvas
-  code: |
-    canvas.width = 0;
-    canvas.height = 10;
-    @assert canvas.width === 0;
-    @assert canvas.height === 10;
-    @assert throws INVALID_STATE_ERR ctx.createPattern(canvas, 'repeat');
-    canvas.width = 10;
-    canvas.height = 0;
-    @assert canvas.width === 10;
-    @assert canvas.height === 0;
-    @assert throws INVALID_STATE_ERR ctx.createPattern(canvas, 'repeat');
-    canvas.width = 0;
-    canvas.height = 0;
-    @assert canvas.width === 0;
-    @assert canvas.height === 0;
-    @assert throws INVALID_STATE_ERR ctx.createPattern(canvas, 'repeat');
-    t.done();
-
-- name: 2d.pattern.basic.nocontext
-  code: |
-    var offscreenCanvas2 = new OffscreenCanvas(100, 50);
-    var pattern = ctx.createPattern(offscreenCanvas2, 'no-repeat');
-    ctx.fillStyle = '#0f0';
-    ctx.fillRect(0, 0, 100, 50);
-    ctx.fillStyle = '#f00';
-    ctx.fillStyle = pattern;
-    ctx.fillRect(0, 0, 100, 50);
-    @assert pixel 1,1 == 0,255,0,255;
-    @assert pixel 98,1 == 0,255,0,255;
-    @assert pixel 1,48 == 0,255,0,255;
-    @assert pixel 98,48 == 0,255,0,255;
-    t.done();
-
-- name: 2d.pattern.image.undefined
-  code: |
-    @assert throws TypeError ctx.createPattern(undefined, 'repeat');
-    t.done();
-
-- name: 2d.pattern.image.null
-  code: |
-    @assert throws TypeError ctx.createPattern(null, 'repeat');
-    t.done();
-
-- name: 2d.pattern.image.string
-  code: |
-    @assert throws TypeError ctx.createPattern('../images/red.png', 'repeat');
-    t.done();
-
-- name: 2d.pattern.repeat.empty
-  code: |
-    ctx.fillStyle = '#f00';
-    ctx.fillRect(0, 0, 100, 50);
-    var promise = new Promise(function(resolve, reject) {
-        var xhr = new XMLHttpRequest();
-        xhr.open("GET", '/images/green-1x1.png');
-        xhr.responseType = 'blob';
-        xhr.send();
-        xhr.onload = function() {
-            resolve(xhr.response);
-        };
-    });
-    promise.then(function(response) {
-        return createImageBitmap(response).then(bitmap => {
-            var pattern = ctx.createPattern(bitmap, "");
-            ctx.fillStyle = pattern;
-            ctx.fillRect(0, 0, 200, 50);
-            @assert pixel 1,1 == 0,255,0,255;
-            @assert pixel 98,1 == 0,255,0,255;
-            @assert pixel 1,48 == 0,255,0,255;
-            @assert pixel 98,48 == 0,255,0,255;
-        });
-    }).then(t_pass, t_fail);
-
-- name: 2d.pattern.repeat.null
-  code: |
-    @assert ctx.createPattern(canvas, null) != null;
-    t.done();
-
-- name: 2d.pattern.repeat.undefined
-  code: |
-    @assert throws SYNTAX_ERR ctx.createPattern(canvas, undefined);
-    t.done();
-
-- name: 2d.pattern.repeat.unrecognised
-  code: |
-    @assert throws SYNTAX_ERR ctx.createPattern(canvas, "invalid");
-    t.done();
-
-- name: 2d.pattern.repeat.unrecognisednull
-  code: |
-    @assert throws SYNTAX_ERR ctx.createPattern(canvas, "null");
-    t.done();
-
-- name: 2d.pattern.repeat.case
-  code: |
-    @assert throws SYNTAX_ERR ctx.createPattern(canvas, "Repeat");
-    t.done();
-
-- name: 2d.pattern.repeat.nullsuffix
-  code: |
-    @assert throws SYNTAX_ERR ctx.createPattern(canvas, "repeat\0");
-    t.done();
-
-- name: 2d.pattern.modify.canvas1
-  code: |
-    var offscreenCanvas2 = new OffscreenCanvas(100, 50);
-    var ctx2 = offscreenCanvas2.getContext('2d');
-    ctx2.fillStyle = '#0f0';
-    ctx2.fillRect(0, 0, 100, 50);
-    var pattern = ctx.createPattern(offscreenCanvas2, 'no-repeat');
-    ctx2.fillStyle = '#f00';
-    ctx2.fillRect(0, 0, 100, 50);
-    ctx.fillStyle = pattern;
-    ctx.fillRect(0, 0, 100, 50);
-    @assert pixel 1,1 == 0,255,0,255;
-    @assert pixel 98,1 == 0,255,0,255;
-    @assert pixel 1,48 == 0,255,0,255;
-    @assert pixel 98,48 == 0,255,0,255;
-    t.done();
-
-- name: 2d.pattern.modify.canvas2
-  code: |
-    var offscreenCanvas2 = new OffscreenCanvas(100, 50);
-    var ctx2 = offscreenCanvas2.getContext('2d');
-    ctx2.fillStyle = '#0f0';
-    ctx2.fillRect(0, 0, 100, 50);
-    var pattern = ctx.createPattern(offscreenCanvas2, 'no-repeat');
-    ctx.fillStyle = pattern;
-    ctx.fillRect(0, 0, 100, 50);
-    ctx.fillStyle = '#f00';
-    ctx.fillRect(0, 0, 100, 50);
-    ctx2.fillStyle = '#f00';
-    ctx2.fillRect(0, 0, 100, 50);
-    ctx.fillStyle = pattern;
-    ctx.fillRect(0, 0, 100, 50);
-    @assert pixel 1,1 == 0,255,0,255;
-    @assert pixel 98,1 == 0,255,0,255;
-    @assert pixel 1,48 == 0,255,0,255;
-    @assert pixel 98,48 == 0,255,0,255;
-    t.done();
-
-- name: 2d.pattern.crosscanvas
-  code: |
-    var promise = new Promise(function(resolve, reject) {
-        var xhr = new XMLHttpRequest();
-        xhr.open("GET", '/images/green.png');
-        xhr.responseType = 'blob';
-        xhr.send();
-        xhr.onload = function() {
-            resolve(xhr.response);
-        };
-    });
-    promise.then(function(response) {
-        return createImageBitmap(response).then(bitmap => {
-            var offscreenCanvas2 = new OffscreenCanvas(100, 50);
-            var pattern = offscreenCanvas2.getContext('2d').createPattern(bitmap, 'no-repeat');
-            ctx.fillStyle = '#f00';
-            ctx.fillRect(0, 0, 100, 50);
-            ctx.fillStyle = pattern;
-            ctx.fillRect(0, 0, 100, 50);
-            @assert pixel 50,25 == 0,255,0,255;
-        });
-    }).then(t_pass, t_fail);
-
-- name: 2d.pattern.paint.norepeat.basic
-  code: |
-    ctx.fillStyle = '#f00';
-    ctx.fillRect(0, 0, 100, 50);
-    var promise = new Promise(function(resolve, reject) {
-        var xhr = new XMLHttpRequest();
-        xhr.open("GET", '/images/green.png');
-        xhr.responseType = 'blob';
-        xhr.send();
-        xhr.onload = function() {
-            resolve(xhr.response);
-        };
-    });
-    promise.then(function(response) {
-        return createImageBitmap(response).then(bitmap => {
-            var pattern = ctx.createPattern(bitmap, 'no-repeat');
-            ctx.fillStyle = pattern;
-            ctx.fillRect(0, 0, 100, 50);
-            @assert pixel 1,1 == 0,255,0,255;
-            @assert pixel 98,1 == 0,255,0,255;
-            @assert pixel 1,48 == 0,255,0,255;
-            @assert pixel 98,48 == 0,255,0,255;
-        });
-    }).then(t_pass, t_fail);
-
-- name: 2d.pattern.paint.norepeat.outside
-  code: |
-    ctx.fillStyle = '#f00';
-    ctx.fillRect(0, 0, 100, 50);
-    var promise = new Promise(function(resolve, reject) {
-        var xhr = new XMLHttpRequest();
-        xhr.open("GET", '/images/red.png');
-        xhr.responseType = 'blob';
-        xhr.send();
-        xhr.onload = function() {
-            resolve(xhr.response);
-        };
-    });
-    promise.then(function(response) {
-        return createImageBitmap(response).then(bitmap => {
-            var pattern = ctx.createPattern(bitmap, 'no-repeat');
-            ctx.fillStyle = '#0f0';
-            ctx.fillRect(0, 0, 100, 50);
-            ctx.fillStyle = pattern;
-            ctx.fillRect(0, -50, 100, 50);
-            ctx.fillRect(-100, 0, 100, 50);
-            ctx.fillRect(0, 50, 100, 50);
-            ctx.fillRect(100, 0, 100, 50);
-            @assert pixel 1,1 == 0,255,0,255;
-            @assert pixel 98,1 == 0,255,0,255;
-            @assert pixel 1,48 == 0,255,0,255;
-            @assert pixel 98,48 == 0,255,0,255;
-        });
-    }).then(t_pass, t_fail);
-
-- name: 2d.pattern.paint.norepeat.coord1
-  code: |
-    ctx.fillStyle = '#0f0';
-    ctx.fillRect(0, 0, 50, 50);
-    ctx.fillStyle = '#f00';
-    ctx.fillRect(50, 0, 50, 50);
-    var promise = new Promise(function(resolve, reject) {
-        var xhr = new XMLHttpRequest();
-        xhr.open("GET", '/images/green.png');
-        xhr.responseType = 'blob';
-        xhr.send();
-        xhr.onload = function() {
-            resolve(xhr.response);
-        };
-    });
-    promise.then(function(response) {
-        return createImageBitmap(response).then(bitmap => {
-            var pattern = ctx.createPattern(bitmap, 'no-repeat');
-            ctx.fillStyle = pattern;
-            ctx.translate(50, 0);
-            ctx.fillRect(-50, 0, 100, 50);
-            @assert pixel 1,1 == 0,255,0,255;
-            @assert pixel 98,1 == 0,255,0,255;
-            @assert pixel 1,48 == 0,255,0,255;
-            @assert pixel 98,48 == 0,255,0,255;
-        });
-    }).then(t_pass, t_fail);
-
-- name: 2d.pattern.paint.norepeat.coord2
-  code: |
-    var promise = new Promise(function(resolve, reject) {
-        var xhr = new XMLHttpRequest();
-        xhr.open("GET", '/images/green.png');
-        xhr.responseType = 'blob';
-        xhr.send();
-        xhr.onload = function() {
-            resolve(xhr.response);
-        };
-    });
-    promise.then(function(response) {
-        return createImageBitmap(response).then(bitmap => {
-            var pattern = ctx.createPattern(bitmap, 'no-repeat');
-            ctx.fillStyle = pattern;
-            ctx.fillRect(0, 0, 50, 50);
-            ctx.fillStyle = '#f00';
-            ctx.fillRect(50, 0, 50, 50);
-            ctx.fillStyle = pattern;
-            ctx.translate(50, 0);
-            ctx.fillRect(-50, 0, 100, 50);
-            @assert pixel 1,1 == 0,255,0,255;
-            @assert pixel 98,1 == 0,255,0,255;
-            @assert pixel 1,48 == 0,255,0,255;
-            @assert pixel 98,48 == 0,255,0,255;
-        });
-    }).then(t_pass, t_fail);
-
-- name: 2d.pattern.paint.norepeat.coord3
-  code: |
-    ctx.fillStyle = '#0f0';
-    ctx.fillRect(0, 0, 100, 50);
-    var promise = new Promise(function(resolve, reject) {
-        var xhr = new XMLHttpRequest();
-        xhr.open("GET", '/images/red.png');
-        xhr.responseType = 'blob';
-        xhr.send();
-        xhr.onload = function() {
-            resolve(xhr.response);
-        };
-    });
-    promise.then(function(response) {
-        return createImageBitmap(response).then(bitmap => {
-            var pattern = ctx.createPattern(bitmap, 'no-repeat');
-            ctx.fillStyle = pattern;
-            ctx.translate(50, 25);
-            ctx.fillRect(-50, -25, 100, 50);
-            ctx.fillStyle = '#0f0';
-            ctx.fillRect(0, 0, 50, 25);
-            @assert pixel 1,1 == 0,255,0,255;
-            @assert pixel 98,1 == 0,255,0,255;
-            @assert pixel 1,48 == 0,255,0,255;
-            @assert pixel 98,48 == 0,255,0,255;
-        });
-    }).then(t_pass, t_fail);
-
-- name: 2d.pattern.paint.repeat.basic
-  code: |
-    ctx.fillStyle = '#f00';
-    ctx.fillRect(0, 0, 100, 50);
-    var promise = new Promise(function(resolve, reject) {
-        var xhr = new XMLHttpRequest();
-        xhr.open("GET", '/images/green-16x16.png');
-        xhr.responseType = 'blob';
-        xhr.send();
-        xhr.onload = function() {
-            resolve(xhr.response);
-        };
-    });
-    promise.then(function(response) {
-        return createImageBitmap(response).then(bitmap => {
-            var pattern = ctx.createPattern(bitmap, 'no-repeat');
-            ctx.fillStyle = pattern;
-            ctx.fillRect(0, 0, 100, 50);
-            @assert pixel 1,1 == 0,255,0,255;
-            @assert pixel 98,1 == 0,255,0,255;
-            @assert pixel 1,48 == 0,255,0,255;
-            @assert pixel 98,48 == 0,255,0,255;
-        });
-    }).then(t_pass, t_fail);
-
-- name: 2d.pattern.paint.repeat.outside
-  code: |
-    ctx.fillStyle = '#f00';
-    ctx.fillRect(0, 0, 100, 50);
-    var promise = new Promise(function(resolve, reject) {
-        var xhr = new XMLHttpRequest();
-        xhr.open("GET", '/images/green-16x16.png');
-        xhr.responseType = 'blob';
-        xhr.send();
-        xhr.onload = function() {
-            resolve(xhr.response);
-        };
-    });
-    promise.then(function(response) {
-        return createImageBitmap(response).then(bitmap => {
-            var pattern = ctx.createPattern(bitmap, 'no-repeat');
-            ctx.fillStyle = pattern;
-            ctx.translate(50, 25);
-            ctx.fillRect(-50, -25, 100, 50);
-            @assert pixel 1,1 == 0,255,0,255;
-            @assert pixel 98,1 == 0,255,0,255;
-            @assert pixel 1,48 == 0,255,0,255;
-            @assert pixel 98,48 == 0,255,0,255;
-        });
-    }).then(t_pass, t_fail);
-
-- name: 2d.pattern.paint.repeat.coord1
-  code: |
-    ctx.fillStyle = '#f00';
-    ctx.fillRect(0, 0, 100, 50);
-    var promise = new Promise(function(resolve, reject) {
-        var xhr = new XMLHttpRequest();
-        xhr.open("GET", '/images/rgrg-256x256.png');
-        xhr.responseType = 'blob';
-        xhr.send();
-        xhr.onload = function() {
-            resolve(xhr.response);
-        };
-    });
-    promise.then(function(response) {
-        return createImageBitmap(response).then(bitmap => {
-            var pattern = ctx.createPattern(bitmap, 'no-repeat');
-            ctx.fillStyle = pattern;
-            ctx.translate(-128, -78);
-            ctx.fillRect(128, 78, 100, 50);
-            @assert pixel 1,1 == 0,255,0,255;
-            @assert pixel 98,1 == 0,255,0,255;
-            @assert pixel 1,48 == 0,255,0,255;
-            @assert pixel 98,48 == 0,255,0,255;
-        });
-    }).then(t_pass, t_fail);
-
-- name: 2d.pattern.paint.repeat.coord2
-  code: |
-    var promise = new Promise(function(resolve, reject) {
-        var xhr = new XMLHttpRequest();
-        xhr.open("GET", '/images/grgr-256x256.png');
-        xhr.responseType = 'blob';
-        xhr.send();
-        xhr.onload = function() {
-            resolve(xhr.response);
-        };
-    });
-    promise.then(function(response) {
-        return createImageBitmap(response).then(bitmap => {
-            var pattern = ctx.createPattern(bitmap, 'no-repeat');
-            ctx.fillStyle = pattern;
-            ctx.fillRect(0, 0, 100, 50);
-            @assert pixel 1,1 == 0,255,0,255;
-            @assert pixel 98,1 == 0,255,0,255;
-            @assert pixel 1,48 == 0,255,0,255;
-            @assert pixel 98,48 == 0,255,0,255;
-        });
-    }).then(t_pass, t_fail);
-
-- name: 2d.pattern.paint.repeat.coord3
-  code: |
-    var promise = new Promise(function(resolve, reject) {
-        var xhr = new XMLHttpRequest();
-        xhr.open("GET", '/images/rgrg-256x256.png');
-        xhr.responseType = 'blob';
-        xhr.send();
-        xhr.onload = function() {
-            resolve(xhr.response);
-        };
-    });
-    promise.then(function(response) {
-        return createImageBitmap(response).then(bitmap => {
-            var pattern = ctx.createPattern(bitmap, 'no-repeat');
-            ctx.fillStyle = pattern;
-            ctx.fillRect(0, 0, 100, 50);
-            ctx.translate(-128, -78);
-            ctx.fillRect(128, 78, 100, 50);
-            @assert pixel 1,1 == 0,255,0,255;
-            @assert pixel 98,1 == 0,255,0,255;
-            @assert pixel 1,48 == 0,255,0,255;
-            @assert pixel 98,48 == 0,255,0,255;
-        });
-    }).then(t_pass, t_fail);
-
-- name: 2d.pattern.paint.repeatx.basic
-  code: |
-    ctx.fillStyle = '#0f0';
-    ctx.fillRect(0, 0, 100, 50);
-    ctx.fillStyle = '#f00';
-    ctx.fillRect(0, 0, 100, 16);
-    var promise = new Promise(function(resolve, reject) {
-        var xhr = new XMLHttpRequest();
-        xhr.open("GET", '/images/green-16x16.png');
-        xhr.responseType = 'blob';
-        xhr.send();
-        xhr.onload = function() {
-            resolve(xhr.response);
-        };
-    });
-    promise.then(function(response) {
-        return createImageBitmap(response).then(bitmap => {
-            var pattern = ctx.createPattern(bitmap, 'repeat-x');
-            ctx.fillStyle = pattern;
-            ctx.fillRect(0, 0, 100, 50);
-            @assert pixel 1,1 == 0,255,0,255;
-            @assert pixel 98,1 == 0,255,0,255;
-            @assert pixel 1,48 == 0,255,0,255;
-            @assert pixel 98,48 == 0,255,0,255;
-        });
-    }).then(t_pass, t_fail);
-
-- name: 2d.pattern.paint.repeatx.outside
-  code: |
-    ctx.fillStyle = '#0f0';
-    ctx.fillRect(0, 0, 100, 50);
-    var promise = new Promise(function(resolve, reject) {
-        var xhr = new XMLHttpRequest();
-        xhr.open("GET", '/images/red-16x16.png');
-        xhr.responseType = 'blob';
-        xhr.send();
-        xhr.onload = function() {
-            resolve(xhr.response);
-        };
-    });
-    promise.then(function(response) {
-        return createImageBitmap(response).then(bitmap => {
-            var pattern = ctx.createPattern(bitmap, 'repeat-x');
-            ctx.fillStyle = pattern;
-            ctx.fillRect(0, 0, 100, 50);
-            ctx.fillStyle = '#0f0';
-            ctx.fillRect(0, 0, 100, 16);
-            @assert pixel 1,1 == 0,255,0,255;
-            @assert pixel 98,1 == 0,255,0,255;
-            @assert pixel 1,48 == 0,255,0,255;
-            @assert pixel 98,48 == 0,255,0,255;
-        });
-    }).then(t_pass, t_fail);
-
-- name: 2d.pattern.paint.repeatx.coord1
-  code: |
-    ctx.fillStyle = '#0f0';
-    ctx.fillRect(0, 0, 100, 50);
-    var promise = new Promise(function(resolve, reject) {
-        var xhr = new XMLHttpRequest();
-        xhr.open("GET", '/images/red-16x16.png');
-        xhr.responseType = 'blob';
-        xhr.send();
-        xhr.onload = function() {
-            resolve(xhr.response);
-        };
-    });
-    promise.then(function(response) {
-        return createImageBitmap(response).then(bitmap => {
-            var pattern = ctx.createPattern(bitmap, 'repeat-x');
-            ctx.fillStyle = pattern;
-            ctx.translate(0, 16);
-            ctx.fillRect(0, -16, 100, 50);
-            ctx.fillStyle = '#0f0';
-            ctx.fillRect(0, 0, 100, 16);
-            @assert pixel 1,1 == 0,255,0,255;
-            @assert pixel 98,1 == 0,255,0,255;
-            @assert pixel 1,25 == 0,255,0,255;
-            @assert pixel 98,25 == 0,255,0,255;
-            @assert pixel 1,48 == 0,255,0,255;
-            @assert pixel 98,48 == 0,255,0,255;
-        });
-    }).then(t_pass, t_fail);
-
-- name: 2d.pattern.paint.repeaty.basic
-  code: |
-    ctx.fillStyle = '#0f0';
-    ctx.fillRect(0, 0, 100, 50);
-    ctx.fillStyle = '#f00';
-    ctx.fillRect(0, 0, 16, 50);
-    var promise = new Promise(function(resolve, reject) {
-        var xhr = new XMLHttpRequest();
-        xhr.open("GET", '/images/green-16x16.png');
-        xhr.responseType = 'blob';
-        xhr.send();
-        xhr.onload = function() {
-            resolve(xhr.response);
-        };
-    });
-    promise.then(function(response) {
-        return createImageBitmap(response).then(bitmap => {
-            var pattern = ctx.createPattern(bitmap, 'repeat-y');
-            ctx.fillStyle = pattern;
-            ctx.fillRect(0, 0, 100, 50);
-            @assert pixel 1,1 == 0,255,0,255;
-            @assert pixel 98,1 == 0,255,0,255;
-            @assert pixel 1,48 == 0,255,0,255;
-            @assert pixel 98,48 == 0,255,0,255;
-        });
-    }).then(t_pass, t_fail);
-
-- name: 2d.pattern.paint.repeaty.outside
-  code: |
-    ctx.fillStyle = '#0f0';
-    ctx.fillRect(0, 0, 100, 50);
-    var promise = new Promise(function(resolve, reject) {
-        var xhr = new XMLHttpRequest();
-        xhr.open("GET", '/images/red-16x16.png');
-        xhr.responseType = 'blob';
-        xhr.send();
-        xhr.onload = function() {
-            resolve(xhr.response);
-        };
-    });
-    promise.then(function(response) {
-        return createImageBitmap(response).then(bitmap => {
-            var pattern = ctx.createPattern(bitmap, 'repeat-y');
-            ctx.fillStyle = pattern;
-            ctx.fillRect(0, 0, 100, 50);
-            ctx.fillStyle = '#0f0';
-            ctx.fillRect(0, 0, 16, 50);
-            @assert pixel 1,1 == 0,255,0,255;
-            @assert pixel 98,1 == 0,255,0,255;
-            @assert pixel 1,48 == 0,255,0,255;
-            @assert pixel 98,48 == 0,255,0,255;
-        });
-    }).then(t_pass, t_fail);
-
-- name: 2d.pattern.paint.repeaty.coord1
-  images:
-  - red-16x16.png
-  code: |
-    ctx.fillStyle = '#0f0';
-    ctx.fillRect(0, 0, 100, 50);
-    var promise = new Promise(function(resolve, reject) {
-        var xhr = new XMLHttpRequest();
-        xhr.open("GET", '/images/red-16x16.png');
-        xhr.responseType = 'blob';
-        xhr.send();
-        xhr.onload = function() {
-            resolve(xhr.response);
-        };
-    });
-    promise.then(function(response) {
-        return createImageBitmap(response).then(bitmap => {
-            var pattern = ctx.createPattern(bitmap, 'repeat-y');
-            ctx.fillStyle = pattern;
-            ctx.translate(48, 0);
-            ctx.fillRect(-48, 0, 100, 50);
-            ctx.fillStyle = '#0f0';
-            ctx.fillRect(0, 0, 16, 50);
-            @assert pixel 1,1 == 0,255,0,255;
-            @assert pixel 50,1 == 0,255,0,255;
-            @assert pixel 98,1 == 0,255,0,255;
-            @assert pixel 1,48 == 0,255,0,255;
-            @assert pixel 50,48 == 0,255,0,255;
-            @assert pixel 98,48 == 0,255,0,255;
-        });
-    }).then(t_pass, t_fail);
-
-- name: 2d.pattern.paint.orientation.image
-  desc: Image patterns do not get flipped when painted
-  code: |
-    ctx.fillStyle = '#f00';
-    ctx.fillRect(0, 0, 100, 50);
-    var promise = new Promise(function(resolve, reject) {
-        var xhr = new XMLHttpRequest();
-        xhr.open("GET", '/images/rrgg-256x256.png');
-        xhr.responseType = 'blob';
-        xhr.send();
-        xhr.onload = function() {
-            resolve(xhr.response);
-        };
-    });
-    promise.then(function(response) {
-        return createImageBitmap(response).then(bitmap => {
-            var pattern = ctx.createPattern(bitmap, 'no-repeat');
-            ctx.fillStyle = pattern;
-            ctx.save();
-            ctx.translate(0, -103);
-            ctx.fillRect(0, 103, 100, 50);
-            ctx.restore();
-            ctx.fillStyle = '#0f0';
-            ctx.fillRect(0, 0, 100, 25);
-            @assert pixel 1,1 == 0,255,0,255;
-            @assert pixel 98,1 == 0,255,0,255;
-            @assert pixel 1,48 == 0,255,0,255;
-            @assert pixel 98,48 == 0,255,0,255;
-        });
-    }).then(t_pass, t_fail);
-
-- name: 2d.pattern.paint.orientation.canvas
-  desc: Canvas patterns do not get flipped when painted
-  code: |
-    ctx.fillStyle = '#f00';
-    ctx.fillRect(0, 0, 100, 50);
-    var offscreenCanvas2 = new OffscreenCanvas(100, 50);
-    var ctx2 = offscreenCanvas2.getContext('2d');
-    ctx2.fillStyle = '#f00';
-    ctx2.fillRect(0, 0, 100, 25);
-    ctx2.fillStyle = '#0f0';
-    ctx2.fillRect(0, 25, 100, 25);
-    var pattern = ctx.createPattern(offscreenCanvas2, 'no-repeat');
-    ctx.fillStyle = pattern;
-    ctx.fillRect(0, 0, 100, 50);
-    ctx.fillStyle = '#0f0';
-    ctx.fillRect(0, 0, 100, 25);
-    @assert pixel 1,1 == 0,255,0,255;
-    @assert pixel 98,1 == 0,255,0,255;
-    @assert pixel 1,48 == 0,255,0,255;
-    @assert pixel 98,48 == 0,255,0,255;
-    t.done();
diff --git a/third_party/blink/web_tests/platform/linux/css3/filters/effect-reference-zoom-hw-expected.png b/third_party/blink/web_tests/platform/linux/css3/filters/effect-reference-zoom-hw-expected.png
index 6e6ed5c..434f7c6 100644
--- a/third_party/blink/web_tests/platform/linux/css3/filters/effect-reference-zoom-hw-expected.png
+++ b/third_party/blink/web_tests/platform/linux/css3/filters/effect-reference-zoom-hw-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/linux/svg/W3C-SVG-1.1/filters-displace-01-f-expected.png b/third_party/blink/web_tests/platform/linux/svg/W3C-SVG-1.1/filters-displace-01-f-expected.png
index ebda794..125ab4c 100644
--- a/third_party/blink/web_tests/platform/linux/svg/W3C-SVG-1.1/filters-displace-01-f-expected.png
+++ b/third_party/blink/web_tests/platform/linux/svg/W3C-SVG-1.1/filters-displace-01-f-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/linux/virtual/scalefactor200/css3/filters/effect-reference-expected.png b/third_party/blink/web_tests/platform/linux/virtual/scalefactor200/css3/filters/effect-reference-expected.png
index f5c8ea5..5bf9826 100644
--- a/third_party/blink/web_tests/platform/linux/virtual/scalefactor200/css3/filters/effect-reference-expected.png
+++ b/third_party/blink/web_tests/platform/linux/virtual/scalefactor200/css3/filters/effect-reference-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/linux/virtual/scalefactor200/css3/filters/effect-reference-subregion-expected.png b/third_party/blink/web_tests/platform/linux/virtual/scalefactor200/css3/filters/effect-reference-subregion-expected.png
index 9c56e02a..8be4bfd 100644
--- a/third_party/blink/web_tests/platform/linux/virtual/scalefactor200/css3/filters/effect-reference-subregion-expected.png
+++ b/third_party/blink/web_tests/platform/linux/virtual/scalefactor200/css3/filters/effect-reference-subregion-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/linux/virtual/scalefactor200/css3/filters/effect-reference-zoom-expected.png b/third_party/blink/web_tests/platform/linux/virtual/scalefactor200/css3/filters/effect-reference-zoom-expected.png
index fab7cb1..ea5cf08 100644
--- a/third_party/blink/web_tests/platform/linux/virtual/scalefactor200/css3/filters/effect-reference-zoom-expected.png
+++ b/third_party/blink/web_tests/platform/linux/virtual/scalefactor200/css3/filters/effect-reference-zoom-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac-mac11-arm64/css3/filters/effect-reference-expected.png b/third_party/blink/web_tests/platform/mac-mac11-arm64/css3/filters/effect-reference-expected.png
index dc5eaac..bbe8cde 100644
--- a/third_party/blink/web_tests/platform/mac-mac11-arm64/css3/filters/effect-reference-expected.png
+++ b/third_party/blink/web_tests/platform/mac-mac11-arm64/css3/filters/effect-reference-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac-mac11-arm64/css3/filters/effect-reference-subregion-expected.png b/third_party/blink/web_tests/platform/mac-mac11-arm64/css3/filters/effect-reference-subregion-expected.png
index 92d2220..194f0e1 100644
--- a/third_party/blink/web_tests/platform/mac-mac11-arm64/css3/filters/effect-reference-subregion-expected.png
+++ b/third_party/blink/web_tests/platform/mac-mac11-arm64/css3/filters/effect-reference-subregion-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac-mac11-arm64/css3/filters/effect-reference-zoom-expected.png b/third_party/blink/web_tests/platform/mac-mac11-arm64/css3/filters/effect-reference-zoom-expected.png
index 4c791106..4a694eb 100644
--- a/third_party/blink/web_tests/platform/mac-mac11-arm64/css3/filters/effect-reference-zoom-expected.png
+++ b/third_party/blink/web_tests/platform/mac-mac11-arm64/css3/filters/effect-reference-zoom-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac-mac11-arm64/virtual/scalefactor200/css3/filters/effect-reference-expected.png b/third_party/blink/web_tests/platform/mac-mac11-arm64/virtual/scalefactor200/css3/filters/effect-reference-expected.png
index 0be30f99..e6a4432 100644
--- a/third_party/blink/web_tests/platform/mac-mac11-arm64/virtual/scalefactor200/css3/filters/effect-reference-expected.png
+++ b/third_party/blink/web_tests/platform/mac-mac11-arm64/virtual/scalefactor200/css3/filters/effect-reference-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac-mac11-arm64/virtual/scalefactor200/css3/filters/effect-reference-subregion-expected.png b/third_party/blink/web_tests/platform/mac-mac11-arm64/virtual/scalefactor200/css3/filters/effect-reference-subregion-expected.png
index 148cf83..14e7288 100644
--- a/third_party/blink/web_tests/platform/mac-mac11-arm64/virtual/scalefactor200/css3/filters/effect-reference-subregion-expected.png
+++ b/third_party/blink/web_tests/platform/mac-mac11-arm64/virtual/scalefactor200/css3/filters/effect-reference-subregion-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac-mac11-arm64/virtual/scalefactor200/css3/filters/effect-reference-zoom-expected.png b/third_party/blink/web_tests/platform/mac-mac11-arm64/virtual/scalefactor200/css3/filters/effect-reference-zoom-expected.png
index c1988bf8..773af12 100644
--- a/third_party/blink/web_tests/platform/mac-mac11-arm64/virtual/scalefactor200/css3/filters/effect-reference-zoom-expected.png
+++ b/third_party/blink/web_tests/platform/mac-mac11-arm64/virtual/scalefactor200/css3/filters/effect-reference-zoom-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac-mac12-arm64/css3/filters/effect-reference-expected.png b/third_party/blink/web_tests/platform/mac-mac12-arm64/css3/filters/effect-reference-expected.png
index dc5eaac..bbe8cde 100644
--- a/third_party/blink/web_tests/platform/mac-mac12-arm64/css3/filters/effect-reference-expected.png
+++ b/third_party/blink/web_tests/platform/mac-mac12-arm64/css3/filters/effect-reference-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac-mac12-arm64/css3/filters/effect-reference-subregion-expected.png b/third_party/blink/web_tests/platform/mac-mac12-arm64/css3/filters/effect-reference-subregion-expected.png
index 92d2220..194f0e1 100644
--- a/third_party/blink/web_tests/platform/mac-mac12-arm64/css3/filters/effect-reference-subregion-expected.png
+++ b/third_party/blink/web_tests/platform/mac-mac12-arm64/css3/filters/effect-reference-subregion-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac-mac12-arm64/css3/filters/effect-reference-zoom-expected.png b/third_party/blink/web_tests/platform/mac-mac12-arm64/css3/filters/effect-reference-zoom-expected.png
index 4c791106..4a694eb 100644
--- a/third_party/blink/web_tests/platform/mac-mac12-arm64/css3/filters/effect-reference-zoom-expected.png
+++ b/third_party/blink/web_tests/platform/mac-mac12-arm64/css3/filters/effect-reference-zoom-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac-mac12-arm64/virtual/scalefactor200/css3/filters/effect-reference-expected.png b/third_party/blink/web_tests/platform/mac-mac12-arm64/virtual/scalefactor200/css3/filters/effect-reference-expected.png
index 0be30f99..e6a4432 100644
--- a/third_party/blink/web_tests/platform/mac-mac12-arm64/virtual/scalefactor200/css3/filters/effect-reference-expected.png
+++ b/third_party/blink/web_tests/platform/mac-mac12-arm64/virtual/scalefactor200/css3/filters/effect-reference-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac-mac12-arm64/virtual/scalefactor200/css3/filters/effect-reference-subregion-expected.png b/third_party/blink/web_tests/platform/mac-mac12-arm64/virtual/scalefactor200/css3/filters/effect-reference-subregion-expected.png
index 148cf83..14e7288 100644
--- a/third_party/blink/web_tests/platform/mac-mac12-arm64/virtual/scalefactor200/css3/filters/effect-reference-subregion-expected.png
+++ b/third_party/blink/web_tests/platform/mac-mac12-arm64/virtual/scalefactor200/css3/filters/effect-reference-subregion-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac-mac12-arm64/virtual/scalefactor200/css3/filters/effect-reference-zoom-expected.png b/third_party/blink/web_tests/platform/mac-mac12-arm64/virtual/scalefactor200/css3/filters/effect-reference-zoom-expected.png
index c1988bf8..773af12 100644
--- a/third_party/blink/web_tests/platform/mac-mac12-arm64/virtual/scalefactor200/css3/filters/effect-reference-zoom-expected.png
+++ b/third_party/blink/web_tests/platform/mac-mac12-arm64/virtual/scalefactor200/css3/filters/effect-reference-zoom-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac-mac13-arm64/css3/filters/effect-reference-expected.png b/third_party/blink/web_tests/platform/mac-mac13-arm64/css3/filters/effect-reference-expected.png
index dc5eaac..bbe8cde 100644
--- a/third_party/blink/web_tests/platform/mac-mac13-arm64/css3/filters/effect-reference-expected.png
+++ b/third_party/blink/web_tests/platform/mac-mac13-arm64/css3/filters/effect-reference-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac-mac13-arm64/css3/filters/effect-reference-subregion-expected.png b/third_party/blink/web_tests/platform/mac-mac13-arm64/css3/filters/effect-reference-subregion-expected.png
index 92d2220..194f0e1 100644
--- a/third_party/blink/web_tests/platform/mac-mac13-arm64/css3/filters/effect-reference-subregion-expected.png
+++ b/third_party/blink/web_tests/platform/mac-mac13-arm64/css3/filters/effect-reference-subregion-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac-mac13-arm64/css3/filters/effect-reference-zoom-expected.png b/third_party/blink/web_tests/platform/mac-mac13-arm64/css3/filters/effect-reference-zoom-expected.png
index 4c791106..4a694eb 100644
--- a/third_party/blink/web_tests/platform/mac-mac13-arm64/css3/filters/effect-reference-zoom-expected.png
+++ b/third_party/blink/web_tests/platform/mac-mac13-arm64/css3/filters/effect-reference-zoom-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac-mac13-arm64/virtual/scalefactor200/css3/filters/effect-reference-expected.png b/third_party/blink/web_tests/platform/mac-mac13-arm64/virtual/scalefactor200/css3/filters/effect-reference-expected.png
index 0be30f99..e6a4432 100644
--- a/third_party/blink/web_tests/platform/mac-mac13-arm64/virtual/scalefactor200/css3/filters/effect-reference-expected.png
+++ b/third_party/blink/web_tests/platform/mac-mac13-arm64/virtual/scalefactor200/css3/filters/effect-reference-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac-mac13-arm64/virtual/scalefactor200/css3/filters/effect-reference-subregion-expected.png b/third_party/blink/web_tests/platform/mac-mac13-arm64/virtual/scalefactor200/css3/filters/effect-reference-subregion-expected.png
index 148cf83..14e7288 100644
--- a/third_party/blink/web_tests/platform/mac-mac13-arm64/virtual/scalefactor200/css3/filters/effect-reference-subregion-expected.png
+++ b/third_party/blink/web_tests/platform/mac-mac13-arm64/virtual/scalefactor200/css3/filters/effect-reference-subregion-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac-mac13-arm64/virtual/scalefactor200/css3/filters/effect-reference-zoom-expected.png b/third_party/blink/web_tests/platform/mac-mac13-arm64/virtual/scalefactor200/css3/filters/effect-reference-zoom-expected.png
index c1988bf8..773af12 100644
--- a/third_party/blink/web_tests/platform/mac-mac13-arm64/virtual/scalefactor200/css3/filters/effect-reference-zoom-expected.png
+++ b/third_party/blink/web_tests/platform/mac-mac13-arm64/virtual/scalefactor200/css3/filters/effect-reference-zoom-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac/css3/filters/effect-reference-expected.png b/third_party/blink/web_tests/platform/mac/css3/filters/effect-reference-expected.png
index 3b3e27f..53bb7d7 100644
--- a/third_party/blink/web_tests/platform/mac/css3/filters/effect-reference-expected.png
+++ b/third_party/blink/web_tests/platform/mac/css3/filters/effect-reference-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac/css3/filters/effect-reference-subregion-expected.png b/third_party/blink/web_tests/platform/mac/css3/filters/effect-reference-subregion-expected.png
index 3d55a4a..d1a7404a 100644
--- a/third_party/blink/web_tests/platform/mac/css3/filters/effect-reference-subregion-expected.png
+++ b/third_party/blink/web_tests/platform/mac/css3/filters/effect-reference-subregion-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac/svg/W3C-SVG-1.1/filters-displace-01-f-expected.png b/third_party/blink/web_tests/platform/mac/svg/W3C-SVG-1.1/filters-displace-01-f-expected.png
index f097ebc..d363590 100644
--- a/third_party/blink/web_tests/platform/mac/svg/W3C-SVG-1.1/filters-displace-01-f-expected.png
+++ b/third_party/blink/web_tests/platform/mac/svg/W3C-SVG-1.1/filters-displace-01-f-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac/virtual/scalefactor200/css3/filters/effect-reference-expected.png b/third_party/blink/web_tests/platform/mac/virtual/scalefactor200/css3/filters/effect-reference-expected.png
index f5c8ea5..5bf9826 100644
--- a/third_party/blink/web_tests/platform/mac/virtual/scalefactor200/css3/filters/effect-reference-expected.png
+++ b/third_party/blink/web_tests/platform/mac/virtual/scalefactor200/css3/filters/effect-reference-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac/virtual/scalefactor200/css3/filters/effect-reference-subregion-expected.png b/third_party/blink/web_tests/platform/mac/virtual/scalefactor200/css3/filters/effect-reference-subregion-expected.png
index 9c56e02a..8be4bfd 100644
--- a/third_party/blink/web_tests/platform/mac/virtual/scalefactor200/css3/filters/effect-reference-subregion-expected.png
+++ b/third_party/blink/web_tests/platform/mac/virtual/scalefactor200/css3/filters/effect-reference-subregion-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac/virtual/scalefactor200/css3/filters/effect-reference-zoom-expected.png b/third_party/blink/web_tests/platform/mac/virtual/scalefactor200/css3/filters/effect-reference-zoom-expected.png
index fab7cb1..ea5cf08 100644
--- a/third_party/blink/web_tests/platform/mac/virtual/scalefactor200/css3/filters/effect-reference-zoom-expected.png
+++ b/third_party/blink/web_tests/platform/mac/virtual/scalefactor200/css3/filters/effect-reference-zoom-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/css3/filters/effect-reference-expected.png b/third_party/blink/web_tests/platform/win/css3/filters/effect-reference-expected.png
index c67931a..afcfb9b 100644
--- a/third_party/blink/web_tests/platform/win/css3/filters/effect-reference-expected.png
+++ b/third_party/blink/web_tests/platform/win/css3/filters/effect-reference-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/css3/filters/effect-reference-subregion-expected.png b/third_party/blink/web_tests/platform/win/css3/filters/effect-reference-subregion-expected.png
index 3a9cd7b..cc43a0cd 100644
--- a/third_party/blink/web_tests/platform/win/css3/filters/effect-reference-subregion-expected.png
+++ b/third_party/blink/web_tests/platform/win/css3/filters/effect-reference-subregion-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/svg/W3C-SVG-1.1/filters-displace-01-f-expected.png b/third_party/blink/web_tests/platform/win/svg/W3C-SVG-1.1/filters-displace-01-f-expected.png
index 5553d3d..005f0b8b 100644
--- a/third_party/blink/web_tests/platform/win/svg/W3C-SVG-1.1/filters-displace-01-f-expected.png
+++ b/third_party/blink/web_tests/platform/win/svg/W3C-SVG-1.1/filters-displace-01-f-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/virtual/scalefactor200/css3/filters/effect-reference-expected.png b/third_party/blink/web_tests/platform/win/virtual/scalefactor200/css3/filters/effect-reference-expected.png
index c5f2def..33da66c 100644
--- a/third_party/blink/web_tests/platform/win/virtual/scalefactor200/css3/filters/effect-reference-expected.png
+++ b/third_party/blink/web_tests/platform/win/virtual/scalefactor200/css3/filters/effect-reference-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/virtual/scalefactor200/css3/filters/effect-reference-subregion-expected.png b/third_party/blink/web_tests/platform/win/virtual/scalefactor200/css3/filters/effect-reference-subregion-expected.png
index b99c08431..6267f66 100644
--- a/third_party/blink/web_tests/platform/win/virtual/scalefactor200/css3/filters/effect-reference-subregion-expected.png
+++ b/third_party/blink/web_tests/platform/win/virtual/scalefactor200/css3/filters/effect-reference-subregion-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/virtual/scalefactor200/css3/filters/effect-reference-zoom-expected.png b/third_party/blink/web_tests/platform/win/virtual/scalefactor200/css3/filters/effect-reference-zoom-expected.png
index 7a31ea8..b502752 100644
--- a/third_party/blink/web_tests/platform/win/virtual/scalefactor200/css3/filters/effect-reference-zoom-expected.png
+++ b/third_party/blink/web_tests/platform/win/virtual/scalefactor200/css3/filters/effect-reference-zoom-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/svg/custom/feDisplacementMap-01-expected.png b/third_party/blink/web_tests/svg/custom/feDisplacementMap-01-expected.png
index cbdad660..52a48ea0 100644
--- a/third_party/blink/web_tests/svg/custom/feDisplacementMap-01-expected.png
+++ b/third_party/blink/web_tests/svg/custom/feDisplacementMap-01-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/svg/dynamic-updates/SVGFEDisplacementMapElement-dom-in-attr-expected.png b/third_party/blink/web_tests/svg/dynamic-updates/SVGFEDisplacementMapElement-dom-in-attr-expected.png
index 850fa68..88290ae 100644
--- a/third_party/blink/web_tests/svg/dynamic-updates/SVGFEDisplacementMapElement-dom-in-attr-expected.png
+++ b/third_party/blink/web_tests/svg/dynamic-updates/SVGFEDisplacementMapElement-dom-in-attr-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/svg/dynamic-updates/SVGFEDisplacementMapElement-dom-in2-attr-expected.png b/third_party/blink/web_tests/svg/dynamic-updates/SVGFEDisplacementMapElement-dom-in2-attr-expected.png
index 850fa68..88290ae 100644
--- a/third_party/blink/web_tests/svg/dynamic-updates/SVGFEDisplacementMapElement-dom-in2-attr-expected.png
+++ b/third_party/blink/web_tests/svg/dynamic-updates/SVGFEDisplacementMapElement-dom-in2-attr-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/svg/dynamic-updates/SVGFEDisplacementMapElement-dom-scale-attr-expected.png b/third_party/blink/web_tests/svg/dynamic-updates/SVGFEDisplacementMapElement-dom-scale-attr-expected.png
index 850fa68..88290ae 100644
--- a/third_party/blink/web_tests/svg/dynamic-updates/SVGFEDisplacementMapElement-dom-scale-attr-expected.png
+++ b/third_party/blink/web_tests/svg/dynamic-updates/SVGFEDisplacementMapElement-dom-scale-attr-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/svg/dynamic-updates/SVGFEDisplacementMapElement-dom-xChannelSelector-attr-expected.png b/third_party/blink/web_tests/svg/dynamic-updates/SVGFEDisplacementMapElement-dom-xChannelSelector-attr-expected.png
index 850fa68..88290ae 100644
--- a/third_party/blink/web_tests/svg/dynamic-updates/SVGFEDisplacementMapElement-dom-xChannelSelector-attr-expected.png
+++ b/third_party/blink/web_tests/svg/dynamic-updates/SVGFEDisplacementMapElement-dom-xChannelSelector-attr-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/svg/dynamic-updates/SVGFEDisplacementMapElement-dom-yChannelSelector-attr-expected.png b/third_party/blink/web_tests/svg/dynamic-updates/SVGFEDisplacementMapElement-dom-yChannelSelector-attr-expected.png
index 850fa68..88290ae 100644
--- a/third_party/blink/web_tests/svg/dynamic-updates/SVGFEDisplacementMapElement-dom-yChannelSelector-attr-expected.png
+++ b/third_party/blink/web_tests/svg/dynamic-updates/SVGFEDisplacementMapElement-dom-yChannelSelector-attr-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/svg/dynamic-updates/SVGFEDisplacementMapElement-svgdom-in-prop-expected.png b/third_party/blink/web_tests/svg/dynamic-updates/SVGFEDisplacementMapElement-svgdom-in-prop-expected.png
index 850fa68..88290ae 100644
--- a/third_party/blink/web_tests/svg/dynamic-updates/SVGFEDisplacementMapElement-svgdom-in-prop-expected.png
+++ b/third_party/blink/web_tests/svg/dynamic-updates/SVGFEDisplacementMapElement-svgdom-in-prop-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/svg/dynamic-updates/SVGFEDisplacementMapElement-svgdom-in2-prop-expected.png b/third_party/blink/web_tests/svg/dynamic-updates/SVGFEDisplacementMapElement-svgdom-in2-prop-expected.png
index 850fa68..88290ae 100644
--- a/third_party/blink/web_tests/svg/dynamic-updates/SVGFEDisplacementMapElement-svgdom-in2-prop-expected.png
+++ b/third_party/blink/web_tests/svg/dynamic-updates/SVGFEDisplacementMapElement-svgdom-in2-prop-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/svg/dynamic-updates/SVGFEDisplacementMapElement-svgdom-scale-prop-expected.png b/third_party/blink/web_tests/svg/dynamic-updates/SVGFEDisplacementMapElement-svgdom-scale-prop-expected.png
index 850fa68..88290ae 100644
--- a/third_party/blink/web_tests/svg/dynamic-updates/SVGFEDisplacementMapElement-svgdom-scale-prop-expected.png
+++ b/third_party/blink/web_tests/svg/dynamic-updates/SVGFEDisplacementMapElement-svgdom-scale-prop-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/svg/dynamic-updates/SVGFEDisplacementMapElement-svgdom-xChannelSelector-prop-expected.png b/third_party/blink/web_tests/svg/dynamic-updates/SVGFEDisplacementMapElement-svgdom-xChannelSelector-prop-expected.png
index 850fa68..88290ae 100644
--- a/third_party/blink/web_tests/svg/dynamic-updates/SVGFEDisplacementMapElement-svgdom-xChannelSelector-prop-expected.png
+++ b/third_party/blink/web_tests/svg/dynamic-updates/SVGFEDisplacementMapElement-svgdom-xChannelSelector-prop-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/svg/dynamic-updates/SVGFEDisplacementMapElement-svgdom-yChannelSelector-prop-expected.png b/third_party/blink/web_tests/svg/dynamic-updates/SVGFEDisplacementMapElement-svgdom-yChannelSelector-prop-expected.png
index 850fa68..88290ae 100644
--- a/third_party/blink/web_tests/svg/dynamic-updates/SVGFEDisplacementMapElement-svgdom-yChannelSelector-prop-expected.png
+++ b/third_party/blink/web_tests/svg/dynamic-updates/SVGFEDisplacementMapElement-svgdom-yChannelSelector-prop-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/svg/filters/feDisplacementMap-expected.png b/third_party/blink/web_tests/svg/filters/feDisplacementMap-expected.png
index 1e89df6..4fac2b0 100644
--- a/third_party/blink/web_tests/svg/filters/feDisplacementMap-expected.png
+++ b/third_party/blink/web_tests/svg/filters/feDisplacementMap-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/svg/filters/feDisplacementMap-filterUnits-expected.svg b/third_party/blink/web_tests/svg/filters/feDisplacementMap-filterUnits-expected.svg
index 13fad3c..1b912cd 100644
--- a/third_party/blink/web_tests/svg/filters/feDisplacementMap-filterUnits-expected.svg
+++ b/third_party/blink/web_tests/svg/filters/feDisplacementMap-filterUnits-expected.svg
@@ -1,9 +1,25 @@
 <svg xmlns="http://www.w3.org/2000/svg" width="500" height="300">
-  <rect fill="rgb(50%,50%,50%)" stroke="green" x="20" y="20" width="75" height="75"/>
-  <rect fill="rgb(50%,50%,50%)" stroke="green" x="186" y="20" width="54" height="75"/>
-  <rect fill="rgb(50%,50%,50%)" stroke="green" x="304" y="64" width="56" height="56"/>
+  <!--The gray-filled rectangles represent the expected output of the
+      displacement filter effect. Small variation is acceptable tue to
+      image sampling and fractional color values interpreted as vectors.
 
-  <rect fill="rgb(50%,50%,50%)" stroke="green" x="44" y="164" width="76" height="76"/>
-  <rect fill="rgb(50%,50%,50%)" stroke="green" x="140" y="140" width="75" height="75"/>
-  <rect fill="rgb(50%,50%,50%)" stroke="green" x="287" y="167" width="73" height="73"/>
+      The green stroked rectangles must exactly match the original SVG,
+      which are not affected by a filter.-->
+  <rect fill="rgb(50%,50%,50%)" stroke="none" x="20" y="20" width="75" height="75"/>
+  <rect fill="none" stroke="green" x="20" y="20" width="75" height="75"/>
+
+  <rect fill="rgb(50%,50%,50%)" stroke="none" x="187" y="20" width="53" height="75"/>
+  <rect fill="none" stroke="green" x="186" y="20" width="54" height="75"/>
+
+  <rect fill="rgb(50%,50%,50%)" stroke="none" x="305" y="65" width="55" height="55"/>
+  <rect fill="none" stroke="green" x="304" y="64" width="56" height="56"/>
+
+  <rect fill="rgb(50%,50%,50%)" stroke="none" x="45" y="165" width="75" height="75"/>
+  <rect fill="none" stroke="green" x="44" y="164" width="76" height="76"/>
+
+  <rect fill="rgb(50%,50%,50%)" stroke="none" x="140" y="140" width="75" height="75"/>
+  <rect fill="none" stroke="green" x="140" y="140" width="75" height="75"/>
+
+  <rect fill="rgb(50%,50%,50%)" stroke="none" x="288" y="168" width="72" height="72"/>
+  <rect fill="none" stroke="green" x="287" y="167" width="73" height="73"/>
 </svg>
diff --git a/third_party/blink/web_tests/svg/filters/feDisplacementMap-turbulence-expected.png b/third_party/blink/web_tests/svg/filters/feDisplacementMap-turbulence-expected.png
index 0e4c4dc9..839d605 100644
--- a/third_party/blink/web_tests/svg/filters/feDisplacementMap-turbulence-expected.png
+++ b/third_party/blink/web_tests/svg/filters/feDisplacementMap-turbulence-expected.png
Binary files differ
diff --git a/third_party/sqlite/BUILD.gn b/third_party/sqlite/BUILD.gn
index 60cb0d0..cd7282c 100644
--- a/third_party/sqlite/BUILD.gn
+++ b/third_party/sqlite/BUILD.gn
@@ -574,7 +574,7 @@
 }
 
 # Upstream fuzzer that tests corrupted database files.
-if (use_fuzzing_engine) {
+if (use_fuzzing_engine && !use_clang_coverage) {
   fuzzer_test("sqlite3_dbfuzz2_fuzzer") {
     include_dirs = [ "." ]
     sources = [ "src/test/dbfuzz2.c" ]
diff --git a/third_party/wpt_tools/README.chromium b/third_party/wpt_tools/README.chromium
index 8e19f3e..c0572aa 100644
--- a/third_party/wpt_tools/README.chromium
+++ b/third_party/wpt_tools/README.chromium
@@ -1,7 +1,7 @@
 Name: web-platform-tests - Test Suites for Web Platform specifications
 Short Name: wpt
 URL: https://github.com/web-platform-tests/wpt/
-Version: 66a4dc9e85ef03a9700c9c0bd28c341f847f62ff
+Version: 112626065f296e24461beee136008f5ec40b90ca
 License: LICENSES FOR W3C TEST SUITES (https://www.w3.org/Consortium/Legal/2008/03-bsd-license.html)
 License File: NOT_SHIPPED
 Security Critical: no
diff --git a/third_party/wpt_tools/wpt/tools/serve/serve.py b/third_party/wpt_tools/wpt/tools/serve/serve.py
index e5aa5f0..1725399 100644
--- a/third_party/wpt_tools/wpt/tools/serve/serve.py
+++ b/third_party/wpt_tools/wpt/tools/serve/serve.py
@@ -574,6 +574,7 @@
             ("*", "/.well-known/attribution-reporting/report-aggregate-attribution", handlers.PythonScriptHandler),
             ("*", "/.well-known/attribution-reporting/debug/report-aggregate-attribution", handlers.PythonScriptHandler),
             ("*", "/.well-known/attribution-reporting/debug/verbose", handlers.PythonScriptHandler),
+            ("*", "/.well-known/private-aggregation/*", handlers.PythonScriptHandler),
             ("*", "/.well-known/web-identity", handlers.PythonScriptHandler),
             ("*", "*.py", handlers.PythonScriptHandler),
             ("GET", "*", handlers.FileHandler)
diff --git a/third_party/wpt_tools/wpt/tools/webdriver/webdriver/bidi/modules/script.py b/third_party/wpt_tools/wpt/tools/webdriver/webdriver/bidi/modules/script.py
index d994c63..99b2692 100644
--- a/third_party/wpt_tools/wpt/tools/webdriver/webdriver/bidi/modules/script.py
+++ b/third_party/wpt_tools/wpt/tools/webdriver/webdriver/bidi/modules/script.py
@@ -7,8 +7,40 @@
 
 class ScriptEvaluateResultException(Exception):
     def __init__(self, result: Mapping[str, Any]):
+        super().__init__()
+
         self.result = result
-        super().__init__("Script execution failed.")
+
+        details = result.get("exceptionDetails", {})
+        self.column_number = details.get("columnNumber")
+        self.exception = details.get("exception")
+        self.line_number = details.get("lineNumber")
+        self.stacktrace = self.process_stacktrace(details.get("stackTrace", {}))
+        self.text = details.get("text")
+
+    def process_stacktrace(self, stacktrace: Mapping[str, Any]) -> str:
+        stack = ""
+        for frame in stacktrace.get("callFrames", []):
+            data = frame.get("functionName") or "eval code"
+            if "url" in frame:
+                data += f"@{frame['url']}"
+            data += f":{frame.get('lineNumber', 0)}:{frame.get('columnNumber', 0)}"
+            stack += data + "\n"
+
+        return stack
+
+    def __repr__(self) -> str:
+        """Return the object representation in string format."""
+        return f"<{self.__class__.__name__}(), {self.text})>"
+
+    def __str__(self) -> str:
+        """Return the string representation of the object."""
+        message: str = self.text
+
+        if self.stacktrace:
+            message += f"\n\nStacktrace:\n\n{self.stacktrace}"
+
+        return message
 
 
 class OwnershipModel(Enum):
diff --git a/third_party/wpt_tools/wpt/tools/webdriver/webdriver/bidi/modules/session.py b/third_party/wpt_tools/wpt/tools/webdriver/webdriver/bidi/modules/session.py
index 7c1fef3..cdcef11 100644
--- a/third_party/wpt_tools/wpt/tools/webdriver/webdriver/bidi/modules/session.py
+++ b/third_party/wpt_tools/wpt/tools/webdriver/webdriver/bidi/modules/session.py
@@ -6,11 +6,13 @@
 class Session(BidiModule):
     @command
     def new(self, capabilities: Mapping[str, Any]) -> Mapping[str, Mapping[str, Any]]:
-        return {"capabilities": capabilities}
+        params: MutableMapping[str, Any] = {}
+        params["capabilities"] = capabilities
+        return params
 
     @new.result
     def _new(self, result: Mapping[str, Any]) -> Any:
-        return result.get("session_id"), result.get("capabilities", {})
+        return result.get("sessionId"), result.get("capabilities", {})
 
     @command
     def subscribe(self,
diff --git a/third_party/wpt_tools/wpt/tools/wptrunner/requirements_edge.txt b/third_party/wpt_tools/wpt/tools/wptrunner/requirements_edge.txt
index d2389eee..45f93a3bb 100644
--- a/third_party/wpt_tools/wpt/tools/wptrunner/requirements_edge.txt
+++ b/third_party/wpt_tools/wpt/tools/wptrunner/requirements_edge.txt
@@ -1 +1 @@
-selenium==4.9.1
+selenium==4.10.0
diff --git a/third_party/wpt_tools/wpt/tools/wptrunner/requirements_ie.txt b/third_party/wpt_tools/wpt/tools/wptrunner/requirements_ie.txt
index c3fe460..788506b 100644
--- a/third_party/wpt_tools/wpt/tools/wptrunner/requirements_ie.txt
+++ b/third_party/wpt_tools/wpt/tools/wptrunner/requirements_ie.txt
@@ -1,2 +1,2 @@
 mozprocess==1.3.0
-selenium==4.9.1
+selenium==4.10.0
diff --git a/third_party/wpt_tools/wpt/tools/wptrunner/requirements_opera.txt b/third_party/wpt_tools/wpt/tools/wptrunner/requirements_opera.txt
index c3fe460..788506b 100644
--- a/third_party/wpt_tools/wpt/tools/wptrunner/requirements_opera.txt
+++ b/third_party/wpt_tools/wpt/tools/wptrunner/requirements_opera.txt
@@ -1,2 +1,2 @@
 mozprocess==1.3.0
-selenium==4.9.1
+selenium==4.10.0
diff --git a/third_party/wpt_tools/wpt/tools/wptrunner/requirements_sauce.txt b/third_party/wpt_tools/wpt/tools/wptrunner/requirements_sauce.txt
index abc64ec5..5740883d 100644
--- a/third_party/wpt_tools/wpt/tools/wptrunner/requirements_sauce.txt
+++ b/third_party/wpt_tools/wpt/tools/wptrunner/requirements_sauce.txt
@@ -1,2 +1,2 @@
-selenium==4.9.1
+selenium==4.10.0
 requests==2.31.0
diff --git a/tools/binary_size/libsupersize/viewer/static/dom.js b/tools/binary_size/libsupersize/viewer/static/dom.js
index be0f7cc5..ce1e388 100644
--- a/tools/binary_size/libsupersize/viewer/static/dom.js
+++ b/tools/binary_size/libsupersize/viewer/static/dom.js
@@ -21,6 +21,7 @@
   EXCLUDE: 'exclude',
   TYPE: 'type',
   FLAG_FILTER: 'flag_filter',
+  FOCUS: 'focus',
 };
 
 /** Utilities for working with the DOM */
diff --git a/tools/binary_size/libsupersize/viewer/static/start-worker.js b/tools/binary_size/libsupersize/viewer/static/start-worker.js
index c38d0c3..d4a3273 100644
--- a/tools/binary_size/libsupersize/viewer/static/start-worker.js
+++ b/tools/binary_size/libsupersize/viewer/static/start-worker.js
@@ -79,6 +79,7 @@
    * @returns {Promise<BuildTreeResults>}
    */
   buildTree() {
+    state.stFocus.set('');
     const buildOptions = state.exportToBuildOptions();
     return this._waitForResponse('buildTree', {
       buildOptions,
diff --git a/tools/binary_size/libsupersize/viewer/static/state.js b/tools/binary_size/libsupersize/viewer/static/state.js
index 1545bb95..75b8e49 100644
--- a/tools/binary_size/libsupersize/viewer/static/state.js
+++ b/tools/binary_size/libsupersize/viewer/static/state.js
@@ -275,6 +275,9 @@
     /** @public @const {!ElementUiState} */
     this.stFlagFilter = newUiState(STATE_KEY.FLAG_FILTER, g_el.rnlFlagFilter);
 
+    /** @public @const {!QueryParamUiState} */
+    this.stFocus = newUiState(STATE_KEY.FOCUS, null, true);
+
     /** @private {boolean} */
     this.diffMode = false;
   }
@@ -404,6 +407,8 @@
       }
       this.updateUrlParams();
     });
+
+    this.stFocus.addObserver(() => this.updateUrlParams());
   }
 }
 
diff --git a/tools/binary_size/libsupersize/viewer/static/symbol-tree-ui.js b/tools/binary_size/libsupersize/viewer/static/symbol-tree-ui.js
index ca0422e6..b887cca 100644
--- a/tools/binary_size/libsupersize/viewer/static/symbol-tree-ui.js
+++ b/tools/binary_size/libsupersize/viewer/static/symbol-tree-ui.js
@@ -28,6 +28,13 @@
      */
     this.ZERO_WIDTH_SPACE = '$&\u200b';
 
+    /**
+     * Expansion cascade plan: If X -> Y, then on expanding X, also expand
+     * Y if Y -> Z exists, else set focus to Y.
+     * @private @const {!Map<number, number>}
+     */
+    this.expansionIdMap = new Map();
+
     // Event listeners need to be bound to this, but each fresh .bind creates a
     // function, which wastes memory and not usable for removeEventListener().
     // The |_bound*()| functions aim to solve the abolve.
@@ -119,6 +126,48 @@
     return data.children;
   }
 
+  /** @override */
+  autoExpandAttentionWorthyChild(link, childrenElements) {
+    let nodeId = this.uiNodeToData.get(link).id;
+    if (this.expansionIdMap.has(nodeId)) {
+      const nextChildId = this.expansionIdMap.get(nodeId);
+      // Consume expansion link |nodeId| -> |nextChildId| to avoid interfering
+      // with regular UI.
+      this.expansionIdMap.delete(nodeId);
+      if (nextChildId != null) {
+        for (const childElement of childrenElements) {
+          const childNode = childElement.querySelector('.node');
+          const childId = this.uiNodeToData.get(childNode).id;
+          if (childId === nextChildId) {
+            if (this.expansionIdMap.has(childId)) {
+              // Found the child to expand: Click to expand and propagate.
+              childNode.click();
+              return;
+            }
+            // |nextChildId|'s absence in |expansionIdMap| means it's the target
+            // node ID, so set focus. Use dom.onNodeAdded() since |childElement|
+            // (and hence |childNode|) might not be added to the DOM yet.
+            dom.onNodeAdded(childNode, () => childNode.focus());
+            // Continue to default behavior, which may cause more expansion.
+            break;
+          }
+        }
+      }
+    }
+    super.autoExpandAttentionWorthyChild(link, childrenElements);
+  }
+
+  /**
+   * Adds a path to |expansionIdMap| to cause expansion cascade.
+   * @param {!Array<number>} nodePathIds
+   * @public
+   */
+  planPathExpansion(nodePathIds) {
+    for (let i = 1; i < nodePathIds.length; ++i) {
+      this.expansionIdMap.set(nodePathIds[i - 1], nodePathIds[i]);
+    }
+  }
+
   /**
    * @param {!KeyboardEvent} event
    * @protected
@@ -201,9 +250,11 @@
     const elt = /** @type {!HTMLElement} */ (event.target);
     if (this.isTerminalElement(elt))
       elt.addEventListener('mousedown', this.boundHandleRefocus);
-    displayInfocard(/** @type {!TreeNode} */ (this.uiNodeToData.get(elt)));
+    const data = /** @type {!TreeNode} */ (this.uiNodeToData.get(elt));
+    displayInfocard(data);
     /** @type {HTMLElement} */ (event.currentTarget)
         .parentElement.classList.add('focused');
+    state.stFocus.set(data.id.toString());
   }
 
   /**
@@ -219,6 +270,11 @@
         .parentElement.classList.remove('focused');
   }
 
+  /** @override @protected */
+  onTreeBlur() {
+    state.stFocus.set('');
+  }
+
   /** @override @public */
   init() {
     super.init();
diff --git a/tools/binary_size/libsupersize/viewer/static/ui-main.js b/tools/binary_size/libsupersize/viewer/static/ui-main.js
index ff4befb..21e5a72 100644
--- a/tools/binary_size/libsupersize/viewer/static/ui-main.js
+++ b/tools/binary_size/libsupersize/viewer/static/ui-main.js
@@ -216,6 +216,26 @@
     g_el.divMetadataView.classList.toggle('active', true);
   }
 
+  /**
+   * @param {!TreeWorker} worker
+   * @return {!Promise}
+   */
+  async function planSymbolTreeFocusPathExpansionIfRequired(worker) {
+    const focusStr = state.stFocus.get();
+    if (focusStr) {
+      const focus = parseInt(focusStr, 10);
+      if (!isNaN(focus)) {
+        const ancestryResults = await worker.queryAncestryById(focus);
+        if (ancestryResults.ancestorIds?.length) {
+          _symbolTreeUi.planPathExpansion(ancestryResults.ancestorIds);
+          _symbolTreeUi.focus();
+          return;
+        }
+      }
+      state.stFocus.set('');  // Clear invalid value.
+    }
+  }
+
   /** @param {!Array<!URL>} urlsToLoad */
   async function performInitialLoad(urlsToLoad) {
     let accessToken = null;
@@ -227,6 +247,7 @@
     const worker = restartWorker(onProgressMessage);
     _progress.setValue(0.3);
     const message = await worker.loadAndBuildTree('from-url://', accessToken);
+    await planSymbolTreeFocusPathExpansionIfRequired(worker);
     processLoadTreeResponse(message);
   }
 
@@ -254,7 +275,7 @@
     input.value = '';
   });
 
-  g_el.frmOptions.addEventListener('change', event => {
+  g_el.frmOptions.addEventListener('change', (event) => {
     // Update the tree when options change.
     // Some options update the tree themselves, don't regenerate when those
     // options (marked by "data-dynamic") are changed.
@@ -263,7 +284,7 @@
       rebuildTree();
     }
   });
-  g_el.frmOptions.addEventListener('submit', event => {
+  g_el.frmOptions.addEventListener('submit', (event) => {
     event.preventDefault();
     rebuildTree();
   });
@@ -275,4 +296,11 @@
   }
   if (urlsToLoad.length > 0)
     performInitialLoad(urlsToLoad);
+
+  // By default, updating the hash portion of the URL via UI does not cause page
+  // refresh. We can intercept this for interesting navigation feature, but for
+  // now just refresh the page to be consistent with other changes to the URL.
+  window.addEventListener('hashchange', (event) => {
+    window.location.reload();
+  });
 })();
diff --git a/tools/metrics/actions/actions.xml b/tools/metrics/actions/actions.xml
index 4bd778d7..910ed916 100644
--- a/tools/metrics/actions/actions.xml
+++ b/tools/metrics/actions/actions.xml
@@ -31601,7 +31601,6 @@
 </action>
 
 <action name="Signin_Impression_FromMenu" not_user_triggered="true">
-  <obsolete>Removed in M116.</obsolete>
   <owner>gogerald@chromium.org</owner>
   <description>Recorded when showing sign in entry in the menu.</description>
 </action>
@@ -32201,6 +32200,7 @@
 </action>
 
 <action name="Signin_Signin_FromContentArea">
+  <obsolete>Removed in M116.</obsolete>
   <owner>gogerald@chromium.org</owner>
   <description>
     Recorded on sign in start from access point
@@ -32295,7 +32295,6 @@
 </action>
 
 <action name="Signin_Signin_FromMenu">
-  <obsolete>Removed in M116.</obsolete>
   <owner>gogerald@chromium.org</owner>
   <description>
     Recorded on sign in start from access point
diff --git a/tools/metrics/histograms/enums.xml b/tools/metrics/histograms/enums.xml
index cd7e577..a1ca9d2 100644
--- a/tools/metrics/histograms/enums.xml
+++ b/tools/metrics/histograms/enums.xml
@@ -1282,6 +1282,7 @@
   <int value="16" label="Turn sync on button in Chrome Settings"/>
   <int value="17"
       label="Launched from ChromeOS Projector App for re-authentication"/>
+  <int value="18" label="Turn on sync menu item in Chrome app menu"/>
 </enum>
 
 <enum name="AccountManagerAccountUpsertionResultStatus">
@@ -42944,6 +42945,12 @@
              accounts."/>
 </enum>
 
+<enum name="FedCmMismatchDialogResult">
+  <int value="0" label="Continue button clicked"/>
+  <int value="1" label="Dismissed by user through clicking close icon"/>
+  <int value="2" label="Dialog unhandled by user"/>
+</enum>
+
 <enum name="FedCmNumAccounts">
   <int value="0" label="Zero accounts"/>
   <int value="1" label="One account"/>
@@ -43036,6 +43043,12 @@
   <int value="3" label="Sign in to IDP static"/>
 </enum>
 
+<enum name="FedCmShowPopupWindowResult">
+  <int value="0" label="Successfully opened"/>
+  <int value="1" label="Invalid URL"/>
+  <int value="2" label="Failed for other reasons"/>
+</enum>
+
 <enum name="FedCmSignInStateMatchStatus">
   <int value="0" label="Success"/>
   <int value="1" label="IdpClaimedSignIn"/>
@@ -59286,6 +59299,7 @@
   <int value="-1942730618" label="WebAuthenticationTouchId:disabled"/>
   <int value="-1942518189" label="HeavyAdIntervention:disabled"/>
   <int value="-1942419166" label="SyncPseudoUSSApps:disabled"/>
+  <int value="-1942058396" label="ThumbnailPlaceholder:enabled"/>
   <int value="-1942057472" label="ExperimentalAccessibilityLabels:enabled"/>
   <int value="-1941852572" label="floating-virtual-keyboard"/>
   <int value="-1940806558" label="enable-syncfs-directory-operation"/>
@@ -61069,6 +61083,7 @@
   <int value="-1024644761" label="OmniboxShortcutBoost:enabled"/>
   <int value="-1024065253" label="EnableWireGuard:disabled"/>
   <int value="-1022971520" label="enable-search-button-in-omnibox-for-str"/>
+  <int value="-1022290134" label="WebApkInstallFailureRetry:disabled"/>
   <int value="-1022281869" label="ArcRtVcpuDualCore:enabled"/>
   <int value="-1022165708" label="BreakingNewsPush:disabled"/>
   <int value="-1021097344" label="PolicyAtomicGroup:disabled"/>
@@ -61501,6 +61516,7 @@
   <int value="-800301139" label="VCLightIntensity:enabled"/>
   <int value="-800127303" label="OmniboxPedalsTranslationConsole:disabled"/>
   <int value="-799931058" label="UseMultiloginEndpoint:disabled"/>
+  <int value="-799444168" label="WebApkInstallFailureNotification:enabled"/>
   <int value="-799395788" label="SearchPrefetchServicePrefetching:disabled"/>
   <int value="-798521418" label="ArcInputOverlayBeta:disabled"/>
   <int value="-798200573" label="NotificationInteractionHistory:disabled"/>
@@ -62150,6 +62166,7 @@
   <int value="-460062351" label="VariationsGoogleGroupFiltering:disabled"/>
   <int value="-459469509" label="SidePanelImprovedClobbering:enabled"/>
   <int value="-459318667" label="AccessiblePDFForm:enabled"/>
+  <int value="-458726025" label="WebApkInstallFailureNotification:disabled"/>
   <int value="-458406186" label="UserBypassUI:disabled"/>
   <int value="-458308082" label="ContextualTriggersSelectionMenu:disabled"/>
   <int value="-458090289" label="CrOSLateBootMissiveStorage:disabled"/>
@@ -62275,6 +62292,7 @@
   <int value="-385461103" label="XsurfaceMetricsReporting:enabled"/>
   <int value="-385337473" label="enable-fast-unload"/>
   <int value="-384589459" label="disable-supervised-user-safesites"/>
+  <int value="-382542238" label="CWSInfoFastCheck:disabled"/>
   <int value="-381181808" label="DragAppsInTabletMode:enabled"/>
   <int value="-380992110"
       label="AutofillEnableSaveCardInfoBarAccountIndicationFooter:enabled"/>
@@ -62458,6 +62476,7 @@
   <int value="-290802952" label="OmniboxOnDeviceTailModel:enabled"/>
   <int value="-290802690" label="PartialSplit:enabled"/>
   <int value="-290672626" label="enable-asm-wasm"/>
+  <int value="-290533453" label="SafetyCheckExtensions:enabled"/>
   <int value="-290329565" label="CrosVmCupsProxy:disabled"/>
   <int value="-288316828" label="enable-delegated-renderer"/>
   <int value="-287089194" label="PrintManagementSetupAssistance:enabled"/>
@@ -62765,6 +62784,7 @@
   <int value="-128289378" label="EnableFamilyInfoFeedback:disabled"/>
   <int value="-127666141" label="TabGroups:disabled"/>
   <int value="-127231994" label="VrBrowsingNativeAndroidUi:disabled"/>
+  <int value="-126945286" label="WebApkInstallFailureRetry:enabled"/>
   <int value="-125941743" label="EnableLogControllerForDiagnosticsApp:enabled"/>
   <int value="-124604747" label="WebViewRestrictSensitiveContent:disabled"/>
   <int value="-123599761" label="IncludeIpcOverheadInNavigationStart:disabled"/>
@@ -63703,6 +63723,7 @@
   <int value="355970257" label="MediaRouterOTRInstance:disabled"/>
   <int value="357138275" label="enable-floating-virtual-keyboard:disabled"/>
   <int value="357165937" label="ShareToGoogleCollections:enabled"/>
+  <int value="357832577" label="OmniboxSuggestionHoverFillShape:disabled"/>
   <int value="358399482" label="enable-high-dpi-fixed-position-compositing"/>
   <int value="358493847" label="BackgroundLoader:disabled"/>
   <int value="358906344" label="NotificationsRefresh:disabled"/>
@@ -64463,6 +64484,7 @@
   <int value="752939691" label="disable-tab-for-desktop-share"/>
   <int value="756897997" label="PaintHoldingCrossOrigin:disabled"/>
   <int value="757645375" label="AndroidDarkSearch:disabled"/>
+  <int value="758299873" label="OmniboxSuggestionHoverFillShape:enabled"/>
   <int value="759296601" label="DiscountConsentV2:disabled"/>
   <int value="760542355" label="ServiceWorkerScriptFullCodeCache:enabled"/>
   <int value="761770770"
@@ -65209,6 +65231,7 @@
   <int value="1165828757" label="kAutofillEnableOffersInDownstream:disabled"/>
   <int value="1166169237" label="disable-delay-agnostic-aec"/>
   <int value="1166789664" label="SyncPseudoUSSAppList:disabled"/>
+  <int value="1166883356" label="CWSInfoFastCheck:enabled"/>
   <int value="1166897718" label="TextSuggestionsTouchBar:disabled"/>
   <int value="1167350114" label="AndroidNightMode:enabled"/>
   <int value="1167613030" label="enable-permission-action-reporting"/>
@@ -66329,6 +66352,7 @@
   <int value="1758262950"
       label="OmniboxUIExperimentVerticalMarginLimitToNonTouchOnly:disabled"/>
   <int value="1758688616" label="OmniboxModernizeVisualUpdate:disabled"/>
+  <int value="1759193892" label="SafetyCheckExtensions:disabled"/>
   <int value="1759323272" label="DefaultCalculatorWebApp:disabled"/>
   <int value="1760547768" label="BackGestureRefactorActivityAndroid:disabled"/>
   <int value="1760946944" label="MacViewsAutofillPopup:disabled"/>
@@ -67086,6 +67110,7 @@
   <int value="2129814401" label="SyncTrustedVaultPassphrasePromo:enabled"/>
   <int value="2129929643" label="enable-use-zoom-for-dsf"/>
   <int value="2130479056" label="CCTResizableSideSheet:disabled"/>
+  <int value="2132018256" label="ThumbnailPlaceholder:disabled"/>
   <int value="2132335798" label="EcheSWA:disabled"/>
   <int value="2132595171" label="OmniboxSearchEngineLogo:enabled"/>
   <int value="2133594095" label="CryptAuthV2DeviceActivityStatus:disabled"/>
@@ -75410,6 +75435,7 @@
   <int value="169" label="Hotspot"/>
   <int value="170" label="Geolocation Switch"/>
   <int value="171" label="Multi Capture On Login"/>
+  <int value="172" label="Floating Workspace"/>
 </enum>
 
 <enum name="NotificationDatabaseStatus">
@@ -96573,7 +96599,7 @@
 <enum name="SigninAccessPoint">
   <int value="0" label="Start page"/>
   <int value="1" label="NTP Link"/>
-  <int value="2" label="Menu (deprecated)"/>
+  <int value="2" label="Menu"/>
   <int value="3" label="Settings"/>
   <int value="4" label="Supervised user"/>
   <int value="5" label="Extension install bubble"/>
@@ -97917,9 +97943,6 @@
 </enum>
 
 <enum name="SmartLockAuthMethodChoice">
-  <obsolete>
-    Deprecated on 05/2023.
-  </obsolete>
   <int value="0" label="Smart Lock"/>
   <int value="1" label="Other"/>
 </enum>
@@ -115124,6 +115147,10 @@
   <int value="65" label="Customize your Chrome"/>
   <int value="66" label="Close this profile"/>
   <int value="67" label="Manage your Google account"/>
+  <int value="68"
+      label="Show reauth sign in prompt to user who has sync paused"/>
+  <int value="69" label="Show sync settings page"/>
+  <int value="70" label="Show turn on sync prompt"/>
 </enum>
 
 <enum name="WrongConfigurationMetric">
diff --git a/tools/metrics/histograms/metadata/blink/histograms.xml b/tools/metrics/histograms/metadata/blink/histograms.xml
index 2a1d1b4..efa16c64 100644
--- a/tools/metrics/histograms/metadata/blink/histograms.xml
+++ b/tools/metrics/histograms/metadata/blink/histograms.xml
@@ -1120,6 +1120,27 @@
   </summary>
 </histogram>
 
+<histogram name="Blink.FedCm.IdpSigninStatus.MismatchDialogResult"
+    enum="FedCmMismatchDialogResult" expires_after="M125">
+  <owner>tanzachary@chromium.org</owner>
+  <owner>web-identity-eng@google.com</owner>
+  <summary>
+    Records the outcome of the mismatch dialog. Recorded once per the mismatch
+    dialog being displayed.
+  </summary>
+</histogram>
+
+<histogram name="Blink.FedCm.IdpSigninStatus.ShowPopupWindowResult"
+    enum="FedCmShowPopupWindowResult" expires_after="M125">
+  <owner>tanzachary@chromium.org</owner>
+  <owner>web-identity-eng@google.com</owner>
+  <summary>
+    Records the outcome of attempting to show a pop-up window, triggered by the
+    user clicking on the &quot;Continue&quot; button on the IDP sign-in status
+    mismatch dialog. Recorded once per click of the &quot;Continue&quot; button.
+  </summary>
+</histogram>
+
 <histogram name="Blink.FedCm.IdpSignoutRequestInitiatedByUser"
     enum="BooleanYesNo" expires_after="2023-11-12">
   <owner>cbiesinger@chromium.org</owner>
diff --git a/tools/metrics/histograms/metadata/compositing/histograms.xml b/tools/metrics/histograms/metadata/compositing/histograms.xml
index ba65b7c..57086b71 100644
--- a/tools/metrics/histograms/metadata/compositing/histograms.xml
+++ b/tools/metrics/histograms/metadata/compositing/histograms.xml
@@ -855,6 +855,22 @@
   </summary>
 </histogram>
 
+<histogram name="Compositing.SurfaceAggregator.Has{Type}PerFrame"
+    enum="Boolean" expires_after="2023-11-01">
+  <owner>magchen@chromium.org</owner>
+  <owner>graphics-dev@chromium.org</owner>
+  <summary>
+    Record whether a frame has a specific type of render pass. This is logged
+    once per frame.
+  </summary>
+  <token key="Type">
+    <variant name="CopyRequests"/>
+    <variant name="PixelMovingBackdropFilters"/>
+    <variant name="PixelMovingFilters"/>
+    <variant name="UnembeddedRenderPasses"/>
+  </token>
+</histogram>
+
 <histogram name="Compositing.SurfaceAggregator.PrewalkedSurfaceCount"
     units="surfaces" expires_after="2023-08-20">
   <owner>kylechar@chromium.org</owner>
@@ -1379,22 +1395,6 @@
   <token key="Sequence" variants="SmoothnessSequence"/>
 </histogram>
 
-<histogram
-    name="Graphics.Smoothness.PerSession.MaxPercentDroppedFrames_1sWindow"
-    units="%" expires_after="2023-06-11">
-  <owner>jonross@chromium.org</owner>
-  <owner>graphics-dev@chromium.org</owner>
-  <summary>
-    Tracks the Max of dropped frames percent of a sliding window of 1 second.
-    The metric is reported once per page-load when the page closes.
-
-    PercentDroppedFrames is measured by tracking the number of frames which were
-    not displayed on screen out of the total number of frames expected to be
-    produced and displayed. In other words, the lower this number is, the
-    smoother experience.
-  </summary>
-</histogram>
-
 <histogram name="Graphics.Smoothness.Stale" units="ms"
     expires_after="2023-11-12">
   <owner>jonross@chromium.org</owner>
diff --git a/tools/metrics/histograms/metadata/content/histograms.xml b/tools/metrics/histograms/metadata/content/histograms.xml
index 01a2080..3720dd9 100644
--- a/tools/metrics/histograms/metadata/content/histograms.xml
+++ b/tools/metrics/histograms/metadata/content/histograms.xml
@@ -1967,8 +1967,7 @@
 
 <histogram
     name="ContentSuggestions.{FeedType}.LoadMoreTrigger.OffsetFromEndOfStream"
-    units="cards" expires_after="2023-07-23">
-  <owner>rogerm@chromium.org</owner>
+    units="cards" expires_after="2024-01-31">
   <owner>dewittj@chromium.org</owner>
   <owner>feed@chromium.org</owner>
   <summary>
@@ -1983,8 +1982,7 @@
 </histogram>
 
 <histogram name="ContentSuggestions.{FeedType}.LoadMoreTrigger.TotalCards"
-    units="cards" expires_after="2023-07-23">
-  <owner>rogerm@chromium.org</owner>
+    units="cards" expires_after="2024-01-31">
   <owner>dewittj@chromium.org</owner>
   <owner>feed@chromium.org</owner>
   <summary>
diff --git a/tools/metrics/histograms/metadata/cross_device/histograms.xml b/tools/metrics/histograms/metadata/cross_device/histograms.xml
index 396ba3b8..0773da72 100644
--- a/tools/metrics/histograms/metadata/cross_device/histograms.xml
+++ b/tools/metrics/histograms/metadata/cross_device/histograms.xml
@@ -2116,9 +2116,6 @@
 
 <histogram name="SmartLock.AuthMethodChoice.Unlock"
     enum="SmartLockAuthMethodChoice" expires_after="2023-12-31">
-  <obsolete>
-    SmartLock.AuthResult.* is sufficient to determine engaged user count.
-  </obsolete>
   <owner>hansberry@chromium.org</owner>
   <owner>chromeos-cross-device-eng@google.com</owner>
   <summary>Records the user's unlock method choice.</summary>
diff --git a/tools/metrics/histograms/metadata/installer/histograms.xml b/tools/metrics/histograms/metadata/installer/histograms.xml
index b413aff..f129202 100644
--- a/tools/metrics/histograms/metadata/installer/histograms.xml
+++ b/tools/metrics/histograms/metadata/installer/histograms.xml
@@ -104,6 +104,16 @@
   </summary>
 </histogram>
 
+<histogram name="Installer.Postinstall.ESPMountRetries" units="retries"
+    expires_after="2024-01-20">
+  <owner>khionu@google.com</owner>
+  <owner>chromeos-flex-eng@google.com</owner>
+  <summary>
+    How many retries were necessary when attempting to mount the boot Partition
+    after partition updates. Reported once during postinstall. ChromeOS only.
+  </summary>
+</histogram>
+
 <histogram name="Installer.Postinstall.ManagedEfiBootEntryCount"
     units="entries" expires_after="2023-12-10">
   <owner>tbrandston@google.com</owner>
diff --git a/tools/metrics/histograms/metadata/others/histograms.xml b/tools/metrics/histograms/metadata/others/histograms.xml
index cb0c26bb..7c303de9 100644
--- a/tools/metrics/histograms/metadata/others/histograms.xml
+++ b/tools/metrics/histograms/metadata/others/histograms.xml
@@ -14893,6 +14893,9 @@
     <variant name="ShowDownloads"/>
     <variant name="ShowHistory"/>
     <variant name="ShowKaleidoscope"/>
+    <variant name="ShowSigninWhenPaused"/>
+    <variant name="ShowSyncSettings"/>
+    <variant name="ShowTurnOnSync"/>
     <variant name="SiteSettings"/>
     <variant name="TaskManager"/>
     <variant name="ViewSource"/>
diff --git a/tools/metrics/histograms/metadata/power/histograms.xml b/tools/metrics/histograms/metadata/power/histograms.xml
index 0916b00..f88219c4 100644
--- a/tools/metrics/histograms/metadata/power/histograms.xml
+++ b/tools/metrics/histograms/metadata/power/histograms.xml
@@ -1069,10 +1069,11 @@
 </histogram>
 
 <histogram name="Power.BatteryDischargeRateWhileHibernated" units="mW"
-    expires_after="2023-06-15">
+    expires_after="2024-06-08">
   <owner>puthik@chromium.org</owner>
-  <owner>evgreen@chromium.org</owner>
+  <owner>mka@chromium.org</owner>
   <owner>chromeos-platform-power@google.com</owner>
+  <owner>chromeos-hibernate@google.com</owner>
   <summary>
     Chrome OS battery discharge rate in mW while the system was hibernated,
     sampled at resume. Only reported if the system was on battery power both
@@ -1830,10 +1831,11 @@
 </histogram>
 
 <histogram name="Power.HibernateAttemptsBeforeCancel" units="units"
-    expires_after="2023-06-15">
+    expires_after="2024-06-08">
   <owner>puthik@chromium.org</owner>
-  <owner>evgreen@chromium.org</owner>
+  <owner>mka@chromium.org</owner>
   <owner>chromeos-platform-power@google.com</owner>
+  <owner>chromeos-hibernate@google.com</owner>
   <summary>
     The number of hibernate attempts performed for a single hibernate request
     (e.g. triggered by transition from suspend to hibernation) that was
@@ -1845,10 +1847,11 @@
 </histogram>
 
 <histogram name="Power.HibernateAttemptsBeforeSuccess" units="units"
-    expires_after="2023-06-15">
+    expires_after="2024-06-08">
   <owner>puthik@chromium.org</owner>
-  <owner>evgreen@chromium.org</owner>
+  <owner>mka@chromium.org</owner>
   <owner>chromeos-platform-power@google.com</owner>
+  <owner>chromeos-hibernate@google.com</owner>
   <summary>
     The number of hibernation attempts performed for a single hibernation
     request (e.g. triggered by transition from suspend to hibernation) that
diff --git a/tools/perf/expectations.config b/tools/perf/expectations.config
index c29a5ba..574a6c5d 100644
--- a/tools/perf/expectations.config
+++ b/tools/perf/expectations.config
@@ -167,6 +167,7 @@
 crbug.com/1095610 [ win-laptop ] media.desktop/video.html?src=garden2_10s.mp4&seek [ Skip ]
 crbug.com/1211828 [ win ] media.desktop/video.html?src=tulip0.av1.mp4 [ Skip ]
 crbug.com/1277968 [ win-laptop ] media.desktop/video.html?src=garden2_10s.webm&seek [ Skip ]
+crbug.com/1452148 [ win-laptop ] media.desktop/video.html?src=boat_1080p60fps_vp9.webm [ Skip ]
 
 # Benchmark: media.mobile
 crbug.com/1202988 [ fuchsia-board-astro ] media.mobile/video.html?src=boat_1080p60fps_vp9.webm [ Skip ]
@@ -270,6 +271,7 @@
 crbug.com/1452148 [ android-pixel-2 ] rendering.mobile/mobile_news_sandbox [ Skip ]
 crbug.com/1452148 [ android-pixel-4 ] rendering.mobile/mobile_news_sandbox [ Skip ]
 crbug.com/1452148 [ android-pixel-6 ] rendering.mobile/mobile_news_sandbox [ Skip ]
+crbug.com/1452148 [ android-pixel-6-pro ] rendering.mobile/gpu_bound_shader.html [ Skip ]
 # Fuchsia does not support KEY_IDLE_POWER
 crbug.com/1269978 [ fuchsia ] rendering.mobile/idle_power_blank [ Skip ]
 crbug.com/1269978 [ fuchsia ] rendering.mobile/idle_power_animated_gif [ Skip ]
@@ -575,24 +577,7 @@
 crbug.com/903417 [ mac ] system_health.memory_desktop/long_running:tools:gmail-foreground [ Skip ]
 crbug.com/1216366 [ win-laptop ] system_health.memory_desktop/load:tools:drive:2019 [ Skip ]
 crbug.com/1362017 system_health.memory_desktop/browse:tech:discourse_infinite_scroll:2018 [ Skip ]
-crbug.com/1452148 [ win-laptop ] system_health.memory_desktop/browse:news:cnn:2021 [ Skip ]
-crbug.com/1452148 [ android-pixel-4 ] system_health.memory_mobile/browse:news:businessinsider:2021 [ Skip ]
-crbug.com/1452148 [ android-pixel-6 ] system_health.memory_mobile/browse:news:businessinsider:2021 [ Skip ]
-crbug.com/1452148 [ android-pixel-6-pro ] system_health.memory_mobile/browse:news:businessinsider:2021 [ Skip ]
-crbug.com/1452148 [ android-pixel-2 ] system_health.memory_mobile/browse:news:cnn:2021 [ Skip ]
-crbug.com/1452148 [ android-pixel-4 ] system_health.memory_mobile/browse:news:cnn:2021 [ Skip ]
-crbug.com/1452148 [ android-pixel-6 ] system_health.memory_mobile/browse:news:cnn:2021 [ Skip ]
-crbug.com/1452148 [ android-pixel-6-pro ] system_health.memory_mobile/browse:news:cnn:2021 [ Skip ]
-crbug.com/1452148 [ android-pixel-2 ] system_health.memory_mobile/browse:news:washingtonpost:2019 [ Skip ]
-crbug.com/1452148 [ android-pixel-6 ] system_health.memory_mobile/browse:news:washingtonpost:2019 [ Skip ]
-crbug.com/1452148 [ android-pixel-6-pro ] system_health.memory_mobile/browse:news:washingtonpost:2019 [ Skip ]
-crbug.com/1452148 [ android-pixel-4 ] system_health.memory_mobile/browse:shopping:amazon:2019 [ Skip ]
-crbug.com/1452148 [ android-pixel-6 ] system_health.memory_mobile/browse:shopping:amazon:2019 [ Skip ]
-crbug.com/1452148 [ android-pixel-6-pro ] system_health.memory_mobile/browse:shopping:amazon:2019 [ Skip ]
-crbug.com/1452148 [ android-pixel-6 ] system_health.memory_mobile/browse:social:twitter:2019 [ Skip ]
-crbug.com/1452148 [ android-pixel-6-pro ] system_health.memory_mobile/browse:social:twitter:2019 [ Skip ]
-crbug.com/1452148 [ android-pixel-6 ] system_health.memory_mobile/load:tools:dropbox:2019 [ Skip ]
-crbug.com/1452148 [ android-pixel-6-pro ] system_health.memory_mobile/load:tools:dropbox:2019 [ Skip ]
+crbug.com/1452148 [ win10 ] system_health.memory_desktop/browse:news:cnn:2021 [ Skip ]
 
 # Memory dumps don't work at the moment for WebAssembly apps.
 crbug.com/1057035 system_health.memory_desktop/browse:tools:autocad:2021 [ Skip ]
@@ -627,6 +612,23 @@
 crbug.com/1421060 [ android-pixel-4 ] system_health.memory_mobile/browse:news:globo:2019 [ Skip ]
 crbug.com/1421060 [ android-pixel-6 ] system_health.memory_mobile/browse:news:globo:2019 [ Skip ]
 crbug.com/1421060 [ android-pixel-6-pro ] system_health.memory_mobile/browse:news:globo:2019 [ Skip ]
+crbug.com/1452148 [ android-pixel-4 ] system_health.memory_mobile/browse:news:businessinsider:2021 [ Skip ]
+crbug.com/1452148 [ android-pixel-6 ] system_health.memory_mobile/browse:news:businessinsider:2021 [ Skip ]
+crbug.com/1452148 [ android-pixel-6-pro ] system_health.memory_mobile/browse:news:businessinsider:2021 [ Skip ]
+crbug.com/1452148 [ android-pixel-2 ] system_health.memory_mobile/browse:news:cnn:2021 [ Skip ]
+crbug.com/1452148 [ android-pixel-4 ] system_health.memory_mobile/browse:news:cnn:2021 [ Skip ]
+crbug.com/1452148 [ android-pixel-6 ] system_health.memory_mobile/browse:news:cnn:2021 [ Skip ]
+crbug.com/1452148 [ android-pixel-6-pro ] system_health.memory_mobile/browse:news:cnn:2021 [ Skip ]
+crbug.com/1452148 [ android-pixel-2 ] system_health.memory_mobile/browse:news:washingtonpost:2019 [ Skip ]
+crbug.com/1452148 [ android-pixel-6 ] system_health.memory_mobile/browse:news:washingtonpost:2019 [ Skip ]
+crbug.com/1452148 [ android-pixel-6-pro ] system_health.memory_mobile/browse:news:washingtonpost:2019 [ Skip ]
+crbug.com/1452148 [ android-pixel-4 ] system_health.memory_mobile/browse:shopping:amazon:2019 [ Skip ]
+crbug.com/1452148 [ android-pixel-6 ] system_health.memory_mobile/browse:shopping:amazon:2019 [ Skip ]
+crbug.com/1452148 [ android-pixel-6-pro ] system_health.memory_mobile/browse:shopping:amazon:2019 [ Skip ]
+crbug.com/1452148 [ android-pixel-6 ] system_health.memory_mobile/browse:social:twitter:2019 [ Skip ]
+crbug.com/1452148 [ android-pixel-6-pro ] system_health.memory_mobile/browse:social:twitter:2019 [ Skip ]
+crbug.com/1452148 [ android-pixel-6 ] system_health.memory_mobile/load:tools:dropbox:2019 [ Skip ]
+crbug.com/1452148 [ android-pixel-6-pro ] system_health.memory_mobile/load:tools:dropbox:2019 [ Skip ]
 
 # Benchmark: system_health.scroll_jank_mobile
 crbug.com/1362017 system_health.scroll_jank_mobile/browse:tech:discourse_infinite_scroll:2018 [ Skip ]
diff --git a/tools/traffic_annotation/safe_list.txt b/tools/traffic_annotation/safe_list.txt
index 64c6da08..512659f5 100644
--- a/tools/traffic_annotation/safe_list.txt
+++ b/tools/traffic_annotation/safe_list.txt
@@ -352,7 +352,6 @@
 missing_new_fields,chrome/browser/enterprise/connectors/device_trust/key_management/core/network/mojo_key_network_delegate.cc
 missing_new_fields,chrome/browser/safe_browsing/chrome_cleaner/chrome_cleaner_fetcher_win.cc
 missing_new_fields,chrome/browser/error_reporting/chrome_js_error_report_processor_nonchromeos.cc
-missing_new_fields,chrome/browser/search/background/ntp_background_service.cc
 missing_new_fields,chrome/browser/ui/sharing_hub/sharing_hub_bubble_controller_desktop_impl.cc
 missing_new_fields,chrome/browser/ui/webui/welcome/ntp_background_fetcher.cc
 missing_new_fields,components/live_caption/live_translate_controller.cc
diff --git a/tools/traffic_annotation/summary/annotations.xml b/tools/traffic_annotation/summary/annotations.xml
index a1076f8..d8080c87 100644
--- a/tools/traffic_annotation/summary/annotations.xml
+++ b/tools/traffic_annotation/summary/annotations.xml
@@ -20,9 +20,9 @@
  <item id="autofill_image_fetcher_card_art_image" added_in_milestone="93" content_hash_code="038b8696" os_list="linux,windows,chromeos,android" file_path="components/autofill/core/browser/ui/autofill_image_fetcher.cc" />
  <item id="autofill_query" added_in_milestone="62" content_hash_code="00ed7a4b" os_list="linux,windows,chromeos,android" file_path="components/autofill/core/browser/autofill_download_manager.cc" />
  <item id="autofill_upload" added_in_milestone="62" content_hash_code="0698270b" os_list="linux,windows,chromeos,android" file_path="components/autofill/core/browser/autofill_download_manager.cc" />
- <item id="backdrop_collection_images_download" added_in_milestone="68" content_hash_code="03c0de0e" os_list="linux,windows,chromeos" file_path="chrome/browser/search/background/ntp_background_service.cc" />
- <item id="backdrop_collection_names_download" added_in_milestone="68" content_hash_code="0592b787" os_list="linux,windows,chromeos" file_path="chrome/browser/search/background/ntp_background_service.cc" />
- <item id="backdrop_next_image_download" added_in_milestone="77" content_hash_code="05df91f2" os_list="linux,windows,chromeos" file_path="chrome/browser/search/background/ntp_background_service.cc" />
+ <item id="backdrop_collection_images_download" added_in_milestone="68" content_hash_code="009770fb" os_list="linux,windows,chromeos" file_path="chrome/browser/search/background/ntp_background_service.cc" />
+ <item id="backdrop_collection_names_download" added_in_milestone="68" content_hash_code="048c7a89" os_list="linux,windows,chromeos" file_path="chrome/browser/search/background/ntp_background_service.cc" />
+ <item id="backdrop_next_image_download" added_in_milestone="77" content_hash_code="003626bf" os_list="linux,windows,chromeos" file_path="chrome/browser/search/background/ntp_background_service.cc" />
  <item id="background_fetch_context" added_in_milestone="62" content_hash_code="031d0caa" os_list="linux,windows,chromeos,android" file_path="content/browser/background_fetch/background_fetch_delegate_proxy.cc" />
  <item id="bidirectional_stream" added_in_milestone="67" content_hash_code="07c03a44" os_list="linux,windows,chromeos,android" file_path="net/http/bidirectional_stream.cc" />
  <item id="blink_extension_resource_loader" added_in_milestone="63" content_hash_code="03c97c39" os_list="linux,windows,chromeos,android" file_path="third_party/blink/renderer/platform/loader/fetch/url_loader/url_loader.cc" />
@@ -422,4 +422,5 @@
  <item id="iwa_update_manifest_fetcher" added_in_milestone="116" type="completing" content_hash_code="06867096" os_list="chromeos" semantics_fields="4,5,7,8,9" policy_fields="-1" file_path="chrome/browser/web_applications/isolated_web_apps/update_manifest/update_manifest_fetcher.cc" />
  <item id="iwa_bundle_downloader" added_in_milestone="116" type="completing" content_hash_code="04d2beae" os_list="chromeos" semantics_fields="4,5,7,8,9" policy_fields="-1" file_path="chrome/browser/web_applications/isolated_web_apps/isolated_web_app_downloader.cc" />
  <item id="bidding_and_auction_server_key_fetch" added_in_milestone="116" content_hash_code="07667a22" os_list="linux,windows,android,chromeos" file_path="content/browser/interest_group/bidding_and_auction_server_key_fetcher.cc" />
+ <item id="backdrop_image_link_verification" added_in_milestone="116" content_hash_code="054ed1ab" os_list="linux,windows,chromeos" file_path="chrome/browser/search/background/ntp_background_service.cc" />
 </annotations>
diff --git a/tools/traffic_annotation/summary/grouping.xml b/tools/traffic_annotation/summary/grouping.xml
index 0e9895d7..fd77203 100644
--- a/tools/traffic_annotation/summary/grouping.xml
+++ b/tools/traffic_annotation/summary/grouping.xml
@@ -285,6 +285,7 @@
       <annotation id="crash_file_uploader"/>
       <annotation id="glanceables_classroom_integration"/>
       <annotation id="promise_app_service_download_icon"/>
+      <annotation id="backdrop_image_link_verification"/>
     </sender>
   </group>
   <group name="Admin Features" hidden="true">
diff --git a/tools/vscode/settings.json b/tools/vscode/settings.json
index aa40b2f..29c8f9ab 100644
--- a/tools/vscode/settings.json
+++ b/tools/vscode/settings.json
@@ -12,13 +12,15 @@
   // Add a line at 80 characters.
   "editor.rulers": [80],
 
-  // Except for Java where we add a line at 100 characters per that style guide.
+  // Except for Java where we add a line at 100 characters per that style guide
+  // and the tab width should be 4 spaces.
   // The chrome java style guide:
   // https://chromium.googlesource.com/chromium/src/+/main/styleguide/java/java.md
   // does not override this and defers to the android style guide:
-  // https://source.android.com/docs/setup/contribute/code-style#limit-line-length
+  // https://source.android.com/docs/setup/contribute/code-style
   "[java]": {
-    "editor.rulers": [100]
+    "editor.rulers": [100],
+    "editor.tabSize": 4
   },
 
   "extensions": {
diff --git a/ui/android/javatests/src/org/chromium/ui/test/util/MockitoHelper.java b/ui/android/javatests/src/org/chromium/ui/test/util/MockitoHelper.java
index 28a9c8a2..707dd7b 100644
--- a/ui/android/javatests/src/org/chromium/ui/test/util/MockitoHelper.java
+++ b/ui/android/javatests/src/org/chromium/ui/test/util/MockitoHelper.java
@@ -9,6 +9,8 @@
 import org.mockito.stubbing.Stubber;
 
 import org.chromium.base.Callback;
+import org.chromium.base.test.util.CriteriaHelper;
+import org.chromium.base.test.util.ScalableTimeout;
 
 /** Simplifies common interactions with Mockito. */
 public class MockitoHelper {
@@ -48,4 +50,11 @@
             return null;
         }));
     }
-}
\ No newline at end of file
+
+    /** Mockito.verify but with a timeout to reduce flakes. */
+    public static <T> T waitForEvent(T mock) {
+        return Mockito.verify(mock,
+                Mockito.timeout(
+                        ScalableTimeout.scaleTimeout(CriteriaHelper.DEFAULT_MAX_TIME_TO_POLL)));
+    }
+}
diff --git a/ui/base/ime/input_method.h b/ui/base/ime/input_method.h
index d526b984..f178e0c 100644
--- a/ui/base/ime/input_method.h
+++ b/ui/base/ime/input_method.h
@@ -86,6 +86,9 @@
   // Returns whether the system input locale is in CJK languages.
   // This is only used in Windows platforms.
   virtual bool IsInputLocaleCJK() const = 0;
+
+  // Called when a frame with a committed Url has received focus.
+  virtual void OnUrlChanged() {}
 #endif
 
   // Sets the text input client which receives text input events such as
diff --git a/ui/base/ime/text_input_client.h b/ui/base/ime/text_input_client.h
index b6ef235..9bb2ae3 100644
--- a/ui/base/ime/text_input_client.h
+++ b/ui/base/ime/text_input_client.h
@@ -338,6 +338,9 @@
   };
 
   virtual ui::TextInputClient::EditingContext GetTextEditingContext();
+
+  // Notifies accessibility when a frame with a committed Url receives focus.
+  virtual void OnFrameFocusChanged() {}
 #endif
 
   // Called before ui::InputMethod dispatches a not-consumed event to PostIME
diff --git a/ui/base/ime/win/input_method_win_tsf.cc b/ui/base/ime/win/input_method_win_tsf.cc
index a6bca9b..56edc931 100644
--- a/ui/base/ime/win/input_method_win_tsf.cc
+++ b/ui/base/ime/win/input_method_win_tsf.cc
@@ -179,4 +179,12 @@
     ui::TSFBridge::GetInstance()->ConfirmComposition();
 }
 
+void InputMethodWinTSF::OnUrlChanged() {
+  if (!ui::TSFBridge::GetInstance()) {
+    return;
+  }
+
+  ui::TSFBridge::GetInstance()->UrlChanged();
+}
+
 }  // namespace ui
diff --git a/ui/base/ime/win/input_method_win_tsf.h b/ui/base/ime/win/input_method_win_tsf.h
index b78fd2d2..c67ec04 100644
--- a/ui/base/ime/win/input_method_win_tsf.h
+++ b/ui/base/ime/win/input_method_win_tsf.h
@@ -38,6 +38,7 @@
   void OnInputLocaleChanged() override;
   bool IsInputLocaleCJK() const override;
   bool IsCandidatePopupOpen() const override;
+  void OnUrlChanged() override;
 
   // Overridden from InputMethodBase:
   void OnWillChangeFocusedClient(TextInputClient* focused_before,
diff --git a/ui/base/ime/win/mock_tsf_bridge.cc b/ui/base/ime/win/mock_tsf_bridge.cc
index 7e2de8bf..92d2f56b 100644
--- a/ui/base/ime/win/mock_tsf_bridge.cc
+++ b/ui/base/ime/win/mock_tsf_bridge.cc
@@ -65,6 +65,8 @@
   return false;
 }
 
+void MockTSFBridge::UrlChanged() {}
+
 void MockTSFBridge::Reset() {
   enable_ime_call_count_ = 0;
   disable_ime_call_count_ = 0;
diff --git a/ui/base/ime/win/mock_tsf_bridge.h b/ui/base/ime/win/mock_tsf_bridge.h
index dfab842..637775f 100644
--- a/ui/base/ime/win/mock_tsf_bridge.h
+++ b/ui/base/ime/win/mock_tsf_bridge.h
@@ -38,6 +38,7 @@
   Microsoft::WRL::ComPtr<ITfThreadMgr> GetThreadManager() override;
   TextInputClient* GetFocusedTextInputClient() const override;
   bool IsInputLanguageCJK() override;
+  void UrlChanged() override;
 
   // Resets MockTSFBridge state including function call counter.
   void Reset();
diff --git a/ui/base/ime/win/tsf_bridge.cc b/ui/base/ime/win/tsf_bridge.cc
index 5a345c5..8fbf975e 100644
--- a/ui/base/ime/win/tsf_bridge.cc
+++ b/ui/base/ime/win/tsf_bridge.cc
@@ -53,6 +53,7 @@
   bool IsInputLanguageCJK() override;
   Microsoft::WRL::ComPtr<ITfThreadMgr> GetThreadManager() override;
   TextInputClient* GetFocusedTextInputClient() const override;
+  void UrlChanged() override;
 
  private:
   // Returns S_OK if |tsf_document_map_| is successfully initialized. This
@@ -154,6 +155,9 @@
 
   // Represents the window that is currently owns text input focus.
   HWND attached_window_handle_ = nullptr;
+
+  // Tracks Windows OS support for empty TSF text stores.
+  bool empty_TSF_support_ = false;
 };
 
 TSFBridgeImpl::TSFBridgeImpl() = default;
@@ -395,6 +399,14 @@
   return client_;
 }
 
+void TSFBridgeImpl::UrlChanged() {
+  TSFDocument* document = GetAssociatedDocument();
+  if (!document || !document->text_store) {
+    return;
+  }
+  document->text_store->MaybeSendOnUrlChanged();
+}
+
 Microsoft::WRL::ComPtr<ITfThreadMgr> TSFBridgeImpl::GetThreadManager() {
   DCHECK(base::CurrentUIThread::IsSet());
   DCHECK(IsInitialized());
@@ -495,6 +507,17 @@
       TEXT_INPUT_TYPE_TELEPHONE, TEXT_INPUT_TYPE_URL,
       TEXT_INPUT_TYPE_TEXT_AREA,
   };
+  // Query TSF for empty TSF text store support, introduced with Windows 11.
+  // If support is present, as indicated by successful return of an interface
+  // for the IID value GUID_COMPARTMENT_EMPTYCONTEXT, we use a dummy/empty Text
+  // store when there is no text.
+  Microsoft::WRL::ComPtr<IUnknown> flag_empty_context;
+  HRESULT res = thread_manager_->QueryInterface(GUID_COMPARTMENT_EMPTYCONTEXT,
+                                                &flag_empty_context);
+  if (SUCCEEDED(res)) {
+    empty_TSF_support_ = true;
+  }
+
   for (size_t i = 0; i < std::size(kTextInputTypes); ++i) {
     const TextInputType input_type = kTextInputTypes[i];
     Microsoft::WRL::ComPtr<ITfContext> context;
@@ -502,7 +525,9 @@
     DWORD source_cookie = TF_INVALID_COOKIE;
     DWORD key_trace_sink_cookie = TF_INVALID_COOKIE;
     DWORD language_profile_cookie = TF_INVALID_COOKIE;
-    const bool use_null_text_store = (input_type == TEXT_INPUT_TYPE_NONE);
+    // Use a null text store if empty tsf text store is not supported.
+    const bool use_null_text_store =
+        (input_type == TEXT_INPUT_TYPE_NONE && !empty_TSF_support_);
     DWORD* source_cookie_ptr = use_null_text_store ? nullptr : &source_cookie;
     DWORD* key_trace_sink_cookie_ptr =
         use_null_text_store ? nullptr : &key_trace_sink_cookie;
@@ -510,7 +535,8 @@
         use_null_text_store ? nullptr : &language_profile_cookie;
     scoped_refptr<TSFTextStore> text_store =
         use_null_text_store ? nullptr : new TSFTextStore();
-    if (text_store) {
+    if (text_store && input_type != TEXT_INPUT_TYPE_NONE) {
+      // No need to initialize for TEXT_INPUT_TYPE_NONE.
       HRESULT hr = text_store->Initialize();
       if (FAILED(hr))
         return hr;
@@ -520,7 +546,10 @@
         key_trace_sink_cookie_ptr, language_profile_cookie_ptr);
     if (FAILED(hr))
       return hr;
-    if (input_type == TEXT_INPUT_TYPE_PASSWORD) {
+    if (input_type == TEXT_INPUT_TYPE_PASSWORD ||
+        (empty_TSF_support_ && input_type == TEXT_INPUT_TYPE_NONE)) {
+      // Disable context for TEXT_INPUT_TYPE_NONE, if empty text store is
+      // supported.
       hr = InitializeDisabledContext(context.Get());
       if (FAILED(hr))
         return hr;
@@ -531,8 +560,10 @@
     tsf_document_map_[input_type].key_trace_sink_cookie = key_trace_sink_cookie;
     tsf_document_map_[input_type].language_profile_cookie =
         language_profile_cookie;
-    if (text_store)
+    if (text_store) {
       text_store->OnContextInitialized(context.Get());
+      text_store->SetEmptyTextStoreSupport(empty_TSF_support_);
+    }
   }
   return S_OK;
 }
diff --git a/ui/base/ime/win/tsf_bridge.h b/ui/base/ime/win/tsf_bridge.h
index 07017e7..b822c16 100644
--- a/ui/base/ime/win/tsf_bridge.h
+++ b/ui/base/ime/win/tsf_bridge.h
@@ -101,6 +101,9 @@
   // Returns the focused text input client.
   virtual TextInputClient* GetFocusedTextInputClient() const = 0;
 
+  // Notify TSF when a frame with a committed Url has been focused.
+  virtual void UrlChanged() = 0;
+
  protected:
   // Uses GetInstance() instead.
   TSFBridge() = default;
diff --git a/ui/base/ime/win/tsf_text_store.cc b/ui/base/ime/win/tsf_text_store.cc
index 92fec08..7aff119d 100644
--- a/ui/base/ime/win/tsf_text_store.cc
+++ b/ui/base/ime/win/tsf_text_store.cc
@@ -266,6 +266,11 @@
   // TODO(IME): Remove TS_SS_TRANSITORY to support Korean reconversion
   status->dwStaticFlags = TS_SS_TRANSITORY | TS_SS_NOHIDDENTEXT;
 
+  // No text support is needed for empty text store.
+  if (text_input_client_ && is_empty_text_store_supported_ &&
+      text_input_client_->GetTextInputType() == TEXT_INPUT_TYPE_NONE) {
+    status->dwDynamicFlags |= TS_SD_READONLY;
+  }
   return S_OK;
 }
 
@@ -574,6 +579,13 @@
 HRESULT TSFTextStore::RequestLock(DWORD lock_flags, HRESULT* result) {
   if (!text_input_client_)
     return E_UNEXPECTED;
+  // No lock is necessary for an empty text store. This is to deny lock to an
+  // unsuspecting TSF in the wild that always assumed a text update with a
+  // store.
+  if (is_empty_text_store_supported_ && text_input_client_ &&
+      text_input_client_->GetTextInputType() == TEXT_INPUT_TYPE_NONE) {
+    return E_FAIL;
+  }
 
   if (!text_store_acp_sink_.Get())
     return E_FAIL;
@@ -1654,4 +1666,23 @@
   return result;
 }
 
+void ui::TSFTextStore::SetEmptyTextStoreSupport(bool isEnabled) {
+  is_empty_text_store_supported_ = isEnabled;
+}
+
+bool TSFTextStore::MaybeSendOnUrlChanged() {
+  // When the user interacts with a traditional editing control, TSF will query
+  // for the current Url as needed. However, when TSF supports empty stores, we
+  // will also notify the OS when a frame with a committed Url is focused, to
+  // enable scenarios where, for example, a page implements its own controls in
+  // JavaScript (crbug.com/1447061).
+  if (!is_empty_text_store_supported_) {
+    return false;
+  }
+  TS_ATTRID attrs[1];
+  attrs[0] = GUID_PROP_URL;
+  text_store_acp_sink_->OnAttrsChange(NULL, NULL, 1, attrs);
+  return true;
+}
+
 }  // namespace ui
diff --git a/ui/base/ime/win/tsf_text_store.h b/ui/base/ime/win/tsf_text_store.h
index 56a63e1..5273113 100644
--- a/ui/base/ime/win/tsf_text_store.h
+++ b/ui/base/ime/win/tsf_text_store.h
@@ -269,6 +269,13 @@
   // Sends OnLayoutChange() via |text_store_acp_sink_|.
   void SendOnLayoutChange();
 
+  // Sends a Url change notification via |text_store_acp_sink_| if the current
+  // version of TSF supports empty text stores.
+  bool MaybeSendOnUrlChanged();
+
+  // Sets the flag to indicate TSF support for empty text stores.
+  void SetEmptyTextStoreSupport(bool isEnabled);
+
  private:
   friend class TSFTextStoreTest;
   friend class TSFTextStoreTestCallback;
@@ -328,6 +335,10 @@
   // composed text.
   void GetStyle(const TF_DISPLAYATTRIBUTE& attribute, ImeTextSpan* span);
 
+  // Indicates if the operating system's version of TSF supports empty text
+  // stores.
+  bool is_empty_text_store_supported_ = false;
+
   // The reference count of this instance.
   volatile LONG ref_count_ = 0;
 
diff --git a/ui/base/ime/win/tsf_text_store_unittest.cc b/ui/base/ime/win/tsf_text_store_unittest.cc
index 511a7e7..beb4bc90 100644
--- a/ui/base/ime/win/tsf_text_store_unittest.cc
+++ b/ui/base/ime/win/tsf_text_store_unittest.cc
@@ -432,6 +432,18 @@
   EXPECT_EQ((ULONG)TS_SD_INPUTPANEMANUALDISPLAYENABLE, status.dwDynamicFlags);
   EXPECT_EQ((ULONG)(TS_SS_TRANSITORY | TS_SS_NOHIDDENTEXT),
             status.dwStaticFlags);
+
+  text_store_->SetEmptyTextStoreSupport(true);
+  status = {};
+  EXPECT_CALL(text_input_client_, GetTextInputType())
+      .WillRepeatedly(Return(TEXT_INPUT_TYPE_NONE));
+  EXPECT_EQ(S_OK, text_store_->GetStatus(&status));
+  EXPECT_EQ((ULONG)TS_SD_READONLY, status.dwDynamicFlags & TS_SD_READONLY);
+  status = {};
+  EXPECT_CALL(text_input_client_, GetTextInputType())
+      .WillRepeatedly(Return(TEXT_INPUT_TYPE_TEXT));
+  EXPECT_EQ(S_OK, text_store_->GetStatus(&status));
+  EXPECT_EQ((ULONG)0, status.dwDynamicFlags & TS_SD_READONLY);
 }
 
 TEST_F(TSFTextStoreTest, QueryInsertTest) {
@@ -1635,6 +1647,14 @@
   }
 }
 
+TEST_F(TSFTextStoreTest, SendOnUrlChanged) {
+  text_store_->SetEmptyTextStoreSupport(true);
+  EXPECT_TRUE(text_store_->MaybeSendOnUrlChanged());
+
+  text_store_->SetEmptyTextStoreSupport(false);
+  EXPECT_FALSE(text_store_->MaybeSendOnUrlChanged());
+}
+
 class KeyEventTestCallback : public TSFTextStoreTestCallback {
  public:
   explicit KeyEventTestCallback(TSFTextStore* text_store)
diff --git a/ui/color/color_id.h b/ui/color/color_id.h
index 713dacb..0a9045d 100644
--- a/ui/color/color_id.h
+++ b/ui/color/color_id.h
@@ -245,6 +245,7 @@
   E_CPONLY(kColorBubbleBorderWhenShadowPresent) \
   E_CPONLY(kColorBubbleFooterBackground) \
   E_CPONLY(kColorBubbleFooterBorder) \
+  E_CPONLY(kColorButtonFeatureAttentionHighlight) \
   E_CPONLY(kColorButtonBackground) \
   E_CPONLY(kColorButtonBackgroundPressed) \
   E_CPONLY(kColorButtonBackgroundProminent) \
@@ -253,6 +254,7 @@
   E_CPONLY(kColorButtonBackgroundTonal) \
   E_CPONLY(kColorButtonBackgroundTonalDisabled) \
   E_CPONLY(kColorButtonBackgroundTonalFocused) \
+  E_CPONLY(kColorButtonBackgroundWithAttention) \
   E_CPONLY(kColorButtonBorder) \
   E_CPONLY(kColorButtonBorderDisabled) \
   E_CPONLY(kColorButtonForeground) \
diff --git a/ui/color/sys_color_mixer.cc b/ui/color/sys_color_mixer.cc
index 2b882cb..8555722 100644
--- a/ui/color/sys_color_mixer.cc
+++ b/ui/color/sys_color_mixer.cc
@@ -22,21 +22,52 @@
   const bool dark_mode =
       key.color_mode == ColorProviderManager::ColorMode::kDark;
 
+  // Surfaces.
+  mixer[kColorSysSurface] = {dark_mode ? kColorRefNeutral10
+                                       : kColorRefNeutral99};
+  mixer[kColorSysSurface1] =
+      dark_mode ? GetResultingPaintColor(SetAlpha({kColorRefPrimary80}, 0x0C),
+                                         {kColorRefNeutral10})
+                : GetResultingPaintColor(SetAlpha({kColorRefPrimary40}, 0x0C),
+                                         {kColorRefNeutral99});
+  mixer[kColorSysSurface2] =
+      dark_mode ? GetResultingPaintColor(SetAlpha({kColorRefPrimary80}, 0x14),
+                                         {kColorRefNeutral10})
+                : GetResultingPaintColor(SetAlpha({kColorRefPrimary40}, 0x14),
+                                         {kColorRefNeutral99});
+  mixer[kColorSysSurface3] =
+      dark_mode ? GetResultingPaintColor(SetAlpha({kColorRefPrimary80}, 0x1C),
+                                         {kColorRefNeutral10})
+                : GetResultingPaintColor(SetAlpha({kColorRefPrimary40}, 0x1C),
+                                         {kColorRefNeutral99});
+  mixer[kColorSysSurface4] =
+      dark_mode ? GetResultingPaintColor(SetAlpha({kColorRefPrimary80}, 0x1E),
+                                         {kColorRefNeutral10})
+                : GetResultingPaintColor(SetAlpha({kColorRefPrimary40}, 0x1E),
+                                         {kColorRefNeutral99});
+  mixer[kColorSysSurface5] =
+      dark_mode ? GetResultingPaintColor(SetAlpha({kColorRefPrimary80}, 0x23),
+                                         {kColorRefNeutral10})
+                : GetResultingPaintColor(SetAlpha({kColorRefPrimary40}, 0x23),
+                                         {kColorRefNeutral99});
+
   // General.
   mixer[kColorSysOnSurfaceSecondary] = {dark_mode ? kColorRefSecondary80
                                                   : kColorRefSecondary30};
+  mixer[kColorSysTonalContainer] = {dark_mode ? kColorRefPrimary30
+                                              : kColorRefPrimary90};
   mixer[kColorSysNeutralContainer] = {dark_mode ? kColorRefNeutralVariant15
-                                                : kColorSysSurface1};
+                                                : kColorRefNeutral94};
   mixer[kColorSysDivider] = {dark_mode ? kColorRefSecondary25
                                        : kColorRefPrimary90};
 
   // Chrome surfaces.
   mixer[kColorSysBase] = {dark_mode ? kColorRefSecondary25
-                                    : kColorRefNeutral99};
+                                    : kColorRefNeutral98};
   mixer[kColorSysBaseContainer] = {dark_mode ? kColorRefSecondary15
                                              : kColorSysSurface4};
   mixer[kColorSysBaseContainerElevated] = {dark_mode ? kColorRefSecondary25
-                                                     : kColorRefNeutral99};
+                                                     : kColorRefNeutral98};
   mixer[kColorSysOnBaseDivider] = {dark_mode ? kColorRefSecondary35
                                              : kColorRefPrimary90};
 
diff --git a/ui/color/ui_color_mixer.cc b/ui/color/ui_color_mixer.cc
index 22fda2a8..5f73cfc 100644
--- a/ui/color/ui_color_mixer.cc
+++ b/ui/color/ui_color_mixer.cc
@@ -52,8 +52,13 @@
   mixer[kColorButtonBackgroundTonalDisabled] = {
       kColorSysStateDisabledContainer};
   mixer[kColorButtonBackgroundTonalFocused] = {kColorButtonBackgroundTonal};
+  mixer[kColorButtonBackgroundWithAttention] = {
+      dark_mode ? SkColorSetRGB(0x35, 0x36, 0x3A) : SK_ColorWHITE};
   mixer[kColorButtonBorder] = {kColorMidground};
   mixer[kColorButtonBorderDisabled] = {kColorSubtleEmphasisBackground};
+  mixer[kColorButtonFeatureAttentionHighlight] =
+      ui::PickGoogleColor(ui::kColorAccent, kColorButtonBackgroundWithAttention,
+                          color_utils::kMinimumVisibleContrastRatio);
   mixer[kColorButtonForeground] =
       PickGoogleColor(kColorAccent, kColorButtonBackground,
                       color_utils::kMinimumReadableContrastRatio);
diff --git a/ui/ozone/platform/flatland/BUILD.gn b/ui/ozone/platform/flatland/BUILD.gn
index 8750b5a..07c5bee2 100644
--- a/ui/ozone/platform/flatland/BUILD.gn
+++ b/ui/ozone/platform/flatland/BUILD.gn
@@ -66,7 +66,6 @@
     "//ui/base",
     "//ui/base/cursor",
     "//ui/base/ime/fuchsia",
-    "//ui/display/fake",
     "//ui/events:dom_keycode_converter",
     "//ui/events/ozone/layout",
     "//ui/ozone:ozone_base",
diff --git a/ui/ozone/platform/flatland/ozone_platform_flatland.cc b/ui/ozone/platform/flatland/ozone_platform_flatland.cc
index 51fdcdb..aa37e16 100644
--- a/ui/ozone/platform/flatland/ozone_platform_flatland.cc
+++ b/ui/ozone/platform/flatland/ozone_platform_flatland.cc
@@ -21,7 +21,6 @@
 #include "mojo/public/cpp/bindings/pending_remote.h"
 #include "ui/base/cursor/cursor_factory.h"
 #include "ui/base/ime/fuchsia/input_method_fuchsia.h"
-#include "ui/display/fake/fake_display_delegate.h"
 #include "ui/events/ozone/layout/keyboard_layout_engine_manager.h"
 #include "ui/events/ozone/layout/stub/stub_keyboard_layout_engine.h"
 #include "ui/events/platform/platform_event_source.h"
@@ -133,7 +132,7 @@
   std::unique_ptr<display::NativeDisplayDelegate> CreateNativeDisplayDelegate()
       override {
     NOTIMPLEMENTED();
-    return std::make_unique<display::FakeDisplayDelegate>();
+    return nullptr;
   }
 
   std::unique_ptr<PlatformScreen> CreateScreen() override {
diff --git a/ui/ozone/platform/scenic/BUILD.gn b/ui/ozone/platform/scenic/BUILD.gn
index 29e29e87..0e24d84 100644
--- a/ui/ozone/platform/scenic/BUILD.gn
+++ b/ui/ozone/platform/scenic/BUILD.gn
@@ -74,7 +74,6 @@
     "//ui/base",
     "//ui/base/cursor",
     "//ui/base/ime/fuchsia",
-    "//ui/display/fake",
     "//ui/events:dom_keycode_converter",
     "//ui/events/ozone/layout",
     "//ui/ozone:ozone_base",
diff --git a/ui/ozone/platform/scenic/ozone_platform_scenic.cc b/ui/ozone/platform/scenic/ozone_platform_scenic.cc
index cab86ec..0bc5a272 100644
--- a/ui/ozone/platform/scenic/ozone_platform_scenic.cc
+++ b/ui/ozone/platform/scenic/ozone_platform_scenic.cc
@@ -24,7 +24,6 @@
 #include "mojo/public/cpp/bindings/pending_remote.h"
 #include "ui/base/cursor/cursor_factory.h"
 #include "ui/base/ime/fuchsia/input_method_fuchsia.h"
-#include "ui/display/fake/fake_display_delegate.h"
 #include "ui/events/ozone/layout/keyboard_layout_engine_manager.h"
 #include "ui/events/ozone/layout/stub/stub_keyboard_layout_engine.h"
 #include "ui/events/platform/platform_event_source.h"
@@ -141,7 +140,7 @@
   std::unique_ptr<display::NativeDisplayDelegate> CreateNativeDisplayDelegate()
       override {
     NOTIMPLEMENTED();
-    return std::make_unique<display::FakeDisplayDelegate>();
+    return nullptr;
   }
 
   std::unique_ptr<PlatformScreen> CreateScreen() override {
diff --git a/ui/ozone/platform/x11/BUILD.gn b/ui/ozone/platform/x11/BUILD.gn
index e537f1eb1..76f399c 100644
--- a/ui/ozone/platform/x11/BUILD.gn
+++ b/ui/ozone/platform/x11/BUILD.gn
@@ -70,7 +70,6 @@
     "//ui/base/ime",
     "//ui/base/x",
     "//ui/base/x:gl",
-    "//ui/display/fake",
     "//ui/events",
     "//ui/events:dom_keycode_converter",
     "//ui/events/devices",
diff --git a/ui/ozone/platform/x11/ozone_platform_x11.cc b/ui/ozone/platform/x11/ozone_platform_x11.cc
index 807a8f8..464421d 100644
--- a/ui/ozone/platform/x11/ozone_platform_x11.cc
+++ b/ui/ozone/platform/x11/ozone_platform_x11.cc
@@ -23,7 +23,6 @@
 #include "ui/base/dragdrop/os_exchange_data_provider_factory_ozone.h"
 #include "ui/base/x/x11_cursor_factory.h"
 #include "ui/base/x/x11_util.h"
-#include "ui/display/fake/fake_display_delegate.h"
 #include "ui/events/devices/x11/touch_factory_x11.h"
 #include "ui/events/ozone/layout/keyboard_layout_engine_manager.h"
 #include "ui/events/ozone/layout/stub/stub_keyboard_layout_engine.h"
@@ -107,7 +106,8 @@
 
   std::unique_ptr<display::NativeDisplayDelegate> CreateNativeDisplayDelegate()
       override {
-    return std::make_unique<display::FakeDisplayDelegate>();
+    NOTIMPLEMENTED();
+    return nullptr;
   }
 
   std::unique_ptr<PlatformScreen> CreateScreen() override {
diff --git a/ui/qt/qt_ui.cc b/ui/qt/qt_ui.cc
index a97fa8b..a40661f1 100644
--- a/ui/qt/qt_ui.cc
+++ b/ui/qt/qt_ui.cc
@@ -27,6 +27,7 @@
 #include "ui/base/ime/linux/linux_input_method_context.h"
 #include "ui/color/color_mixer.h"
 #include "ui/color/color_provider.h"
+#include "ui/color/color_provider_manager.h"
 #include "ui/color/color_recipe.h"
 #include "ui/color/color_transform.h"
 #include "ui/gfx/color_palette.h"
diff --git a/ui/views/BUILD.gn b/ui/views/BUILD.gn
index 146d542..f4d38afe 100644
--- a/ui/views/BUILD.gn
+++ b/ui/views/BUILD.gn
@@ -96,6 +96,7 @@
     "animation/ink_drop_state.h",
     "animation/ink_drop_stub.h",
     "animation/ink_drop_util.h",
+    "animation/pulsing_ink_drop_mask.h",
     "animation/scroll_animator.h",
     "animation/slide_out_controller.h",
     "animation/slide_out_controller_delegate.h",
@@ -330,6 +331,7 @@
     "animation/ink_drop_state.cc",
     "animation/ink_drop_stub.cc",
     "animation/ink_drop_util.cc",
+    "animation/pulsing_ink_drop_mask.cc",
     "animation/scroll_animator.cc",
     "animation/slide_out_controller.cc",
     "animation/square_ink_drop_ripple.cc",
diff --git a/ui/views/animation/ink_drop_host.cc b/ui/views/animation/ink_drop_host.cc
index 481c0ed..fd17caad 100644
--- a/ui/views/animation/ink_drop_host.cc
+++ b/ui/views/animation/ink_drop_host.cc
@@ -19,6 +19,7 @@
 #include "ui/views/animation/ink_drop_impl.h"
 #include "ui/views/animation/ink_drop_mask.h"
 #include "ui/views/animation/ink_drop_stub.h"
+#include "ui/views/animation/pulsing_ink_drop_mask.h"
 #include "ui/views/animation/square_ink_drop_ripple.h"
 #include "ui/views/controls/focus_ring.h"
 #include "ui/views/controls/highlight_path_generator.h"
@@ -134,6 +135,11 @@
 }
 
 std::unique_ptr<views::InkDropMask> InkDropHost::CreateInkDropMask() const {
+  // Attention mask takes precedence.
+  if (in_attention_state_) {
+    return std::make_unique<views::PulsingInkDropMask>(host_view_);
+  }
+
   if (create_ink_drop_mask_callback_) {
     return create_ink_drop_mask_callback_.Run();
   }
@@ -146,7 +152,23 @@
   create_ink_drop_mask_callback_ = std::move(callback);
 }
 
+void InkDropHost::ToggleAttentionState(bool attention_on) {
+  in_attention_state_ = attention_on;
+
+  // Calling HostSizeChanged() will force the new mask and color to be fetched.
+  // TODO(collinbaker): Consider adding explicit way to recreate mask instead
+  // of relying on HostSizeChanged() to do so.
+  GetInkDrop()->HostSizeChanged(host_view_->size());
+}
+
 SkColor InkDropHost::GetBaseColor() const {
+  // Attention color takes precedence.
+  if (in_attention_state_) {
+    ui::ColorProvider* const color_provider = host_view_->GetColorProvider();
+    CHECK(color_provider);
+    return color_provider->GetColor(ui::kColorButtonFeatureAttentionHighlight);
+  }
+
   if (absl::holds_alternative<ui::ColorId>(ink_drop_base_color_)) {
     ui::ColorProvider* color_provider = host_view_->GetColorProvider();
     CHECK(color_provider);
@@ -269,7 +291,9 @@
 
 void InkDropHost::AddInkDropLayer(ui::Layer* ink_drop_layer) {
   // If a clip is provided, use that as it is more performant than a mask.
-  if (!AddInkDropClip(ink_drop_layer)) {
+  // If `host_view_` is in attention state e.g. has an IPH bubble attached
+  // also install the attention mask.
+  if (!AddInkDropClip(ink_drop_layer) || in_attention_state_) {
     InstallInkDropMask(ink_drop_layer);
   }
   host_view_->AddLayerToRegion(ink_drop_layer, layer_region_);
diff --git a/ui/views/animation/ink_drop_host.h b/ui/views/animation/ink_drop_host.h
index 109522b..c1c06310 100644
--- a/ui/views/animation/ink_drop_host.h
+++ b/ui/views/animation/ink_drop_host.h
@@ -99,10 +99,15 @@
 
   // Callback replacement of CreateInkDropMask().
   // TODO(pbos): Investigate removing this. It currently is only used by
-  // ToolbarButton.
+  // PieMenuView.
   void SetCreateMaskCallback(
       base::RepeatingCallback<std::unique_ptr<InkDropMask>()> callback);
 
+  // Toggles ink drop attention state on/off. If set on, a pulsing highlight
+  // is shown, prompting users to interact with `host_view_`.
+  // Called by components that want to call into user's attention, e.g. IPH.
+  void ToggleAttentionState(bool attention_on);
+
   // Returns the base color for the ink drop.
   SkColor GetBaseColor() const;
 
@@ -280,6 +285,13 @@
       create_ink_drop_mask_callback_;
 
   base::RepeatingClosureList highlighted_changed_callbacks_;
+
+  // Attention is a state we apply on Buttons' ink drop when we want to draw
+  // users' attention to this button and prompt users' interaction.
+  // It consists of two visual effects: a default light blue color and a pulsing
+  // effect. Current use case is IPH. Go to chrome://internals/user-education
+  // and press e.g. IPH_TabSearch to see the effects.
+  bool in_attention_state_ = false;
 };
 
 }  // namespace views
diff --git a/ui/views/animation/ink_drop_host_unittest.cc b/ui/views/animation/ink_drop_host_unittest.cc
index c550b4b..649a0edc 100644
--- a/ui/views/animation/ink_drop_host_unittest.cc
+++ b/ui/views/animation/ink_drop_host_unittest.cc
@@ -295,44 +295,70 @@
   ~BasicTestViewWithInkDrop() override = default;
 };
 
-// Tests the existence of layer clipping or layer masking when certain path
-// generators are applied on an InkDropHost.
-class InkDropHostClippingTest : public testing::Test {
+// This fixture tests mask, clipping, color and attention.
+class InkDropHostPropertyTest : public ViewsTestBase {
  public:
-  InkDropHostClippingTest() : host_view_test_api_(InkDrop::Get(&host_view_)) {
-    // Set up an InkDropHost. Clipping is based on the size of the view, so
-    // make sure the size is non empty.
-    host_view_test_api_.SetInkDropMode(views::InkDropHost::InkDropMode::ON);
-    host_view_.SetSize(gfx::Size(20, 20));
-
-    // The root layer of the ink drop is created the first time GetInkDrop is
-    // called and then kept alive until the host view is destroyed.
-    ink_drop_ =
-        static_cast<InkDropImpl*>(InkDrop::Get(&host_view_)->GetInkDrop());
-    ink_drop_test_api_ = std::make_unique<test::InkDropImplTestApi>(ink_drop_);
-  }
-  InkDropHostClippingTest(const InkDropHostClippingTest&) = delete;
-  InkDropHostClippingTest& operator=(const InkDropHostClippingTest&) = delete;
-  ~InkDropHostClippingTest() override = default;
+  InkDropHostPropertyTest() = default;
+  InkDropHostPropertyTest(const InkDropHostPropertyTest&) = delete;
+  InkDropHostPropertyTest& operator=(const InkDropHostPropertyTest&) = delete;
+  ~InkDropHostPropertyTest() override = default;
 
   ui::Layer* GetRootLayer() { return ink_drop_test_api_->GetRootLayer(); }
 
  protected:
-  // Test target.
-  BasicTestViewWithInkDrop host_view_;
+  void SetUp() override {
+    ViewsTestBase::SetUp();
+    widget_ = CreateTestWidget();
+    view_ = std::make_unique<BasicTestViewWithInkDrop>();
+    ink_drop_host_test_api_ =
+        std::make_unique<InkDropHostTestApi>(ink_drop_host());
 
-  // Provides internal access to |host_view_| test target.
-  InkDropHostTestApi host_view_test_api_;
+    // Set up an InkDropHost. Clipping is based on the size of the view, so
+    // make sure the size is non empty.
+    ink_drop_host()->SetMode(views::InkDropHost::InkDropMode::ON);
+    view_->SetSize(gfx::Size(20, 20));
+
+    // The root layer of the ink drop is created the first time GetInkDrop is
+    // called and then kept alive until the host view is destroyed.
+    ink_drop_ =
+        static_cast<InkDropImpl*>(InkDrop::Get(view_.get())->GetInkDrop());
+    ink_drop_test_api_ = std::make_unique<test::InkDropImplTestApi>(ink_drop_);
+
+    widget_->SetContentsView(view_.get());
+  }
+
+  void TearDown() override {
+    ink_drop_test_api_.reset();
+    ink_drop_ = nullptr;
+    view_.reset();
+    ink_drop_host_test_api_.reset();
+    widget_.reset();
+    ViewsTestBase::TearDown();
+  }
+
+  InkDropHost* ink_drop_host() { return InkDrop::Get(view_.get()); }
+
+  const ui::ColorProvider& color_provider() {
+    return *widget_->GetColorProvider();
+  }
+
+  // Test target.
+  std::unique_ptr<BasicTestViewWithInkDrop> view_;
 
   raw_ptr<InkDropImpl> ink_drop_ = nullptr;
 
-  // Provides internal access to |host_view_|'s ink drop.
+  // Provides internal access to ink drop host.
+  std::unique_ptr<InkDropHostTestApi> ink_drop_host_test_api_;
+
+  // Provides internal access to `host_view_`'s ink drop.
   std::unique_ptr<test::InkDropImplTestApi> ink_drop_test_api_;
+
+  std::unique_ptr<Widget> widget_;
 };
 
 // Tests that by default (no highlight path generator applied), the root layer
 // will be masked.
-TEST_F(InkDropHostClippingTest, DefaultInkDropMasksRootLayer) {
+TEST_F(InkDropHostPropertyTest, DefaultInkDropMasksRootLayer) {
   ink_drop_->SetHovered(true);
   EXPECT_TRUE(GetRootLayer()->layer_mask_layer());
   EXPECT_TRUE(GetRootLayer()->clip_rect().IsEmpty());
@@ -340,9 +366,9 @@
 
 // Tests that when adding a non empty highlight path generator, the root layer
 // is clipped instead of masked.
-TEST_F(InkDropHostClippingTest,
+TEST_F(InkDropHostPropertyTest,
        HighlightPathGeneratorClipsRootLayerWithoutMask) {
-  views::InstallRectHighlightPathGenerator(&host_view_);
+  views::InstallRectHighlightPathGenerator(view_.get());
   ink_drop_->SetHovered(true);
   EXPECT_FALSE(GetRootLayer()->layer_mask_layer());
   EXPECT_FALSE(GetRootLayer()->clip_rect().IsEmpty());
@@ -351,67 +377,81 @@
 // An empty highlight path generator is used for views who do not want their
 // highlight or ripple constrained by their size. Test that the views' ink
 // drop root layers have neither a clip or mask.
-TEST_F(InkDropHostClippingTest,
+TEST_F(InkDropHostPropertyTest,
        EmptyHighlightPathGeneratorUsesNeitherMaskNorClipsRootLayer) {
-  views::InstallEmptyHighlightPathGenerator(&host_view_);
+  views::InstallEmptyHighlightPathGenerator(view_.get());
   ink_drop_->SetHovered(true);
   EXPECT_FALSE(GetRootLayer()->layer_mask_layer());
   EXPECT_TRUE(GetRootLayer()->clip_rect().IsEmpty());
 }
 
-class InkDropInWidgetTest : public ViewsTestBase {
- public:
-  InkDropInWidgetTest() = default;
-  InkDropInWidgetTest(const InkDropInWidgetTest&) = delete;
-  InkDropInWidgetTest& operator=(const InkDropInWidgetTest&) = delete;
-  ~InkDropInWidgetTest() override = default;
-
- protected:
-  void SetUp() override {
-    ViewsTestBase::SetUp();
-    widget_ = CreateTestWidget();
-    view_ =
-        widget_->SetContentsView(std::make_unique<BasicTestViewWithInkDrop>());
-  }
-
-  void TearDown() override {
-    view_ = nullptr;
-    widget_.reset();
-    ViewsTestBase::TearDown();
-  }
-
-  InkDropHost& ink_drop() { return *InkDrop::Get(view_); }
-  const ui::ColorProvider& color_provider() {
-    return *widget_->GetColorProvider();
-  }
-
- private:
-  std::unique_ptr<Widget> widget_;
-  raw_ptr<View> view_ = nullptr;
-};
-
-TEST_F(InkDropInWidgetTest, SetBaseColor) {
-  ink_drop().SetBaseColor(SK_ColorBLUE);
-  EXPECT_EQ(ink_drop().GetBaseColor(), SK_ColorBLUE);
+TEST_F(InkDropHostPropertyTest, SetBaseColor) {
+  ink_drop_host()->SetBaseColor(SK_ColorBLUE);
+  EXPECT_EQ(ink_drop_host()->GetBaseColor(), SK_ColorBLUE);
 }
 
-TEST_F(InkDropInWidgetTest, SetBaseColorId) {
-  ink_drop().SetBaseColorId(ui::kColorSeparator);
-  EXPECT_EQ(ink_drop().GetBaseColor(),
+TEST_F(InkDropHostPropertyTest, SetBaseColorId) {
+  ink_drop_host()->SetBaseColorId(ui::kColorSeparator);
+  EXPECT_EQ(ink_drop_host()->GetBaseColor(),
             color_provider().GetColor(ui::kColorSeparator));
 
-  ink_drop().SetBaseColor(SK_ColorBLUE);
-  EXPECT_EQ(ink_drop().GetBaseColor(), SK_ColorBLUE);
+  ink_drop_host()->SetBaseColor(SK_ColorBLUE);
+  EXPECT_EQ(ink_drop_host()->GetBaseColor(), SK_ColorBLUE);
 }
 
-TEST_F(InkDropInWidgetTest, SetBaseColorCallback) {
+TEST_F(InkDropHostPropertyTest, SetBaseColorCallback) {
   base::MockRepeatingCallback<SkColor()> callback;
   EXPECT_CALL(callback, Run).WillRepeatedly(testing::Return(SK_ColorCYAN));
-  ink_drop().SetBaseColorCallback(callback.Get());
-  EXPECT_EQ(ink_drop().GetBaseColor(), SK_ColorCYAN);
+  ink_drop_host()->SetBaseColorCallback(callback.Get());
+  EXPECT_EQ(ink_drop_host()->GetBaseColor(), SK_ColorCYAN);
 
-  ink_drop().SetBaseColor(SK_ColorBLUE);
-  EXPECT_EQ(ink_drop().GetBaseColor(), SK_ColorBLUE);
+  ink_drop_host()->SetBaseColor(SK_ColorBLUE);
+  EXPECT_EQ(ink_drop_host()->GetBaseColor(), SK_ColorBLUE);
+}
+
+TEST_F(InkDropHostPropertyTest, ToggleAttentionColor) {
+  // Give it an original color before flipping attention to true.
+  ink_drop_host()->SetBaseColorId(ui::kColorSeparator);
+  EXPECT_EQ(ink_drop_host()->GetBaseColor(),
+            color_provider().GetColor(ui::kColorSeparator));
+
+  // Flipping attention state to true triggers attention color.
+  ink_drop_host()->ToggleAttentionState(true);
+  EXPECT_EQ(
+      ink_drop_host()->GetBaseColor(),
+      color_provider().GetColor(ui::kColorButtonFeatureAttentionHighlight));
+
+  // Flipping attention state to false triggers color restore to original.
+  ink_drop_host()->ToggleAttentionState(false);
+  EXPECT_EQ(ink_drop_host()->GetBaseColor(),
+            color_provider().GetColor(ui::kColorSeparator));
+}
+
+TEST_F(InkDropHostPropertyTest, ToggleAttentionMask) {
+  // Set default state.
+  ink_drop_->SetHovered(true);
+  EXPECT_TRUE(GetRootLayer()->layer_mask_layer());
+
+  // Manually remove ink drop mask.
+  ink_drop_host_test_api_->RemoveInkDropMask();
+  EXPECT_FALSE(GetRootLayer()->layer_mask_layer());
+
+  // Flipping attention to true triggers pulsing mask applied.
+  ink_drop_host()->ToggleAttentionState(true);
+  EXPECT_TRUE(GetRootLayer()->layer_mask_layer());
+}
+
+TEST_F(InkDropHostPropertyTest, AttentionMaskCoexistWithClipping) {
+  views::InstallRectHighlightPathGenerator(view_.get());
+
+  // Clipping suppresses mask with attention off.
+  ink_drop_->SetHovered(true);
+  EXPECT_FALSE(GetRootLayer()->layer_mask_layer());
+
+  // Attention mask can be added while clipping is on.
+  ink_drop_host()->ToggleAttentionState(true);
+  EXPECT_TRUE(GetRootLayer()->layer_mask_layer());
+  EXPECT_FALSE(GetRootLayer()->clip_rect().IsEmpty());
 }
 
 }  // namespace views::test
diff --git a/ui/views/animation/pulsing_ink_drop_mask.cc b/ui/views/animation/pulsing_ink_drop_mask.cc
new file mode 100644
index 0000000..743f4a4
--- /dev/null
+++ b/ui/views/animation/pulsing_ink_drop_mask.cc
@@ -0,0 +1,54 @@
+// 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 "ui/views/animation/pulsing_ink_drop_mask.h"
+#include "ui/compositor/paint_recorder.h"
+
+namespace {
+// Cycle duration of ink drop pulsing animation used for in-product help.
+constexpr base::TimeDelta kFeaturePromoPulseDuration = base::Milliseconds(800);
+
+// Max inset for pulsing animation.
+constexpr float kFeaturePromoPulseInsetDip = 3.0f;
+}  // namespace
+
+namespace views {
+PulsingInkDropMask::PulsingInkDropMask(views::View* layer_container)
+    : AnimationDelegateViews(layer_container),
+      views::InkDropMask(layer_container->size()),
+      layer_container_(layer_container),
+      normal_corner_radius_(layer_container->height() / 2.0f),
+      throb_animation_(this) {
+  throb_animation_.SetThrobDuration(kFeaturePromoPulseDuration);
+  throb_animation_.StartThrobbing(-1);
+}
+
+void PulsingInkDropMask::OnPaintLayer(const ui::PaintContext& context) {
+  cc::PaintFlags flags;
+  flags.setStyle(cc::PaintFlags::kFill_Style);
+  flags.setAntiAlias(true);
+
+  ui::PaintRecorder recorder(context, layer()->size());
+
+  gfx::RectF bounds(layer()->bounds());
+
+  const float current_inset =
+      throb_animation_.CurrentValueBetween(0.0f, kFeaturePromoPulseInsetDip);
+  bounds.Inset(gfx::InsetsF(current_inset));
+  const float corner_radius = normal_corner_radius_ - current_inset;
+
+  recorder.canvas()->DrawRoundRect(bounds, corner_radius, flags);
+}
+
+void PulsingInkDropMask::AnimationProgressed(const gfx::Animation* animation) {
+  DCHECK_EQ(animation, &throb_animation_);
+  layer()->SchedulePaint(gfx::Rect(layer()->size()));
+
+  // This is a workaround for crbug.com/935808: for scale factors >1,
+  // invalidating the mask layer doesn't cause the whole layer to be repainted
+  // on screen. TODO(crbug.com/935808): remove this workaround once the bug is
+  // fixed.
+  layer_container_->SchedulePaint();
+}
+}  // namespace views
diff --git a/ui/views/animation/pulsing_ink_drop_mask.h b/ui/views/animation/pulsing_ink_drop_mask.h
new file mode 100644
index 0000000..91fb2517
--- /dev/null
+++ b/ui/views/animation/pulsing_ink_drop_mask.h
@@ -0,0 +1,42 @@
+// 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 UI_VIEWS_ANIMATION_PULSING_INK_DROP_MASK_H_
+#define UI_VIEWS_ANIMATION_PULSING_INK_DROP_MASK_H_
+
+#include "ui/gfx/animation/throb_animation.h"
+#include "ui/views/animation/animation_delegate_views.h"
+#include "ui/views/animation/ink_drop_mask.h"
+
+namespace views {
+
+// An InkDropMask that animates into pulsing effect.
+class VIEWS_EXPORT PulsingInkDropMask : public views::AnimationDelegateViews,
+                                        public views::InkDropMask {
+ public:
+  explicit PulsingInkDropMask(views::View* layer_container);
+  PulsingInkDropMask(const PulsingInkDropMask&) = delete;
+  PulsingInkDropMask& operator=(const PulsingInkDropMask&) = delete;
+
+ private:
+  // views::InkDropMask:
+  void OnPaintLayer(const ui::PaintContext& context) override;
+
+  // views::AnimationDelegateViews:
+  void AnimationProgressed(const gfx::Animation* animation) override;
+
+  // The View that contains the InkDrop layer we're masking. This must outlive
+  // our instance.
+  const raw_ptr<views::View> layer_container_;
+
+  // Normal corner radius of the ink drop without animation. This is also the
+  // corner radius at the largest instant of the animation.
+  const float normal_corner_radius_;
+
+  gfx::ThrobAnimation throb_animation_;
+};
+
+}  // namespace views
+
+#endif  // UI_VIEWS_ANIMATION_PULSING_INK_DROP_MASK_H_
diff --git a/ui/views/animation/test/ink_drop_host_test_api.cc b/ui/views/animation/test/ink_drop_host_test_api.cc
index bc35e43..0f5ecec 100644
--- a/ui/views/animation/test/ink_drop_host_test_api.cc
+++ b/ui/views/animation/test/ink_drop_host_test_api.cc
@@ -6,6 +6,8 @@
 
 #include <utility>
 
+#include "ui/views/animation/ink_drop_mask.h"
+
 namespace views::test {
 
 InkDropHostTestApi::InkDropHostTestApi(InkDropHost* ink_drop_host)
@@ -43,4 +45,8 @@
   ink_drop_host_->AnimateToState(state, event);
 }
 
+void InkDropHostTestApi::RemoveInkDropMask() {
+  ink_drop_host_->ink_drop_mask_.reset();
+}
+
 }  // namespace views::test
diff --git a/ui/views/animation/test/ink_drop_host_test_api.h b/ui/views/animation/test/ink_drop_host_test_api.h
index 9f2aec1..0434b4b 100644
--- a/ui/views/animation/test/ink_drop_host_test_api.h
+++ b/ui/views/animation/test/ink_drop_host_test_api.h
@@ -50,6 +50,8 @@
 
   InkDropMode ink_drop_mode() const { return ink_drop_host_->ink_drop_mode_; }
 
+  void RemoveInkDropMask();
+
  private:
   // The InkDropHost to provide internal access to.
   raw_ptr<InkDropHost, DanglingUntriaged> ink_drop_host_;
diff --git a/ui/views/bubble/bubble_dialog_model_host.cc b/ui/views/bubble/bubble_dialog_model_host.cc
index 3c5f0f5..3be0c03 100644
--- a/ui/views/bubble/bubble_dialog_model_host.cc
+++ b/ui/views/bubble/bubble_dialog_model_host.cc
@@ -16,6 +16,7 @@
 #include "ui/base/models/dialog_model.h"
 #include "ui/base/models/dialog_model_field.h"
 #include "ui/base/ui_base_types.h"
+#include "ui/gfx/color_utils.h"
 #include "ui/gfx/geometry/insets.h"
 #include "ui/views/accessibility/view_accessibility.h"
 #include "ui/views/border.h"
diff --git a/ui/views/controls/combobox/combobox_util.cc b/ui/views/controls/combobox/combobox_util.cc
index 5f85d082..c880304 100644
--- a/ui/views/controls/combobox/combobox_util.cc
+++ b/ui/views/controls/combobox/combobox_util.cc
@@ -11,6 +11,7 @@
 #include "ui/base/ui_base_features.h"
 #include "ui/color/color_id.h"
 #include "ui/gfx/canvas.h"
+#include "ui/gfx/color_utils.h"
 #include "ui/gfx/geometry/point_f.h"
 #include "ui/gfx/geometry/rect.h"
 #include "ui/gfx/geometry/size_f.h"
diff --git a/ui/views/examples/examples_with_content_main.cc b/ui/views/examples/examples_with_content_main.cc
index 87dfb09..0a20e42 100644
--- a/ui/views/examples/examples_with_content_main.cc
+++ b/ui/views/examples/examples_with_content_main.cc
@@ -9,6 +9,7 @@
 #include "build/build_config.h"
 #include "content/public/browser/browser_context.h"
 #include "ui/base/resource/resource_bundle.h"
+#include "ui/color/color_provider_manager.h"
 #include "ui/views/examples/examples_color_mixer.h"
 #include "ui/views/examples/examples_window.h"
 #include "ui/views/examples/examples_window_with_content.h"
diff --git a/ui/views/widget/widget.cc b/ui/views/widget/widget.cc
index 1f1b9852..4186640 100644
--- a/ui/views/widget/widget.cc
+++ b/ui/views/widget/widget.cc
@@ -27,6 +27,7 @@
 #include "ui/base/metadata/metadata_impl_macros.h"
 #include "ui/base/models/image_model.h"
 #include "ui/base/resource/resource_bundle.h"
+#include "ui/color/color_provider_manager.h"
 #include "ui/compositor/compositor.h"
 #include "ui/compositor/layer.h"
 #include "ui/display/screen.h"
diff --git a/ui/webui/examples/BUILD.gn b/ui/webui/examples/BUILD.gn
index 164536a..7b8f7472 100644
--- a/ui/webui/examples/BUILD.gn
+++ b/ui/webui/examples/BUILD.gn
@@ -7,8 +7,6 @@
 import("//tools/grit/repack.gni")
 import("//tools/v8_context_snapshot/v8_context_snapshot.gni")
 
-assert(use_aura)
-
 static_library("webui_examples_lib") {
   testonly = true
 
@@ -27,12 +25,6 @@
     "browser/devtools/devtools_manager_delegate.h",
     "browser/devtools/devtools_server.cc",
     "browser/devtools/devtools_server.h",
-    "browser/ui/aura/aura_context.cc",
-    "browser/ui/aura/aura_context.h",
-    "browser/ui/aura/content_window.cc",
-    "browser/ui/aura/content_window.h",
-    "browser/ui/aura/fill_layout.cc",
-    "browser/ui/aura/fill_layout.h",
     "browser/ui/web/webui.cc",
     "browser/ui/web/webui.h",
     "browser/webui_controller_factory.cc",
@@ -41,8 +33,6 @@
     "common/content_client.h",
   ]
 
-  data_deps = [ "//tools/v8_context_snapshot" ]
-
   deps = [
     ":pak",
     ":resources_grit",
@@ -52,15 +42,36 @@
     "//content/public/browser",
     "//ipc",
     "//net",
-    "//ui/aura",
-    "//ui/aura:test_support",
     "//ui/base",
     "//ui/display",
     "//ui/platform_window",
-    "//ui/wm",
-    "//ui/wm/public",
     "//url",
   ]
+
+  data_deps = [ "//tools/v8_context_snapshot" ]
+
+  if (use_aura) {
+    sources += [
+      "browser/browser_main_parts_aura.cc",
+      "browser/ui/aura/aura_context.cc",
+      "browser/ui/aura/aura_context.h",
+      "browser/ui/aura/content_window.cc",
+      "browser/ui/aura/content_window.h",
+      "browser/ui/aura/fill_layout.cc",
+      "browser/ui/aura/fill_layout.h",
+    ]
+    deps += [
+      "//ui/aura",
+      "//ui/aura:test_support",
+      "//ui/wm",
+      "//ui/wm/public",
+    ]
+  }
+
+  if (is_mac) {
+    sources += [ "browser/browser_main_parts_mac.mm" ]
+    configs += [ "//build/config/compiler:enable_arc" ]
+  }
 }
 
 grit("resources") {
@@ -122,4 +133,8 @@
       "//sandbox/win:sandbox",
     ]
   }
+
+  if (is_mac) {
+    deps += [ "//sandbox/mac:seatbelt" ]
+  }
 }
diff --git a/ui/webui/examples/app/main.cc b/ui/webui/examples/app/main.cc
index 83cb91e..1f29286 100644
--- a/ui/webui/examples/app/main.cc
+++ b/ui/webui/examples/app/main.cc
@@ -14,6 +14,11 @@
 #include "sandbox/win/src/sandbox_types.h"
 #endif  // BUILDFLAG(IS_WIN)
 
+#if BUILDFLAG(IS_MAC)
+#include "base/files/file_path.h"
+#include "sandbox/mac/seatbelt_exec.h"
+#endif  // BUILDFLAG(IS_MAC)
+
 #if BUILDFLAG(IS_WIN)
 int wWinMain(HINSTANCE instance, HINSTANCE, wchar_t*, int) {
   base::CommandLine::Init(0, nullptr);
@@ -27,6 +32,22 @@
 
   return content::ContentMain(std::move(params));
 }
+#elif BUILDFLAG(IS_MAC)
+int main(int argc, const char** argv) {
+  base::CommandLine::Init(argc, argv);
+  base::CommandLine* command_line = base::CommandLine::ForCurrentProcess();
+  sandbox::SeatbeltExecServer::CreateFromArgumentsResult seatbelt =
+      sandbox::SeatbeltExecServer::CreateFromArguments(
+          command_line->GetProgram().value().c_str(), argc,
+          const_cast<char**>(argv));
+  if (seatbelt.sandbox_required) {
+    CHECK(seatbelt.server->InitializeSandbox());
+  }
+
+  webui_examples::MainDelegate delegate;
+  content::ContentMainParams params(&delegate);
+  return content::ContentMain(std::move(params));
+}
 #else
 int main(int argc, const char** argv) {
   base::CommandLine::Init(argc, argv);
diff --git a/ui/webui/examples/browser/browser_main_parts.cc b/ui/webui/examples/browser/browser_main_parts.cc
index f681264..522c3649 100644
--- a/ui/webui/examples/browser/browser_main_parts.cc
+++ b/ui/webui/examples/browser/browser_main_parts.cc
@@ -13,14 +13,14 @@
 #include "content/public/browser/browser_context.h"
 #include "content/public/browser/browser_task_traits.h"
 #include "content/public/browser/browser_thread.h"
+#include "content/public/browser/navigation_controller.h"
+#include "content/public/browser/web_contents.h"
 #include "content/public/browser/web_contents_view_delegate.h"
 #include "ui/base/l10n/l10n_util.h"
 #include "ui/webui/examples/browser/browser_context.h"
 #include "ui/webui/examples/browser/devtools/devtools_frontend.h"
 #include "ui/webui/examples/browser/devtools/devtools_manager_delegate.h"
 #include "ui/webui/examples/browser/devtools/devtools_server.h"
-#include "ui/webui/examples/browser/ui/aura/aura_context.h"
-#include "ui/webui/examples/browser/ui/aura/content_window.h"
 #include "ui/webui/examples/browser/webui_controller_factory.h"
 #include "ui/webui/examples/grit/webui_examples_resources.h"
 
@@ -67,7 +67,7 @@
       web_contents,
       base::BindRepeating(
           [](BrowserMainParts* browser_main_parts, const GURL& url) {
-            return browser_main_parts->CreateAndShowContentWindow(
+            return browser_main_parts->CreateAndShowWindow(
                 url, l10n_util::GetStringUTF16(IDS_DEVTOOLS_WINDOW_TITLE));
           },
           base::Unretained(this)));
@@ -80,12 +80,16 @@
       base::BindRepeating(
           [](BrowserMainParts* browser_main_parts,
              content::BrowserContext* browser_context, const GURL& url) {
-            return browser_main_parts->CreateAndShowContentWindow(
+            return browser_main_parts->CreateAndShowWindow(
                 url, l10n_util::GetStringUTF16(IDS_DEVTOOLS_WINDOW_TITLE));
           },
           base::Unretained(this)));
 }
 
+void BrowserMainParts::InitializeUiToolkit() {}
+
+void BrowserMainParts::ShutdownUiToolkit() {}
+
 int BrowserMainParts::PreMainMessageLoopRun() {
   std::ignore = temp_dir_.CreateUniqueTempDir();
 
@@ -97,15 +101,31 @@
   content::WebUIControllerFactory::RegisterFactory(
       web_ui_controller_factory_.get());
 
-  aura_context_ = std::make_unique<AuraContext>();
+  InitializeUiToolkit();
 
-  CreateAndShowContentWindow(
+  CreateAndShowWindow(
       GURL("chrome://main/"),
       l10n_util::GetStringUTF16(IDS_WEBUI_EXAMPLES_WINDOW_TITLE));
 
   return 0;
 }
 
+content::WebContents* BrowserMainParts::CreateAndShowWindow(
+    GURL url,
+    const std::u16string& title) {
+  ++content_windows_outstanding_;
+
+  content::WebContents::CreateParams params(browser_context_.get());
+  std::unique_ptr<content::WebContents> web_contents =
+      content::WebContents::Create(params);
+  content::NavigationController::LoadURLParams url_params(url);
+  web_contents->GetController().LoadURLWithParams(url_params);
+
+  auto* web_contents_ptr = web_contents.get();
+  CreateAndShowWindowForWebContents(std::move(web_contents), title);
+  return web_contents_ptr;
+}
+
 void BrowserMainParts::WillRunMainMessageLoop(
     std::unique_ptr<base::RunLoop>& run_loop) {
   quit_run_loop_ = run_loop->QuitClosure();
@@ -116,31 +136,9 @@
   browser_context_.reset();
 }
 
-content::WebContents* BrowserMainParts::CreateAndShowContentWindow(
-    GURL url,
-    const std::u16string& title) {
-  auto content_window = std::make_unique<ContentWindow>(aura_context_.get(),
-                                                        browser_context_.get());
-  ContentWindow* content_window_ptr = content_window.get();
-  content_window_ptr->SetTitle(title);
-  content_window_ptr->NavigateToURL(url);
-  content_window_ptr->Show();
-  content_window_ptr->SetCloseCallback(
-      base::BindOnce(&BrowserMainParts::OnWindowClosed,
-                     weak_factory_.GetWeakPtr(), std::move(content_window)));
-  ++content_windows_outstanding_;
-  return content_window_ptr->web_contents();
-}
-
-void BrowserMainParts::OnWindowClosed(
-    std::unique_ptr<ContentWindow> content_window) {
+void BrowserMainParts::OnWindowClosed() {
   --content_windows_outstanding_;
   auto task_runner = content::GetUIThreadTaskRunner({});
-  // We are dispatching a callback that originates from the content_window.
-  // Deleting soon instead of now eliminates the chance of a crash in case the
-  // content_window or associated objects have more work to do after this
-  // callback.
-  task_runner->DeleteSoon(FROM_HERE, std::move(content_window));
   if (content_windows_outstanding_ == 0) {
     task_runner->PostTask(FROM_HERE,
                           base::BindOnce(&BrowserMainParts::QuitMessageLoop,
@@ -149,7 +147,7 @@
 }
 
 void BrowserMainParts::QuitMessageLoop() {
-  aura_context_.reset();
+  ShutdownUiToolkit();
   web_ui_controller_factory_.reset();
   quit_run_loop_.Run();
 }
diff --git a/ui/webui/examples/browser/browser_main_parts.h b/ui/webui/examples/browser/browser_main_parts.h
index fd89bc8..6518d5da 100644
--- a/ui/webui/examples/browser/browser_main_parts.h
+++ b/ui/webui/examples/browser/browser_main_parts.h
@@ -21,14 +21,13 @@
 
 namespace webui_examples {
 
-class AuraContext;
 class BrowserContext;
-class ContentWindow;
 class WebUIControllerFactory;
 
 class BrowserMainParts : public content::BrowserMainParts {
  public:
-  BrowserMainParts();
+  static std::unique_ptr<BrowserMainParts> Create();
+
   BrowserMainParts(const BrowserMainParts&) = delete;
   BrowserMainParts& operator=(const BrowserMainParts&) = delete;
   ~BrowserMainParts() override;
@@ -38,6 +37,18 @@
   std::unique_ptr<content::DevToolsManagerDelegate>
   CreateDevToolsManagerDelegate();
 
+ protected:
+  BrowserMainParts();
+  virtual void InitializeUiToolkit();
+  virtual void ShutdownUiToolkit();
+  virtual void CreateAndShowWindowForWebContents(
+      std::unique_ptr<content::WebContents> web_contents,
+      const std::u16string& title) = 0;
+
+  content::BrowserContext* browser_context() { return browser_context_.get(); }
+
+  void OnWindowClosed();
+
  private:
   // content::BrowserMainParts:
   int PreMainMessageLoopRun() override;
@@ -45,17 +56,15 @@
       std::unique_ptr<base::RunLoop>& run_loop) override;
   void PostMainMessageLoopRun() override;
 
-  // content::WebContents is associated and bound to the lifetime of the window.
-  content::WebContents* CreateAndShowContentWindow(GURL url,
-                                                   const std::u16string& title);
-  void OnWindowClosed(std::unique_ptr<ContentWindow> content_window);
+  content::WebContents* CreateAndShowWindow(GURL url,
+                                            const std::u16string& title);
+
   void QuitMessageLoop();
 
   base::ScopedTempDir temp_dir_;
   std::unique_ptr<WebUIControllerFactory> web_ui_controller_factory_;
   std::unique_ptr<content::BrowserContext> browser_context_;
 
-  std::unique_ptr<AuraContext> aura_context_;
   int content_windows_outstanding_ = 0;
 
   base::RepeatingClosure quit_run_loop_;
diff --git a/ui/webui/examples/browser/browser_main_parts_aura.cc b/ui/webui/examples/browser/browser_main_parts_aura.cc
new file mode 100644
index 0000000..07e3632
--- /dev/null
+++ b/ui/webui/examples/browser/browser_main_parts_aura.cc
@@ -0,0 +1,62 @@
+// 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 "base/types/pass_key.h"
+#include "content/public/browser/browser_thread.h"
+#include "content/public/browser/web_contents.h"
+#include "ui/webui/examples/browser/browser_main_parts.h"
+#include "ui/webui/examples/browser/ui/aura/aura_context.h"
+#include "ui/webui/examples/browser/ui/aura/content_window.h"
+
+namespace webui_examples {
+
+class BrowserMainPartsAura : public BrowserMainParts {
+ public:
+  using PassKey = base::PassKey<BrowserMainParts>;
+  explicit BrowserMainPartsAura(PassKey) {}
+  BrowserMainPartsAura(const BrowserMainPartsAura&) = delete;
+  const BrowserMainPartsAura& operator=(const BrowserMainPartsAura&) = delete;
+  ~BrowserMainPartsAura() override = default;
+
+ private:
+  void InitializeUiToolkit() override {
+    aura_context_ = std::make_unique<AuraContext>();
+  }
+
+  void ShutdownUiToolkit() override { aura_context_.reset(); }
+
+  void CreateAndShowWindowForWebContents(
+      std::unique_ptr<content::WebContents> web_contents,
+      const std::u16string& title) override {
+    auto content_window = std::make_unique<ContentWindow>(
+        aura_context_.get(), std::move(web_contents));
+    ContentWindow* content_window_ptr = content_window.get();
+    content_window_ptr->SetTitle(title);
+    content_window_ptr->Show();
+    content_window_ptr->SetCloseCallback(
+        base::BindOnce(&BrowserMainPartsAura::OnWindowClosed,
+                       weak_factory_.GetWeakPtr(), std::move(content_window)));
+  }
+
+  void OnWindowClosed(std::unique_ptr<ContentWindow> content_window) {
+    // We are dispatching a callback that originates from the content_window.
+    // Deleting soon instead of now eliminates the chance of a crash in case the
+    // content_window or associated objects have more work to do after this
+    // callback.
+    content::GetUIThreadTaskRunner({})->DeleteSoon(FROM_HERE,
+                                                   std::move(content_window));
+    BrowserMainParts::OnWindowClosed();
+  }
+
+  std::unique_ptr<AuraContext> aura_context_;
+  base::WeakPtrFactory<BrowserMainPartsAura> weak_factory_{this};
+};
+
+// static
+std::unique_ptr<BrowserMainParts> BrowserMainParts::Create() {
+  return std::make_unique<BrowserMainPartsAura>(
+      BrowserMainPartsAura::PassKey());
+}
+
+}  // namespace webui_examples
diff --git a/ui/webui/examples/browser/browser_main_parts_mac.mm b/ui/webui/examples/browser/browser_main_parts_mac.mm
new file mode 100644
index 0000000..107c4de00
--- /dev/null
+++ b/ui/webui/examples/browser/browser_main_parts_mac.mm
@@ -0,0 +1,17 @@
+// 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 <memory>
+
+#include "ui/webui/examples/browser/browser_main_parts.h"
+
+namespace webui_examples {
+
+// static
+std::unique_ptr<BrowserMainParts> BrowserMainParts::Create() {
+  NOTREACHED();
+  return nullptr;
+}
+
+}  // namespace webui_examples
diff --git a/ui/webui/examples/browser/content_browser_client.cc b/ui/webui/examples/browser/content_browser_client.cc
index 74bd63d..e5e9047 100644
--- a/ui/webui/examples/browser/content_browser_client.cc
+++ b/ui/webui/examples/browser/content_browser_client.cc
@@ -16,7 +16,7 @@
 
 std::unique_ptr<content::BrowserMainParts>
 ContentBrowserClient::CreateBrowserMainParts(bool is_integration_test) {
-  auto browser_main_parts = std::make_unique<BrowserMainParts>();
+  auto browser_main_parts = BrowserMainParts::Create();
   browser_main_parts_ = browser_main_parts.get();
   return browser_main_parts;
 }
diff --git a/ui/webui/examples/browser/ui/aura/content_window.cc b/ui/webui/examples/browser/ui/aura/content_window.cc
index ce85ec8..9dbed518 100644
--- a/ui/webui/examples/browser/ui/aura/content_window.cc
+++ b/ui/webui/examples/browser/ui/aura/content_window.cc
@@ -4,10 +4,6 @@
 
 #include "ui/webui/examples/browser/ui/aura/content_window.h"
 
-#include "base/containers/flat_set.h"
-#include "base/no_destructor.h"
-#include "content/public/browser/browser_context.h"
-#include "content/public/browser/navigation_controller.h"
 #include "content/public/browser/web_contents.h"
 #include "ui/aura/window.h"
 #include "ui/aura/window_tree_host.h"
@@ -39,14 +35,13 @@
 }  // namespace
 
 ContentWindow::ContentWindow(AuraContext* aura_context,
-                             content::BrowserContext* browser_context) {
+                             std::unique_ptr<content::WebContents> web_contents)
+    : web_contents_(std::move(web_contents)) {
   host_ = aura_context->CreateWindowTreeHost();
 
   // Cursor support.
   root_window_event_filter_ = std::make_unique<wm::CompoundEventFilter>();
 
-  content::WebContents::CreateParams params(browser_context);
-  web_contents_ = content::WebContents::Create(params);
   aura::Window* web_contents_window = web_contents_->GetNativeView();
   aura::WindowTreeHost* window_tree_host = host_->window_tree_host();
   window_tree_host->window()->GetRootWindow()->AddChild(web_contents_window);
@@ -71,11 +66,6 @@
   window_tree_host->Show();
 }
 
-void ContentWindow::NavigateToURL(GURL url) {
-  content::NavigationController::LoadURLParams url_params(url);
-  web_contents_->GetController().LoadURLWithParams(url_params);
-}
-
 void ContentWindow::SetCloseCallback(base::OnceClosure on_close) {
   host_->window_tree_host()->AddObserver(new QuitOnClose(std::move(on_close)));
 }
diff --git a/ui/webui/examples/browser/ui/aura/content_window.h b/ui/webui/examples/browser/ui/aura/content_window.h
index c7e323dd..ba5dd68 100644
--- a/ui/webui/examples/browser/ui/aura/content_window.h
+++ b/ui/webui/examples/browser/ui/aura/content_window.h
@@ -10,7 +10,6 @@
 #include "url/gurl.h"
 
 namespace content {
-class BrowserContext;
 class WebContents;
 }  // namespace content
 
@@ -25,7 +24,7 @@
 class ContentWindow {
  public:
   ContentWindow(AuraContext* aura_context,
-                content::BrowserContext* browser_context);
+                std::unique_ptr<content::WebContents> web_contents);
   ContentWindow(const ContentWindow&) = delete;
   ContentWindow& operator=(const ContentWindow&) = delete;
   ~ContentWindow();
@@ -33,7 +32,6 @@
   void SetTitle(const std::u16string& title);
 
   void Show();
-  void NavigateToURL(GURL url);
   void SetCloseCallback(base::OnceClosure on_close);
 
   content::WebContents* web_contents() { return web_contents_.get(); }