diff --git a/DEPS b/DEPS
index ef9bc69..e7e9aba 100644
--- a/DEPS
+++ b/DEPS
@@ -299,15 +299,15 @@
   # 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': 'd32ef242befe8c58ee01237bcbba6b5a67b0711e',
+  'skia_revision': 'eb3f6d58f591daaedbef747f8f4161d3cb669a5a',
   # 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': '05aca56fd2f465914c0168123499a5a2e3beee9a',
+  'v8_revision': '4b1a75acff49a134aca2d2e85520cc2fff199b09',
   # 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': '1148a66276a924672f052d8609cf56429a538fc6',
+  'angle_revision': '75153e1009ac95eb066725db8248453588755a60',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling SwiftShader
   # and whatever else without interference from each other.
@@ -326,7 +326,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling Fuchsia sdk
   # and whatever else without interference from each other.
-  'fuchsia_version': 'version:11.20230217.0.1',
+  'fuchsia_version': 'version:11.20230217.2.1',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling google-toolbox-for-mac
   # and whatever else without interference from each other.
@@ -370,7 +370,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': '44b7bc5487653bc9c61958ffd58851a6c369d441',
+  'catapult_revision': '767feea600c0102ba7cd82cf725bb8de1fc6b1e0',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling CrossBench
   # and whatever else without interference from each other.
@@ -426,7 +426,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': 'ca469fec10c3b41f672ec7569a861f280990728a',
+  'dawn_revision': '32cb509307806cbccef5642ac365df2d9ed229f4',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling feed
   # and whatever else without interference from each other.
@@ -764,7 +764,7 @@
 
   'src/clank': {
     'url': 'https://chrome-internal.googlesource.com/clank/internal/apps.git' + '@' +
-    'ec0c0cc6520a8c14f821566d044979ce3cbf2fe6',
+    'da576c1e69b367796b931cd0c8eb53ef331049e6',
     'condition': 'checkout_android and checkout_src_internal',
   },
 
@@ -1120,7 +1120,7 @@
     Var('boringssl_git') + '/boringssl.git' + '@' +  Var('boringssl_revision'),
 
   'src/third_party/breakpad/breakpad':
-    Var('chromium_git') + '/breakpad/breakpad.git' + '@' + '5687ac51caab4f7100ddcb9dea92ca6bd6be66e7',
+    Var('chromium_git') + '/breakpad/breakpad.git' + '@' + 'abb105db21e962eda5b7d9b7a0ac8dd701e0b987',
 
   'src/third_party/byte_buddy': {
       'packages': [
@@ -1206,13 +1206,13 @@
   },
 
   'src/third_party/depot_tools':
-    Var('chromium_git') + '/chromium/tools/depot_tools.git' + '@' + '3879bd830c6c1c8d4f3460fa93fa58e891e58263',
+    Var('chromium_git') + '/chromium/tools/depot_tools.git' + '@' + '62c7a8bad074fdb4339e544b0053206a4f820e15',
 
   'src/third_party/devtools-frontend/src':
     Var('chromium_git') + '/devtools/devtools-frontend' + '@' + Var('devtools_frontend_revision'),
 
   'src/third_party/devtools-frontend-internal': {
-      'url': 'https://chrome-internal.googlesource.com/devtools/devtools-internal.git' + '@' + 'cce1639bd0d6fb15628558ade2dae5c7bc0d002c',
+      'url': 'https://chrome-internal.googlesource.com/devtools/devtools-internal.git' + '@' + 'fb734201152142ab7dbbb4e8fdbc9641a0dca027',
     'condition': 'checkout_src_internal',
   },
 
@@ -1540,7 +1540,7 @@
     Var('chromium_git') + '/webm/libwebp.git' + '@' +  '603e8d7adb0ccc35237419c2938194623b60e9be',
 
   'src/third_party/libyuv':
-    Var('chromium_git') + '/libyuv/libyuv.git' + '@' + '6e4b0acb4b3d5858c77a044aad46132998ac4a76',
+    Var('chromium_git') + '/libyuv/libyuv.git' + '@' + '2bdc210be9eb11ded16bf3ef1f6cadb0d4dcb0c2',
 
   'src/third_party/lighttpd': {
       'url': Var('chromium_git') + '/chromium/deps/lighttpd.git' + '@' + Var('lighttpd_revision'),
@@ -1684,7 +1684,7 @@
   },
 
   'src/third_party/perfetto':
-    Var('android_git') + '/platform/external/perfetto.git' + '@' + '58600633967db2ab113cf205eb2be6b76aef97d4',
+    Var('android_git') + '/platform/external/perfetto.git' + '@' + '0d180f46481a96cbe8340734fa5cdce3bba636c8',
 
   'src/third_party/perl': {
       'url': Var('chromium_git') + '/chromium/deps/perl.git' + '@' + '6f3e5028eb65d0b4c5fdd792106ac4c84eee1eb3',
@@ -1869,7 +1869,7 @@
     Var('chromium_git') + '/external/github.com/gpuweb/cts.git' + '@' + '6c8361e98f1daba65902f5e2fc1297893ac14b67',
 
   'src/third_party/webrtc':
-    Var('webrtc_git') + '/src.git' + '@' + '0277f2b4a71b83f279aebb963f054befd6cc6b1b',
+    Var('webrtc_git') + '/src.git' + '@' + '48d784225972b7dd0542acfad7cd2d0eed25ad1c',
 
   # Wuffs' canonical repository is at github.com/google/wuffs, but we use
   # Skia's mirror of Wuffs, the same as in upstream Skia's DEPS file.
@@ -1939,7 +1939,7 @@
     Var('chromium_git') + '/v8/v8.git' + '@' +  Var('v8_revision'),
 
   'src-internal': {
-    'url': 'https://chrome-internal.googlesource.com/chrome/src-internal.git@c77eb159ddb564f93ac01cbdfcefdf9861f835c7',
+    'url': 'https://chrome-internal.googlesource.com/chrome/src-internal.git@d3157750332d0cf8323f9a74cf699e5525c88d22',
     'condition': 'checkout_src_internal',
   },
 
diff --git a/ash/accelerators/accelerator_commands.cc b/ash/accelerators/accelerator_commands.cc
index 3e7ce6e2..3cca1c1 100644
--- a/ash/accelerators/accelerator_commands.cc
+++ b/ash/accelerators/accelerator_commands.cc
@@ -62,6 +62,8 @@
 #include "ash/wm/overview/overview_controller.h"
 #include "ash/wm/overview/overview_session.h"
 #include "ash/wm/screen_pinning_controller.h"
+#include "ash/wm/tablet_mode/tablet_mode_multitask_menu_event_handler.h"
+#include "ash/wm/tablet_mode/tablet_mode_window_manager.h"
 #include "ash/wm/window_cycle/window_cycle_controller.h"
 #include "ash/wm/window_state.h"
 #include "ash/wm/window_util.h"
@@ -518,14 +520,17 @@
 }
 
 bool CanToggleMultitaskMenu() {
-  if (!chromeos::wm::features::IsWindowLayoutMenuEnabled() ||
-      Shell::Get()->tablet_mode_controller()->InTabletMode()) {
+  if (!chromeos::wm::features::IsWindowLayoutMenuEnabled()) {
     return false;
   }
   aura::Window* window = window_util::GetActiveWindow();
   if (!window) {
     return false;
   }
+  if (Shell::Get()->tablet_mode_controller()->InTabletMode()) {
+    // In tablet mode, the window just has to be able to maximize.
+    return WindowState::Get(window)->CanMaximize();
+  }
   // If the active window has a visible size button, the menu can be opened.
   if (auto* size_button = GetFrameSizeButton(window);
       size_button && size_button->GetVisible()) {
@@ -1476,6 +1481,15 @@
   DCHECK(chromeos::wm::features::IsWindowLayoutMenuEnabled());
   aura::Window* window = window_util::GetActiveWindow();
   DCHECK(window);
+  if (auto* tablet_mode_controller = Shell::Get()->tablet_mode_controller();
+      tablet_mode_controller->InTabletMode()) {
+    auto* tablet_mode_event_handler =
+        tablet_mode_controller->tablet_mode_window_manager()
+            ->tablet_mode_multitask_menu_event_handler();
+    // Does nothing if the menu is already shown.
+    tablet_mode_event_handler->ShowMultitaskMenu(window);
+    return;
+  }
   auto* frame_view = NonClientFrameViewAsh::Get(window);
   if (!frame_view) {
     // If `window` doesn't have a frame, it must be the multitask menu and have
diff --git a/ash/assistant/ui/main_stage/suggestion_chip_view_unittest.cc b/ash/assistant/ui/main_stage/suggestion_chip_view_unittest.cc
index d996b98..a1b22f7e 100644
--- a/ash/assistant/ui/main_stage/suggestion_chip_view_unittest.cc
+++ b/ash/assistant/ui/main_stage/suggestion_chip_view_unittest.cc
@@ -75,13 +75,6 @@
   return canvas.GetBitmap();
 }
 
-SkBitmap GetFocusRingBitmap(views::FocusRing* focus_ring) {
-  gfx::Canvas canvas(focus_ring->size(), /*image_scale=*/1.0f,
-                     /*is_opaque=*/false);
-  focus_ring->OnPaint(&canvas);
-  return canvas.GetBitmap();
-}
-
 }  // namespace
 
 // Tests -----------------------------------------------------------------------
@@ -161,18 +154,6 @@
           ColorProvider::Get()->GetContentLayerColor(
               ColorProvider::ContentLayerType::kSeparatorColor))));
 
-  // Focus the chip view and confirm that focus ring is rendered.
-  suggestion_chip_view->RequestFocus();
-  views::test::RunScheduledLayout(views::FocusRing::Get(suggestion_chip_view));
-  EXPECT_TRUE(cc::ExactPixelComparator().Compare(
-      GetFocusRingBitmap(views::FocusRing::Get(suggestion_chip_view)),
-      GetBitmapWithInnerRoundedRect(
-          kSuggestionChipViewSize, /*stroke_width=*/2,
-          ColorProvider::Get()->GetControlsLayerColor(
-              ColorProvider::ControlsLayerType::kFocusRingColor))));
-
-  suggestion_chip_view->GetFocusManager()->ClearFocus();
-
   // Switch the color mode.
   dark_light_mode_controller->ToggleColorMode();
   ASSERT_NE(initial_dark_mode_status,
@@ -187,15 +168,6 @@
           kSuggestionChipViewSize, /*stroke_width=*/1,
           ColorProvider::Get()->GetContentLayerColor(
               ColorProvider::ContentLayerType::kSeparatorColor))));
-
-  // Focus the chip view and confirm that focus ring is rendered.
-  suggestion_chip_view->RequestFocus();
-  EXPECT_TRUE(cc::ExactPixelComparator().Compare(
-      GetFocusRingBitmap(views::FocusRing::Get(suggestion_chip_view)),
-      GetBitmapWithInnerRoundedRect(
-          kSuggestionChipViewSize, /*stroke_width=*/2,
-          ColorProvider::Get()->GetControlsLayerColor(
-              ColorProvider::ControlsLayerType::kFocusRingColor))));
 }
 
 TEST_F(SuggestionChipViewTest, FontWeight) {
diff --git a/ash/shell.cc b/ash/shell.cc
index 74c7a0ab..250bdd8 100644
--- a/ash/shell.cc
+++ b/ash/shell.cc
@@ -219,7 +219,6 @@
 #include "chromeos/ash/services/assistant/public/cpp/features.h"
 #include "chromeos/dbus/init/initialize_dbus_client.h"
 #include "chromeos/dbus/power/power_policy_controller.h"
-#include "chromeos/ui/frame/multitask_menu/multitask_menu_nudge_controller.h"
 #include "chromeos/ui/wm/features.h"
 #include "components/prefs/pref_registry_simple.h"
 #include "components/prefs/pref_service.h"
@@ -849,7 +848,7 @@
   // destroyed first to remove the tablet mode observer.
   glanceables_controller_.reset();
 
-  multitask_menu_nudge_controller_.reset();
+  multitask_menu_nudge_delegate_.reset();
   tablet_mode_controller_.reset();
   login_screen_controller_.reset();
   system_notification_controller_.reset();
@@ -1524,15 +1523,6 @@
   // since it may need to add observers to root windows.
   window_restore_controller_ = std::make_unique<WindowRestoreController>();
 
-  // Needs to be constructed after `WindowTreeHostManager::InitHosts()` as it
-  // needs to get the activation client from chromeos/.
-  if (chromeos::wm::features::IsWindowLayoutMenuEnabled()) {
-    multitask_menu_nudge_controller_ =
-        std::make_unique<chromeos::MultitaskMenuNudgeController>(
-            GetPrimaryRootWindow(),
-            std::make_unique<MultitaskMenuNudgeDelegateAsh>());
-  }
-
   static_cast<CursorManager*>(cursor_manager_.get())->Init();
 
   mojo::PendingRemote<device::mojom::Fingerprint> fingerprint;
@@ -1589,6 +1579,8 @@
 
   if (chromeos::wm::features::IsWindowLayoutMenuEnabled()) {
     float_controller_ = std::make_unique<FloatController>();
+    multitask_menu_nudge_delegate_ =
+        std::make_unique<MultitaskMenuNudgeDelegateAsh>();
   }
 
   if (features::IsFederatedServiceEnabled()) {
diff --git a/ash/shell.h b/ash/shell.h
index fc82d549..3eb57fe1 100644
--- a/ash/shell.h
+++ b/ash/shell.h
@@ -24,6 +24,7 @@
 #include "base/memory/weak_ptr.h"
 #include "base/observer_list.h"
 #include "base/scoped_observation_traits.h"
+#include "chromeos/ui/frame/multitask_menu/multitask_menu_nudge_controller.h"
 #include "ui/aura/window.h"
 #include "ui/base/ui_base_types.h"
 #include "ui/display/screen.h"
@@ -40,7 +41,6 @@
 
 namespace chromeos {
 class ImmersiveContext;
-class MultitaskMenuNudgeController;
 class SnapController;
 }  // namespace chromeos
 
@@ -575,9 +575,6 @@
   MultiDisplayMetricsController* multi_display_metrics_controller() {
     return multi_display_metrics_controller_.get();
   }
-  chromeos::MultitaskMenuNudgeController* multitask_menu_nudge_controller() {
-    return multitask_menu_nudge_controller_.get();
-  }
   NearbyShareControllerImpl* nearby_share_controller() {
     return nearby_share_controller_.get();
   }
@@ -939,8 +936,8 @@
       multi_display_metrics_controller_;
   std::unique_ptr<MultiDeviceNotificationPresenter>
       multidevice_notification_presenter_;
-  std::unique_ptr<chromeos::MultitaskMenuNudgeController>
-      multitask_menu_nudge_controller_;
+  std::unique_ptr<chromeos::MultitaskMenuNudgeController::Delegate>
+      multitask_menu_nudge_delegate_;
   std::unique_ptr<NearbyShareControllerImpl> nearby_share_controller_;
   std::unique_ptr<NearbyShareDelegate> nearby_share_delegate_;
   std::unique_ptr<ParentAccessController> parent_access_controller_;
diff --git a/ash/system/message_center/ash_notification_view.cc b/ash/system/message_center/ash_notification_view.cc
index 4167970..9cbca1a 100644
--- a/ash/system/message_center/ash_notification_view.cc
+++ b/ash/system/message_center/ash_notification_view.cc
@@ -292,6 +292,8 @@
 END_METADATA
 
 void AshNotificationView::AddedToWidget() {
+  MessageView::AddedToWidget();
+
   // crbug/1337661: We need to abort animations in a grouped parent view when
   // it's widget is being destroyed. By default when a widget is destroyed, all
   // current animations are forced to finish. The grouped notification removal
diff --git a/ash/system/message_center/ash_notification_view_pixeltest.cc b/ash/system/message_center/ash_notification_view_pixeltest.cc
index e300be3f..adaedbb 100644
--- a/ash/system/message_center/ash_notification_view_pixeltest.cc
+++ b/ash/system/message_center/ash_notification_view_pixeltest.cc
@@ -5,6 +5,7 @@
 #include "ash/capture_mode/capture_mode_controller.h"
 #include "ash/capture_mode/capture_mode_test_util.h"
 #include "ash/capture_mode/capture_mode_util.h"
+#include "ash/system/message_center/ash_notification_view.h"
 #include "ash/system/message_center/message_popup_animation_waiter.h"
 #include "ash/system/notification_center/notification_center_test_api.h"
 #include "ash/system/unified/unified_system_tray.h"
@@ -17,6 +18,7 @@
 #include "ui/base/models/image_model.h"
 #include "ui/message_center/views/message_popup_view.h"
 #include "ui/message_center/views/message_view.h"
+#include "ui/message_center/views/notification_control_buttons_view.h"
 
 namespace ash {
 
@@ -86,6 +88,37 @@
   std::unique_ptr<NotificationCenterTestApi> test_api_;
 };
 
+// Tests that a notification's close button is visible when it is focused.
+TEST_F(AshNotificationViewPixelTestBase, CloseButtonFocused) {
+  // Create a notification and open the notification center bubble to view it.
+  const auto id = test_api()->AddNotification();
+  test_api()->ToggleBubble();
+
+  // Verify that the close button is neither focused nor visible. Note that the
+  // close button, as a `views::ImageButton`, will actually be visible in the
+  // sense of `views::View::GetVisible()`, but its parent's `ui::Layer` will
+  // have an opacity of zero, making it visually invisible.
+  auto* notification_view = static_cast<AshNotificationView*>(
+      test_api()->GetNotificationViewForId(id));
+  auto* control_buttons_layer =
+      notification_view->GetControlButtonsView()->layer();
+  auto* close_button =
+      notification_view->GetControlButtonsView()->close_button();
+  EXPECT_EQ(control_buttons_layer->opacity(), 0);
+  EXPECT_FALSE(close_button->HasFocus());
+
+  // Move focus to the close button.
+  close_button->GetWidget()->widget_delegate()->SetCanActivate(true);
+  close_button->RequestFocus();
+
+  // Verify, with both an assertion and a pixel test, that the close button has
+  // focus and is visible.
+  EXPECT_TRUE(close_button->HasFocus());
+  EXPECT_EQ(control_buttons_layer->opacity(), 1);
+  EXPECT_TRUE(GetPixelDiffer()->CompareUiComponentsOnPrimaryScreen(
+      "close_button_focused", 0u, notification_view));
+}
+
 class AshNotificationViewTitlePixelTest
     : public AshNotificationViewPixelTestBase,
       public testing::WithParamInterface<
diff --git a/ash/system/notification_center/notification_center_view_unittest.cc b/ash/system/notification_center/notification_center_view_unittest.cc
index b07252e..164397b 100644
--- a/ash/system/notification_center/notification_center_view_unittest.cc
+++ b/ash/system/notification_center/notification_center_view_unittest.cc
@@ -8,6 +8,7 @@
 
 #include "ash/constants/ash_features.h"
 #include "ash/constants/ash_pref_names.h"
+#include "ash/focus_cycler.h"
 #include "ash/session/session_controller_impl.h"
 #include "ash/shell.h"
 #include "ash/system/message_center/ash_message_center_lock_screen_controller.h"
@@ -188,6 +189,13 @@
     return focused_message_view;
   }
 
+  void FocusClearAllButton() {
+    auto* widget = GetNotificationBarClearAllButton()->GetWidget();
+    widget->widget_delegate()->SetCanActivate(true);
+    Shell::Get()->focus_cycler()->FocusWidget(widget);
+    GetNotificationBarClearAllButton()->RequestFocus();
+  }
+
   void RelayoutMessageCenterViewForTest() {
     // Outside of tests, any changes to bubble's size as well as scrolling
     // through notification list will trigger TrayBubbleView's BoxLayout to
@@ -725,6 +733,23 @@
             GetNotificationBarClearAllButton()->height());
 }
 
+// Tests that the "Clear all" button is not focusable when it is disabled.
+TEST_P(NotificationCenterViewTest, ClearAllNotFocusableWhenDisabled) {
+  // Add a pinned notification and toggle the bubble.
+  test_api()->AddPinnedNotification();
+  test_api()->ToggleBubble();
+
+  // Verify that the "Clear all" button is visible but disabled.
+  ASSERT_TRUE(GetNotificationBarClearAllButton()->GetVisible());
+  ASSERT_FALSE(GetNotificationBarClearAllButton()->GetEnabled());
+
+  // Attempt to focus the "Clear all" button.
+  FocusClearAllButton();
+
+  // Verify that the "Clear all" button did not receive focus.
+  EXPECT_FALSE(GetNotificationBarClearAllButton()->HasFocus());
+}
+
 TEST_P(NotificationCenterViewTest, StackedNotificationCount) {
   // There should not be any stacked notifications in the expanded message
   // center with just one notification added.
diff --git a/ash/system/notification_center/stacked_notification_bar.cc b/ash/system/notification_center/stacked_notification_bar.cc
index 2037012..a75e5d3f 100644
--- a/ash/system/notification_center/stacked_notification_bar.cc
+++ b/ash/system/notification_center/stacked_notification_bar.cc
@@ -328,9 +328,7 @@
       unpinned_count);
   clear_all_button_->SetTooltipText(tooltip);
   clear_all_button_->SetAccessibleName(tooltip);
-  clear_all_button_->SetState(unpinned_count == 0
-                                  ? views::Button::STATE_DISABLED
-                                  : views::Button::STATE_NORMAL);
+  clear_all_button_->SetEnabled(unpinned_count != 0);
 
   return true;
 }
diff --git a/ash/system/unified/quick_settings_view.cc b/ash/system/unified/quick_settings_view.cc
index f0dabf6d..585f808 100644
--- a/ash/system/unified/quick_settings_view.cc
+++ b/ash/system/unified/quick_settings_view.cc
@@ -6,7 +6,6 @@
 
 #include <numeric>
 
-#include "ash/constants/ash_features.h"
 #include "ash/system/media/unified_media_controls_container.h"
 #include "ash/system/tray/interacted_by_tap_recorder.h"
 #include "ash/system/tray/tray_constants.h"
@@ -25,16 +24,21 @@
 #include "ui/accessibility/ax_node_data.h"
 #include "ui/base/metadata/metadata_impl_macros.h"
 #include "ui/compositor/layer.h"
+#include "ui/gfx/geometry/insets.h"
 #include "ui/views/controls/scroll_view.h"
 #include "ui/views/focus/focus_manager.h"
 #include "ui/views/layout/box_layout.h"
 #include "ui/views/layout/fill_layout.h"
 #include "ui/views/layout/flex_layout_view.h"
+#include "ui/views/view_class_properties.h"
 
 namespace ash {
 
 namespace {
 
+constexpr auto kPageIndicatorMargin = gfx::Insets::TLBR(0, 0, 8, 0);
+constexpr auto kSlidersContainerMargin = gfx::Insets::TLBR(4, 0, 0, 0);
+
 class DetailedViewContainer : public views::View {
  public:
   METADATA_HEADER(DetailedViewContainer);
@@ -144,6 +148,7 @@
           controller_, /*initially_expanded=*/controller_->model()
                                ->pagination_model()
                                ->total_pages() > 1));
+  page_indicator_view_->SetProperty(views::kMarginsKey, kPageIndicatorMargin);
 
   if (base::FeatureList::IsEnabled(media::kGlobalMediaControlsForChromeOS)) {
     media_controls_container_ = system_tray_container_->AddChildView(
@@ -154,6 +159,7 @@
   sliders_container_ = system_tray_container_->AddChildView(
       std::make_unique<views::FlexLayoutView>());
   sliders_container_->SetOrientation(views::LayoutOrientation::kVertical);
+  sliders_container_->SetProperty(views::kMarginsKey, kSlidersContainerMargin);
 
   footer_ = system_tray_container_->AddChildView(
       std::make_unique<QuickSettingsFooter>(controller_));
diff --git a/ash/wm/default_state.cc b/ash/wm/default_state.cc
index 4dd8fbb..ddeb579 100644
--- a/ash/wm/default_state.cc
+++ b/ash/wm/default_state.cc
@@ -660,20 +660,19 @@
   if (window_state->IsMinimized())
     return;
 
-  if (IsMinimizedWindowStateType(previous_state_type) ||
-      window_state->IsFullscreen() || window_state->IsPinned() ||
-      window_state->bounds_animation_type() ==
-          WindowState::BoundsChangeAnimationType::kNone) {
+  if (bool to_float = state_type_ == WindowStateType::kFloated;
+      to_float || previous_state_type == WindowStateType::kFloated) {
+    // Float and unfloat have their own animation.
+    window_state->SetBoundsDirectCrossFade(bounds_in_parent, to_float);
+  } else if (IsMinimizedWindowStateType(previous_state_type) ||
+             window_state->IsFullscreen() || window_state->IsPinned() ||
+             window_state->bounds_animation_type() ==
+                 WindowState::BoundsChangeAnimationType::kNone) {
     window_state->SetBoundsDirect(bounds_in_parent);
   } else if (window_state->IsMaximized() ||
              IsMaximizedOrFullscreenOrPinnedWindowStateType(
                  previous_state_type)) {
     window_state->SetBoundsDirectCrossFade(bounds_in_parent);
-  } else if (window_state->IsFloated() &&
-             previous_state_type == WindowStateType::kFloated) {
-    // This can happen during the tablet -> clamshell transition. Use cross fade
-    // animation for better performance.
-    window_state->SetBoundsDirectCrossFade(bounds_in_parent);
   } else if (window_state->is_dragged()) {
     // SetBoundsDirectAnimated does not work when the window gets reparented.
     // TODO(oshima): Consider fixing it and re-enable the animation.
diff --git a/ash/wm/desks/templates/saved_desk_unittest.cc b/ash/wm/desks/templates/saved_desk_unittest.cc
index 008b9e2..11428f9 100644
--- a/ash/wm/desks/templates/saved_desk_unittest.cc
+++ b/ash/wm/desks/templates/saved_desk_unittest.cc
@@ -403,13 +403,6 @@
         ->set_disable_app_id_check_for_saved_desks(disabled);
   }
 
-  SkBitmap GetFocusRingBitmap(views::FocusRing* focus_ring) {
-    gfx::Canvas canvas(focus_ring->size(), /*image_scale=*/1.0f,
-                       /*is_opaque=*/false);
-    focus_ring->OnPaint(&canvas);
-    return canvas.GetBitmap();
-  }
-
   SkBitmap GetBitmapWithInnerRoundedRect(gfx::Size size,
                                          int stroke_width,
                                          SkColor color) {
@@ -958,7 +951,7 @@
 }
 
 // Tests that the color of save desk button focus ring is as expected.
-TEST_F(SavedDeskTest, SaveDeskButtonFocusRingColor) {
+TEST_F(SavedDeskTest, SaveDeskButtonHighlight) {
   // Create a test window in the current desk.
   auto test_window = CreateAppWindow();
 
@@ -968,45 +961,24 @@
       GetSaveDeskAsTemplateButtonForRoot(root_window);
   auto* save_for_later_button = GetSaveDeskForLaterButtonForRoot(root_window);
 
-  // Verify the focus ring of the given button is as expected.
-  auto verify_button_focus_ring_color = [this](SavedDeskSaveDeskButton* button,
-                                               bool highlighted) {
-    EXPECT_EQ(cc::ExactPixelComparator().Compare(
-                  GetFocusRingBitmap(views::FocusRing::Get(button)),
-                  GetBitmapWithInnerRoundedRect(
-                      views::FocusRing::Get(button)->size(),
-                      /*stroke_width=*/2,
-                      ColorProvider::Get()->GetControlsLayerColor(
-                          ColorProvider::ControlsLayerType::kFocusRingColor))),
-              highlighted);
-  };
-
   // Both buttons are not highlighted.
   ASSERT_FALSE(save_as_template_button->IsViewHighlighted());
   ASSERT_FALSE(save_for_later_button->IsViewHighlighted());
-  verify_button_focus_ring_color(save_as_template_button, false);
-  verify_button_focus_ring_color(save_for_later_button, false);
 
   // Reverse tab, then save desk for later button is highlighted.
   SendKey(ui::VKEY_TAB, ui::EF_SHIFT_DOWN);
   ASSERT_FALSE(save_as_template_button->IsViewHighlighted());
   ASSERT_TRUE(save_for_later_button->IsViewHighlighted());
-  verify_button_focus_ring_color(save_as_template_button, false);
-  verify_button_focus_ring_color(save_for_later_button, true);
 
   // Reverse tab, then save desk as template button is highlighted.
   SendKey(ui::VKEY_TAB, ui::EF_SHIFT_DOWN);
   ASSERT_TRUE(save_as_template_button->IsViewHighlighted());
   ASSERT_FALSE(save_for_later_button->IsViewHighlighted());
-  verify_button_focus_ring_color(save_as_template_button, true);
-  verify_button_focus_ring_color(save_for_later_button, false);
 
   // Reverse tab, then both buttons are not highlighted.
   SendKey(ui::VKEY_TAB, ui::EF_SHIFT_DOWN);
   ASSERT_FALSE(save_as_template_button->IsViewHighlighted());
   ASSERT_FALSE(save_for_later_button->IsViewHighlighted());
-  verify_button_focus_ring_color(save_as_template_button, false);
-  verify_button_focus_ring_color(save_for_later_button, false);
 }
 
 // Tests that the save desk as template button and save for later button are
diff --git a/ash/wm/multitask_menu_nudge_controller_unittest.cc b/ash/wm/multitask_menu_nudge_controller_unittest.cc
index aa3d128..8343523 100644
--- a/ash/wm/multitask_menu_nudge_controller_unittest.cc
+++ b/ash/wm/multitask_menu_nudge_controller_unittest.cc
@@ -13,6 +13,8 @@
 #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_menu_event_handler.h"
+#include "ash/wm/tablet_mode/tablet_mode_window_manager.h"
 #include "ash/wm/window_state.h"
 #include "ash/wm/wm_event.h"
 #include "base/test/scoped_feature_list.h"
@@ -30,48 +32,76 @@
 
 namespace ash {
 
+namespace {
+
+// Returns the nudge controller associated with `window`.
+chromeos::MultitaskMenuNudgeController* GetNudgeControllerForWindow(
+    aura::Window* window) {
+  if (Shell::Get()->tablet_mode_controller()->InTabletMode()) {
+    return TabletModeControllerTestApi()
+        .tablet_mode_window_manager()
+        ->tablet_mode_multitask_menu_event_handler()
+        ->multitask_cue_for_testing()
+        ->nudge_controller_for_testing();
+  }
+
+  if (auto* frame = NonClientFrameViewAsh::Get(window)) {
+    return chromeos::FrameCaptionButtonContainerView::TestApi(
+               frame->GetHeaderView()->caption_button_container())
+        .nudge_controller();
+  }
+
+  return nullptr;
+}
+
+}  // namespace
+
 class MultitaskMenuNudgeControllerTest : public AshTestBase {
  public:
-  MultitaskMenuNudgeControllerTest() = default;
+  MultitaskMenuNudgeControllerTest()
+      : scoped_feature_list_(chromeos::wm::features::kWindowLayoutMenu) {}
   MultitaskMenuNudgeControllerTest(const MultitaskMenuNudgeControllerTest&) =
       delete;
   MultitaskMenuNudgeControllerTest& operator=(
       const MultitaskMenuNudgeControllerTest&) = delete;
   ~MultitaskMenuNudgeControllerTest() override = default;
 
-  views::Widget* GetWidget() { return controller_->nudge_widget_.get(); }
+  views::Widget* GetNudgeWidgetForWindow(aura::Window* window) {
+    chromeos::MultitaskMenuNudgeController* controller =
+        GetNudgeControllerForWindow(window);
+    return controller ? controller->nudge_widget_.get() : nullptr;
+  }
 
-  void FireDismissNudgeTimer() {
-    controller_->clamshell_nudge_dismiss_timer_.FireNow();
+  void FireDismissNudgeTimer(aura::Window* window) {
+    if (chromeos::MultitaskMenuNudgeController* controller =
+            GetNudgeControllerForWindow(window)) {
+      controller->clamshell_nudge_dismiss_timer_.FireNow();
+    }
   }
 
   // AshTestBase:
   void SetUp() override {
-    scoped_feature_list_.InitAndEnableFeature(
-        chromeos::wm::features::kWindowLayoutMenu);
-
     AshTestBase::SetUp();
 
     chromeos::MultitaskMenuNudgeController::SetSuppressNudgeForTesting(false);
-    controller_ = Shell::Get()->multitask_menu_nudge_controller();
-    controller_->SetOverrideClockForTesting(&test_clock_);
+    chromeos::MultitaskMenuNudgeController::SetOverrideClockForTesting(
+        &test_clock_);
 
     // Advance the test clock so we aren't at zero time.
     test_clock_.Advance(base::Hours(50));
   }
 
   void TearDown() override {
-    controller_->SetOverrideClockForTesting(nullptr);
+    chromeos::MultitaskMenuNudgeController::SetOverrideClockForTesting(nullptr);
 
     AshTestBase::TearDown();
   }
 
  protected:
-  base::SimpleTestClock test_clock_;
-
   // Tests that the tablet mode nudge bounds in screen are correct.
   void ExpectCorrectTabletNudgeBounds(aura::Window* window) {
-    const gfx::Size size = GetWidget()->GetContentsView()->GetPreferredSize();
+    const gfx::Size size =
+        GetNudgeWidgetForWindow(window)->GetContentsView()->GetPreferredSize();
     const auto window_screen_bounds = window->GetBoundsInScreen();
     const int tablet_nudge_y_offset =
         MultitaskMenuNudgeDelegateAsh::kTabletNudgeAdditionalYOffset +
@@ -82,12 +112,13 @@
             window_screen_bounds.x(),
         tablet_nudge_y_offset + window_screen_bounds.y(), size.width(),
         size.height());
-    EXPECT_EQ(expected_bounds, GetWidget()->GetWindowBoundsInScreen());
+    EXPECT_EQ(expected_bounds,
+              GetNudgeWidgetForWindow(window)->GetWindowBoundsInScreen());
   }
 
- private:
-  chromeos::MultitaskMenuNudgeController* controller_;
+  base::SimpleTestClock test_clock_;
 
+ private:
   base::test::ScopedFeatureList scoped_feature_list_;
 };
 
@@ -95,7 +126,7 @@
 // test for https://crbug.com/1341142.
 TEST_F(MultitaskMenuNudgeControllerTest, NoCrashAfterFullscreening) {
   auto window = CreateAppWindow(gfx::Rect(300, 300));
-  ASSERT_TRUE(GetWidget());
+  ASSERT_TRUE(GetNudgeWidgetForWindow(window.get()));
 
   // Turn of animations for immersive mode, so we don't have to wait for the top
   // container to hide on fullscreen.
@@ -106,7 +137,7 @@
 
   const WMEvent event(WM_EVENT_TOGGLE_FULLSCREEN);
   WindowState::Get(window.get())->OnWMEvent(&event);
-  EXPECT_FALSE(GetWidget());
+  EXPECT_FALSE(GetNudgeWidgetForWindow(window.get()));
 
   // Window needs to be immersive enabled, but not revealed for the bug to
   // reproduce.
@@ -114,7 +145,7 @@
   ASSERT_FALSE(immersive_controller->IsRevealed());
 
   WindowState::Get(window.get())->OnWMEvent(&event);
-  EXPECT_FALSE(GetWidget());
+  EXPECT_FALSE(GetNudgeWidgetForWindow(window.get()));
 }
 
 // Tests that there is no crash after floating a window via the multitask menu.
@@ -122,7 +153,7 @@
 TEST_F(MultitaskMenuNudgeControllerTest,
        NoCrashAfterFloatingFromMultitaskMenu) {
   auto window = CreateAppWindow(gfx::Rect(300, 300));
-  ASSERT_TRUE(GetWidget());
+  ASSERT_TRUE(GetNudgeWidgetForWindow(window.get()));
 
   // Float the window from the multitask menu. Floating the window using the
   // accelerator does not cause the crash mentioned in the bug because the
@@ -150,25 +181,15 @@
                                        .CenterPoint());
   GetEventGenerator()->ClickLeftButton();
   ASSERT_TRUE(WindowState::Get(window.get())->IsFloated());
-  EXPECT_TRUE(GetWidget());
+  EXPECT_TRUE(GetNudgeWidgetForWindow(window.get()));
 }
 
 TEST_F(MultitaskMenuNudgeControllerTest, NudgeTimeout) {
   auto window = CreateAppWindow(gfx::Rect(300, 300));
-  ASSERT_TRUE(GetWidget());
+  ASSERT_TRUE(GetNudgeWidgetForWindow(window.get()));
 
-  FireDismissNudgeTimer();
-  EXPECT_FALSE(GetWidget());
-}
-
-// Tests that if a window gets destroyed while the nduge is showing, the nudge
-// disappears and there is no crash.
-TEST_F(MultitaskMenuNudgeControllerTest, WindowDestroyedWhileNudgeShown) {
-  auto window = CreateAppWindow(gfx::Rect(300, 300));
-  ASSERT_TRUE(GetWidget());
-
-  window.reset();
-  EXPECT_FALSE(GetWidget());
+  FireDismissNudgeTimer(window.get());
+  EXPECT_FALSE(GetNudgeWidgetForWindow(window.get()));
 }
 
 TEST_F(MultitaskMenuNudgeControllerTest, NudgeMultiDisplay) {
@@ -176,7 +197,7 @@
   ASSERT_EQ(2u, Shell::GetAllRootWindows().size());
 
   auto window = CreateAppWindow(gfx::Rect(300, 300));
-  ASSERT_TRUE(GetWidget());
+  ASSERT_TRUE(GetNudgeWidgetForWindow(window.get()));
 
   // Drag from the caption the window to the other display. The nudge should be
   // on the other display, even though the window is not (the window stays
@@ -186,55 +207,58 @@
   event_generator->set_current_screen_location(gfx::Point(150, 10));
   event_generator->PressLeftButton();
   event_generator->MoveMouseTo(gfx::Point(900, 0));
-  EXPECT_EQ(Shell::GetAllRootWindows()[1],
-            GetWidget()->GetNativeWindow()->GetRootWindow());
+  EXPECT_EQ(Shell::GetAllRootWindows()[1], GetNudgeWidgetForWindow(window.get())
+                                               ->GetNativeWindow()
+                                               ->GetRootWindow());
 
   event_generator->ReleaseLeftButton();
-  EXPECT_EQ(Shell::GetAllRootWindows()[1],
-            GetWidget()->GetNativeWindow()->GetRootWindow());
+  EXPECT_EQ(Shell::GetAllRootWindows()[1], GetNudgeWidgetForWindow(window.get())
+                                               ->GetNativeWindow()
+                                               ->GetRootWindow());
 
   display_move_window_util::HandleMoveActiveWindowBetweenDisplays();
-  EXPECT_EQ(Shell::GetAllRootWindows()[0],
-            GetWidget()->GetNativeWindow()->GetRootWindow());
+  EXPECT_EQ(Shell::GetAllRootWindows()[0], GetNudgeWidgetForWindow(window.get())
+                                               ->GetNativeWindow()
+                                               ->GetRootWindow());
 }
 
 // Tests that based on preferences (shown count, and last shown time), the nudge
 // may or may not be shown.
 TEST_F(MultitaskMenuNudgeControllerTest, NudgePreferences) {
   auto window = CreateAppWindow(gfx::Rect(300, 300));
-  ASSERT_TRUE(GetWidget());
-  FireDismissNudgeTimer();
-  ASSERT_FALSE(GetWidget());
+  ASSERT_TRUE(GetNudgeWidgetForWindow(window.get()));
+  FireDismissNudgeTimer(window.get());
+  ASSERT_FALSE(GetNudgeWidgetForWindow(window.get()));
 
   // Create the window. This does not show the nudge as 24 hours have not
   // elapsed since the nudge was shown.
   window.reset();
   window = CreateAppWindow(gfx::Rect(300, 300));
-  ASSERT_FALSE(GetWidget());
+  ASSERT_FALSE(GetNudgeWidgetForWindow(window.get()));
 
   // Create the window again after waiting 25 hours. The nudge should now show
   // for the second time.
   test_clock_.Advance(base::Hours(25));
   window.reset();
   window = CreateAppWindow(gfx::Rect(300, 300));
-  ASSERT_TRUE(GetWidget());
-  FireDismissNudgeTimer();
-  ASSERT_FALSE(GetWidget());
+  ASSERT_TRUE(GetNudgeWidgetForWindow(window.get()));
+  FireDismissNudgeTimer(window.get());
+  ASSERT_FALSE(GetNudgeWidgetForWindow(window.get()));
 
   // Show the nudge for a third time. This will be the last time it is shown.
   test_clock_.Advance(base::Hours(25));
   window.reset();
   window = CreateAppWindow(gfx::Rect(300, 300));
-  ASSERT_TRUE(GetWidget());
-  FireDismissNudgeTimer();
-  ASSERT_FALSE(GetWidget());
+  ASSERT_TRUE(GetNudgeWidgetForWindow(window.get()));
+  FireDismissNudgeTimer(window.get());
+  ASSERT_FALSE(GetNudgeWidgetForWindow(window.get()));
 
   // Advance the clock and attempt to show the nudge for a forth time. Verify
   // that it will not show.
   test_clock_.Advance(base::Hours(25));
   window.reset();
   window = CreateAppWindow(gfx::Rect(300, 300));
-  EXPECT_FALSE(GetWidget());
+  EXPECT_FALSE(GetNudgeWidgetForWindow(window.get()));
 }
 
 // Tests that the nudge works in tablet mode, and that its bounds in screen are
@@ -244,7 +268,7 @@
 
   // The widget should appear the first time a window is activated.
   auto window = CreateAppWindow();
-  ASSERT_TRUE(GetWidget());
+  ASSERT_TRUE(GetNudgeWidgetForWindow(window.get()));
 
   // Test that the widget is shown at the correct bounds when the window is
   // first created.
@@ -257,15 +281,27 @@
   // snapped in the primary position.
   split_view_controller->SnapWindow(
       window.get(), SplitViewController::SnapPosition::kPrimary);
-  ASSERT_TRUE(GetWidget());
+  ASSERT_TRUE(GetNudgeWidgetForWindow(window.get()));
   ExpectCorrectTabletNudgeBounds(window.get());
 
   // Tests that the widget is shown at the correct bounds when the window is
   // snapped in the secondary position.
   split_view_controller->SnapWindow(
       window.get(), SplitViewController::SnapPosition::kSecondary);
-  ASSERT_TRUE(GetWidget());
+  ASSERT_TRUE(GetNudgeWidgetForWindow(window.get()));
   ExpectCorrectTabletNudgeBounds(window.get());
 }
 
+// Tests that if a window gets destroyed while the nduge is showing in tablet
+// mode, the nudge disappears and there is no crash.
+TEST_F(MultitaskMenuNudgeControllerTest, TabletWindowDestroyedWhileNudgeShown) {
+  TabletModeControllerTestApi().EnterTabletMode();
+
+  auto window = CreateAppWindow(gfx::Rect(300, 300));
+  ASSERT_TRUE(GetNudgeWidgetForWindow(window.get()));
+
+  window.reset();
+  EXPECT_FALSE(GetNudgeWidgetForWindow(window.get()));
+}
+
 }  // namespace ash
diff --git a/ash/wm/tablet_mode/tablet_mode_multitask_cue.cc b/ash/wm/tablet_mode/tablet_mode_multitask_cue.cc
index 84aae73d..7a4b3d5 100644
--- a/ash/wm/tablet_mode/tablet_mode_multitask_cue.cc
+++ b/ash/wm/tablet_mode/tablet_mode_multitask_cue.cc
@@ -8,7 +8,6 @@
 #include "ash/shell.h"
 #include "ash/wm/window_state.h"
 #include "ash/wm/window_util.h"
-#include "chromeos/ui/frame/multitask_menu/multitask_menu_nudge_controller.h"
 #include "chromeos/ui/wm/features.h"
 #include "ui/aura/client/aura_constants.h"
 #include "ui/views/animation/animation_builder.h"
@@ -60,7 +59,6 @@
   // dismissed before it can be shown again. If the user activates a floatable
   // or non-maximizable window, any existing cue should still be dismissed.
   DismissCue();
-  Shell::Get()->multitask_menu_nudge_controller()->DismissNudge();
 
   // Floated windows do not have the multitask menu.
   // TODO(hewer): Consolidate checks with ones for multitask menu in a helper.
@@ -101,8 +99,7 @@
                            &TabletModeMultitaskCue::OnTimerFinished);
 
   // Show the education nudge a maximum of three times with 24h in between.
-  DCHECK(Shell::Get()->multitask_menu_nudge_controller());
-  Shell::Get()->multitask_menu_nudge_controller()->MaybeShowNudge(window_);
+  nudge_controller_.MaybeShowNudge(window_);
 }
 
 void TabletModeMultitaskCue::DismissCue() {
@@ -117,8 +114,7 @@
   cue_layer_.reset();
 
   // The education nudge should not appear without the cue.
-  DCHECK(Shell::Get()->multitask_menu_nudge_controller());
-  Shell::Get()->multitask_menu_nudge_controller()->DismissNudge();
+  nudge_controller_.DismissNudge();
 }
 
 void TabletModeMultitaskCue::OnWindowDestroying(aura::Window* window) {
diff --git a/ash/wm/tablet_mode/tablet_mode_multitask_cue.h b/ash/wm/tablet_mode/tablet_mode_multitask_cue.h
index 539722e..e2cd45c 100644
--- a/ash/wm/tablet_mode/tablet_mode_multitask_cue.h
+++ b/ash/wm/tablet_mode/tablet_mode_multitask_cue.h
@@ -11,6 +11,7 @@
 #include "ash/wm/window_state_observer.h"
 #include "base/scoped_observation.h"
 #include "base/timer/timer.h"
+#include "chromeos/ui/frame/multitask_menu/multitask_menu_nudge_controller.h"
 #include "ui/aura/window_observer.h"
 #include "ui/compositor/layer.h"
 #include "ui/wm/public/activation_change_observer.h"
@@ -60,11 +61,12 @@
   void OnPostWindowStateTypeChange(WindowState* window_state,
                                    chromeos::WindowStateType old_type) override;
 
+  chromeos::MultitaskMenuNudgeController* nudge_controller_for_testing() {
+    return &nudge_controller_;
+  }
   void FireCueDismissTimerForTesting() { cue_dismiss_timer_.FireNow(); }
 
  private:
-  friend class TabletModeMultitaskCueTest;
-
   // Updates the bounds of the cue relative to the window if the window is
   // still available.
   void UpdateCueBounds();
@@ -76,6 +78,9 @@
   // The app window that the cue is associated with.
   aura::Window* window_ = nullptr;
 
+  // Handles showing the educational nudge for the tablet multitask menu.
+  chromeos::MultitaskMenuNudgeController nudge_controller_;
+
   // The solid color layer that represents the cue.
   std::unique_ptr<ui::Layer> cue_layer_;
 
@@ -89,4 +94,4 @@
 
 }  // namespace ash
 
-#endif  // ASH_WM_TABLET_MODE_TABLET_MODE_MULTITASK_CUE_H_
\ No newline at end of file
+#endif  // ASH_WM_TABLET_MODE_TABLET_MODE_MULTITASK_CUE_H_
diff --git a/ash/wm/tablet_mode/tablet_mode_multitask_cue_unittest.cc b/ash/wm/tablet_mode/tablet_mode_multitask_cue_unittest.cc
index 64d114f..a9dc39f1 100644
--- a/ash/wm/tablet_mode/tablet_mode_multitask_cue_unittest.cc
+++ b/ash/wm/tablet_mode/tablet_mode_multitask_cue_unittest.cc
@@ -36,7 +36,7 @@
   TabletModeMultitaskCue* GetMultitaskCue() {
     return TabletModeControllerTestApi()
         .tablet_mode_window_manager()
-        ->tablet_mode_multitask_menu_event_handler_for_testing()
+        ->tablet_mode_multitask_menu_event_handler()
         ->multitask_cue_for_testing();
   }
 
diff --git a/ash/wm/tablet_mode/tablet_mode_multitask_menu_event_handler.cc b/ash/wm/tablet_mode/tablet_mode_multitask_menu_event_handler.cc
index d15e626..b822bb0 100644
--- a/ash/wm/tablet_mode/tablet_mode_multitask_menu_event_handler.cc
+++ b/ash/wm/tablet_mode/tablet_mode_multitask_menu_event_handler.cc
@@ -36,60 +36,16 @@
   Shell::Get()->RemovePreTargetHandler(this);
 }
 
-void TabletModeMultitaskMenuEventHandler::MaybeCreateMultitaskMenu(
-    aura::Window* active_window) {
-  if (!multitask_menu_) {
-    multitask_menu_ =
-        std::make_unique<TabletModeMultitaskMenu>(this, active_window);
-
-    multitask_cue_->DismissCue();
-  }
+void TabletModeMultitaskMenuEventHandler::ShowMultitaskMenu(
+    aura::Window* window) {
+  MaybeCreateMultitaskMenu(window);
+  multitask_menu_->Animate(/*show=*/true);
 }
 
 void TabletModeMultitaskMenuEventHandler::ResetMultitaskMenu() {
   multitask_menu_.reset();
 }
 
-void TabletModeMultitaskMenuEventHandler::OnMouseEvent(ui::MouseEvent* event) {
-  if (event->type() != ui::ET_MOUSEWHEEL)
-    return;
-
-  // Note that connecting a mouse normally puts the device in clamshell mode
-  // unless a developer switch is enabled.
-  if (!debug::DeveloperAcceleratorsEnabled())
-    return;
-
-  const float y_offset = event->AsMouseWheelEvent()->y_offset();
-  if (y_offset == 0.f)
-    return;
-
-  aura::Window* target = static_cast<aura::Window*>(event->target());
-
-  // Close the multitask menu if it is the target and we have a upwards scroll.
-  if (y_offset > 0.f && multitask_menu_ &&
-      target == multitask_menu_->widget()->GetNativeWindow()) {
-    multitask_menu_->Animate(/*show=*/false);
-    return;
-  }
-
-  if (multitask_menu_)
-    return;
-
-  aura::Window* active_window = window_util::GetActiveWindow();
-  if (!active_window || !active_window->Contains(target) ||
-      !WindowState::Get(active_window)->CanMaximize()) {
-    return;
-  }
-
-  // Show the multitask menu if it is in the top quarter of the target and is a
-  // downwards scroll.
-  if (y_offset < 0.f &&
-      event->location_f().y() < target->bounds().height() / 4.f) {
-    MaybeCreateMultitaskMenu(active_window);
-    multitask_menu_->Animate(/*show=*/true);
-  }
-}
-
 void TabletModeMultitaskMenuEventHandler::OnTouchEvent(ui::TouchEvent* event) {
   // The event target may be the active window, multitask menu, or multitask
   // cue, so convert to screen coordinates for consistency.
@@ -206,4 +162,13 @@
   return !window_state->IsFloated() && window_state->CanMaximize();
 }
 
+void TabletModeMultitaskMenuEventHandler::MaybeCreateMultitaskMenu(
+    aura::Window* active_window) {
+  if (!multitask_menu_) {
+    multitask_menu_ =
+        std::make_unique<TabletModeMultitaskMenu>(this, active_window);
+    multitask_cue_->DismissCue();
+  }
+}
+
 }  // namespace ash
diff --git a/ash/wm/tablet_mode/tablet_mode_multitask_menu_event_handler.h b/ash/wm/tablet_mode/tablet_mode_multitask_menu_event_handler.h
index e1a22be..5223121 100644
--- a/ash/wm/tablet_mode/tablet_mode_multitask_menu_event_handler.h
+++ b/ash/wm/tablet_mode/tablet_mode_multitask_menu_event_handler.h
@@ -26,15 +26,13 @@
       const TabletModeMultitaskMenuEventHandler&) = delete;
   ~TabletModeMultitaskMenuEventHandler() override;
 
-  void MaybeCreateMultitaskMenu(aura::Window* active_window);
+  // Creates and shows the menu.
+  void ShowMultitaskMenu(aura::Window* window);
 
   // Destroys the multitask menu.
   void ResetMultitaskMenu();
 
   // ui::EventHandler:
-  // TODO(crbug.com/1336836): Temporarily allow mouse wheel events to show or
-  // hide the multitask menu for developers. Remove this before launch.
-  void OnMouseEvent(ui::MouseEvent* event) override;
   void OnTouchEvent(ui::TouchEvent* event) override;
 
   TabletModeMultitaskMenu* multitask_menu_for_testing() {
@@ -55,6 +53,8 @@
 
   bool CanProcessEvent(aura::Window* window) const;
 
+  void MaybeCreateMultitaskMenu(aura::Window* active_window);
+
   // Valid if we may need to handle the event.
   absl::optional<InitialDragData> initial_drag_data_;
 
diff --git a/ash/wm/tablet_mode/tablet_mode_multitask_menu_event_handler_unittest.cc b/ash/wm/tablet_mode/tablet_mode_multitask_menu_event_handler_unittest.cc
index 7624ecc9..43d8532fa 100644
--- a/ash/wm/tablet_mode/tablet_mode_multitask_menu_event_handler_unittest.cc
+++ b/ash/wm/tablet_mode/tablet_mode_multitask_menu_event_handler_unittest.cc
@@ -94,13 +94,13 @@
   TabletModeMultitaskMenuEventHandler* GetMultitaskMenuEventHandler() {
     return TabletModeControllerTestApi()
         .tablet_mode_window_manager()
-        ->tablet_mode_multitask_menu_event_handler_for_testing();
+        ->tablet_mode_multitask_menu_event_handler();
   }
 
   TabletModeMultitaskMenu* GetMultitaskMenu() {
     return TabletModeControllerTestApi()
         .tablet_mode_window_manager()
-        ->tablet_mode_multitask_menu_event_handler_for_testing()
+        ->tablet_mode_multitask_menu_event_handler()
         ->multitask_menu_for_testing();
   }
 
@@ -314,10 +314,9 @@
 
   ShowMultitaskMenu(*window);
 
-  auto* event_handler =
-      TabletModeControllerTestApi()
-          .tablet_mode_window_manager()
-          ->tablet_mode_multitask_menu_event_handler_for_testing();
+  auto* event_handler = TabletModeControllerTestApi()
+                            .tablet_mode_window_manager()
+                            ->tablet_mode_multitask_menu_event_handler();
   auto* multitask_menu = event_handler->multitask_menu_for_testing();
   ASSERT_TRUE(multitask_menu);
   ASSERT_TRUE(multitask_menu->widget()->GetContentsView()->GetVisible());
diff --git a/ash/wm/tablet_mode/tablet_mode_toggle_fullscreen_event_handler_unittest.cc b/ash/wm/tablet_mode/tablet_mode_toggle_fullscreen_event_handler_unittest.cc
index 36ebc06a..20539cb 100644
--- a/ash/wm/tablet_mode/tablet_mode_toggle_fullscreen_event_handler_unittest.cc
+++ b/ash/wm/tablet_mode/tablet_mode_toggle_fullscreen_event_handler_unittest.cc
@@ -65,11 +65,10 @@
 
     // Swiping down on the center reveals the tablet mode multitask menu. Ensure
     // our swipes do not reveal it, as it may eat following gestures.
-    auto* multitask_menu =
-        TabletModeControllerTestApi()
-            .tablet_mode_window_manager()
-            ->tablet_mode_multitask_menu_event_handler_for_testing()
-            ->multitask_menu_for_testing();
+    auto* multitask_menu = TabletModeControllerTestApi()
+                               .tablet_mode_window_manager()
+                               ->tablet_mode_multitask_menu_event_handler()
+                               ->multitask_menu_for_testing();
     ASSERT_FALSE(multitask_menu);
   }
 
diff --git a/ash/wm/tablet_mode/tablet_mode_window_manager.h b/ash/wm/tablet_mode/tablet_mode_window_manager.h
index 9dd0692..98c8e815 100644
--- a/ash/wm/tablet_mode/tablet_mode_window_manager.h
+++ b/ash/wm/tablet_mode/tablet_mode_window_manager.h
@@ -53,6 +53,11 @@
 
   ~TabletModeWindowManager() override;
 
+  TabletModeMultitaskMenuEventHandler*
+  tablet_mode_multitask_menu_event_handler() {
+    return tablet_mode_multitask_menu_event_handler_.get();
+  }
+
   void Init();
 
   // Stops tracking windows and returns them to their clamshell mode state. Work
@@ -107,11 +112,6 @@
   // SessionObserver:
   void OnActiveUserSessionChanged(const AccountId& account_id) override;
 
-  TabletModeMultitaskMenuEventHandler*
-  tablet_mode_multitask_menu_event_handler_for_testing() {
-    return tablet_mode_multitask_menu_event_handler_.get();
-  }
-
  private:
   using WindowToState = std::map<aura::Window*, TabletModeWindowState*>;
   using WindowAndStateTypeList =
diff --git a/ash/wm/tablet_mode/tablet_mode_window_state.cc b/ash/wm/tablet_mode/tablet_mode_window_state.cc
index 3dfd105..85fae3bb 100644
--- a/ash/wm/tablet_mode/tablet_mode_window_state.cc
+++ b/ash/wm/tablet_mode/tablet_mode_window_state.cc
@@ -304,6 +304,9 @@
     return;
   }
 
+  const chromeos::WindowStateType previous_state_type =
+      window_state->GetStateType();
+
   switch (event->type()) {
     case WM_EVENT_TOGGLE_FULLSCREEN:
       ToggleFullScreen(window_state, window_state->delegate());
@@ -388,15 +391,19 @@
       if (bounds_in_parent.IsEmpty())
         return;
 
-      if (current_state_type_ == WindowStateType::kFloated ||
-          window_util::IsDraggingTabs(window_state->window()) ||
-          IsTabDraggingSourceWindow(window_state->window()) ||
-          TabDragDropDelegate::IsSourceWindowForDrag(window_state->window()) ||
-          BoundsChangeIsFromVKAndAllowed(window_state->window())) {
+      if (bool to_float = current_state_type_ == WindowStateType::kFloated;
+          to_float || previous_state_type == WindowStateType::kFloated) {
         // Floated windows in tablet mode are freeform, so they can placed
-        // anywhere, not just centered. Also, if the window is the current
-        // tab-dragged window or the current tab- dragged window's source
-        // window, we may need to update its bounds during dragging.
+        // anywhere, not just centered.
+        window_state->SetBoundsDirectCrossFade(bounds_in_parent, to_float);
+      } else if (window_util::IsDraggingTabs(window_state->window()) ||
+                 IsTabDraggingSourceWindow(window_state->window()) ||
+                 TabDragDropDelegate::IsSourceWindowForDrag(
+                     window_state->window()) ||
+                 BoundsChangeIsFromVKAndAllowed(window_state->window())) {
+        // If the window is the current tab-dragged window or the current tab-
+        // dragged window's source window, we may need to update its bounds
+        // during dragging.
         window_state->SetBoundsDirect(bounds_in_parent);
       } else if (current_state_type_ == WindowStateType::kMaximized) {
         // Having a maximized window, it could have been created with an empty
diff --git a/ash/wm/window_animations.cc b/ash/wm/window_animations.cc
index e98b1125..8f5feea 100644
--- a/ash/wm/window_animations.cc
+++ b/ash/wm/window_animations.cc
@@ -55,6 +55,10 @@
 
 constexpr base::TimeDelta kCrossFadeMaxDuration = base::Milliseconds(400);
 
+// The default duration for an animation to float or unfloat a window.
+static constexpr base::TimeDelta kFloatUnfloatDuration =
+    base::Milliseconds(400);
+
 // Durations for the brightness/grayscale fade animation, in milliseconds.
 const int kBrightnessGrayscaleFadeDurationMs = 1000;
 
@@ -628,11 +632,23 @@
 void CrossFadeAnimation(aura::Window* window,
                         std::unique_ptr<ui::LayerTreeOwner> old_layer_owner) {
   CrossFadeAnimationInternal(
-      window, std::move(old_layer_owner), /*animate_old_layer=*/true,
+      window, std::move(old_layer_owner), /*animate_old_layer_transform=*/true,
       /*duration=*/absl::nullopt, /*tween_type=*/absl::nullopt,
       /*histogram_name=*/absl::nullopt);
 }
 
+void CrossFadeAnimationForFloatUnfloat(
+    aura::Window* window,
+    std::unique_ptr<ui::LayerTreeOwner> old_layer_owner,
+    bool to_float) {
+  CrossFadeAnimationInternal(window, std::move(old_layer_owner),
+                             /*animate_old_layer_transform=*/true,
+                             kFloatUnfloatDuration,
+                             to_float ? gfx::Tween::Type::ACCEL_30_DECEL_20_85
+                                      : gfx::Tween::Type::FAST_OUT_SLOW_IN_3,
+                             /*histogram_name=*/absl::nullopt);
+}
+
 void CrossFadeAnimationAnimateNewLayerOnly(aura::Window* window,
                                            const gfx::Rect& target_bounds,
                                            base::TimeDelta duration,
diff --git a/ash/wm/window_animations.h b/ash/wm/window_animations.h
index 05a56773..10dd9ce7 100644
--- a/ash/wm/window_animations.h
+++ b/ash/wm/window_animations.h
@@ -36,12 +36,19 @@
     LayerScaleAnimationDirection type);
 
 // Implementation of cross fading. Window is the window being cross faded. It
-// should be at the target bounds. |old_layer_owner| contains the previous layer
-// from |window|.
+// should be at the target bounds. `old_layer_owner` contains the previous layer
+// from `window`.
 ASH_EXPORT void CrossFadeAnimation(
     aura::Window* window,
     std::unique_ptr<ui::LayerTreeOwner> old_layer_owner);
 
+// Implementation of cross fading for floating/unfloating a window. If
+// `to_float` is true, animates to floated state, else animates unfloat.
+ASH_EXPORT void CrossFadeAnimationForFloatUnfloat(
+    aura::Window* window,
+    std::unique_ptr<ui::LayerTreeOwner> old_layer_owner,
+    bool to_float);
+
 // Implementation of cross fading which only animates the new layer. The old
 // layer will be owned by an observer which will update the transform as the new
 // layer's transform and bounds change. This is used by the
diff --git a/ash/wm/window_state.cc b/ash/wm/window_state.cc
index 19b8515..052b363 100644
--- a/ash/wm/window_state.cc
+++ b/ash/wm/window_state.cc
@@ -1037,7 +1037,8 @@
   SetBoundsDirect(bounds);
 }
 
-void WindowState::SetBoundsDirectCrossFade(const gfx::Rect& new_bounds) {
+void WindowState::SetBoundsDirectCrossFade(const gfx::Rect& new_bounds,
+                                           absl::optional<bool> float_state) {
   // Some test results in invoking CrossFadeToBounds when window is not visible.
   // No animation is necessary in that case, thus just change the bounds and
   // quit.
@@ -1065,6 +1066,12 @@
   // Resize the window to the new size, which will force a layout and paint.
   SetBoundsDirect(new_bounds);
 
+  if (float_state) {
+    CrossFadeAnimationForFloatUnfloat(window_, std::move(old_layer_owner),
+                                      *float_state);
+    return;
+  }
+
   CrossFadeAnimation(window_, std::move(old_layer_owner));
 }
 
diff --git a/ash/wm/window_state.h b/ash/wm/window_state.h
index 8d22f89..63b276f2 100644
--- a/ash/wm/window_state.h
+++ b/ash/wm/window_state.h
@@ -536,9 +536,12 @@
       base::TimeDelta duration = kBoundsChangeSlideDuration,
       gfx::Tween::Type animation_type = gfx::Tween::LINEAR);
 
-  // Sets the window's |bounds| and transition to the new bounds with
-  // a cross fade animation.
-  void SetBoundsDirectCrossFade(const gfx::Rect& bounds);
+  // Sets the window's `bounds` and transition to the new bounds with
+  // a cross fade animation. If `float_state` has a value, sets a custom
+  // float/unfloat cross fade animation.
+  void SetBoundsDirectCrossFade(
+      const gfx::Rect& bounds,
+      absl::optional<bool> float_state = absl::nullopt);
 
   // Called before the state change and update PIP related state, such as next
   // window animation type, upon state change.
diff --git a/base/allocator/partition_allocator/pointers/raw_ptr.h b/base/allocator/partition_allocator/pointers/raw_ptr.h
index 65445c70..ac926de5 100644
--- a/base/allocator/partition_allocator/pointers/raw_ptr.h
+++ b/base/allocator/partition_allocator/pointers/raw_ptr.h
@@ -686,12 +686,13 @@
       /*allow_dangling=*/Contains(Traits, RawPtrTraits::kMayDangle)>;
 
 #elif BUILDFLAG(USE_ASAN_UNOWNED_PTR)
-  using UnderlyingImpl =
-      std::conditional_t<Contains(Traits, RawPtrTraits::kMayDangle),
-                         // No special bookkeeping required for this case,
-                         // just treat these as ordinary pointers.
-                         internal::RawPtrNoOpImpl,
-                         internal::RawPtrAsanUnownedImpl>;
+  using UnderlyingImpl = std::conditional_t<
+      Contains(Traits, RawPtrTraits::kMayDangle),
+      // No special bookkeeping required for this case,
+      // just treat these as ordinary pointers.
+      internal::RawPtrNoOpImpl,
+      internal::RawPtrAsanUnownedImpl<
+          Contains(Traits, RawPtrTraits::kAllowPtrArithmetic)>>;
 #elif PA_CONFIG(ENABLE_MTE_CHECKED_PTR_SUPPORT_WITH_64_BITS_POINTERS)
   using UnderlyingImpl =
       std::conditional_t<Contains(Traits, RawPtrTraits::kDisableMTECheckedPtr),
diff --git a/base/allocator/partition_allocator/pointers/raw_ptr_asan_unowned_impl.cc b/base/allocator/partition_allocator/pointers/raw_ptr_asan_unowned_impl.cc
index b372f8e..fb305931 100644
--- a/base/allocator/partition_allocator/pointers/raw_ptr_asan_unowned_impl.cc
+++ b/base/allocator/partition_allocator/pointers/raw_ptr_asan_unowned_impl.cc
@@ -12,21 +12,34 @@
 namespace base::internal {
 
 PA_NO_SANITIZE("address")
-bool RawPtrAsanUnownedImpl::EndOfAliveAllocation(const volatile void* ptr) {
+bool EndOfAliveAllocation(const volatile void* ptr, bool is_adjustable_ptr) {
   uintptr_t address = reinterpret_cast<uintptr_t>(ptr);
 
+  // Normally, we probe the first byte of an object, but in cases of pointer
+  // arithmetic, we may be probing subsequent bytes, including the legal
+  // "end + 1" position.
+  //
   // Alas, ASAN will claim an unmapped page is unpoisoned, so willfully ignore
   // the fist address of a page, since "end + 1" of an object allocated exactly
   // up to a page  boundary will SEGV on probe. This will cause false negatives
   // for pointers that happen to be page aligned, which is undesirable but
   // necessary for now.
-  // TODO(tsepez): investigate pointer tracking approaches to avoid this.
-  return ((address & 0x0fff) == 0 ||
+  //
+  // We minimize the consequences by using the pointer arithmetic flag in
+  // higher levels to conditionalize this suppression.
+  //
+  // TODO(tsepez): this may still fail for a non-accessible but non-null
+  // return from, say, malloc(0) which happens to be page-aligned.
+  //
+  // TODO(tsepez): enforce the pointer arithmetic flag. Until then, we
+  // may fail here if a pointer requires the flag but is lacking it.
+  return is_adjustable_ptr &&
+         ((address & 0x0fff) == 0 ||
           __asan_region_is_poisoned(reinterpret_cast<void*>(address), 1)) &&
          !__asan_region_is_poisoned(reinterpret_cast<void*>(address - 1), 1);
 }
 
-bool RawPtrAsanUnownedImpl::LikelySmuggledScalar(const volatile void* ptr) {
+bool LikelySmuggledScalar(const volatile void* ptr) {
   intptr_t address = reinterpret_cast<intptr_t>(ptr);
   return address < 0x4000;  // Negative or small positive.
 }
diff --git a/base/allocator/partition_allocator/pointers/raw_ptr_asan_unowned_impl.h b/base/allocator/partition_allocator/pointers/raw_ptr_asan_unowned_impl.h
index 4fe1884e..1fac1a9 100644
--- a/base/allocator/partition_allocator/pointers/raw_ptr_asan_unowned_impl.h
+++ b/base/allocator/partition_allocator/pointers/raw_ptr_asan_unowned_impl.h
@@ -19,6 +19,10 @@
 
 namespace base::internal {
 
+bool EndOfAliveAllocation(const volatile void* ptr, bool is_adjustable_ptr);
+bool LikelySmuggledScalar(const volatile void* ptr);
+
+template <bool IsAdjustablePtr>
 struct RawPtrAsanUnownedImpl {
   // Wraps a pointer.
   template <typename T>
@@ -91,14 +95,11 @@
   template <typename T>
   static void ProbeForLowSeverityLifetimeIssue(T* wrapped_ptr) {
     if (wrapped_ptr && !LikelySmuggledScalar(wrapped_ptr) &&
-        !EndOfAliveAllocation(wrapped_ptr)) {
+        !EndOfAliveAllocation(wrapped_ptr, IsAdjustablePtr)) {
       reinterpret_cast<const volatile uint8_t*>(wrapped_ptr)[0];
     }
   }
 
-  static bool EndOfAliveAllocation(const volatile void* ptr);
-  static bool LikelySmuggledScalar(const volatile void* ptr);
-
   // `WrapRawPtrForDuplication` and `UnsafelyUnwrapPtrForDuplication` are used
   // to create a new raw_ptr<T> from another raw_ptr<T> of a different flavor.
   template <typename T>
diff --git a/base/allocator/partition_allocator/pointers/raw_ref.h b/base/allocator/partition_allocator/pointers/raw_ref.h
index cdd5cf7..cb844a3b 100644
--- a/base/allocator/partition_allocator/pointers/raw_ref.h
+++ b/base/allocator/partition_allocator/pointers/raw_ref.h
@@ -79,7 +79,8 @@
                          internal::MTECheckedPtrImplPartitionAllocSupport>> ||
 #endif  // PA_CONFIG(ENABLE_MTE_CHECKED_PTR_SUPPORT_WITH_64_BITS_POINTERS)
 #if BUILDFLAG(USE_ASAN_UNOWNED_PTR)
-      std::is_same_v<Impl, internal::RawPtrAsanUnownedImpl> ||
+      std::is_same_v<Impl, internal::RawPtrAsanUnownedImpl<true>> ||
+      std::is_same_v<Impl, internal::RawPtrAsanUnownedImpl<false>> ||
 #endif  // BUILDFLAG(USE_ASAN_UNOWNED_PTR)
       std::is_same_v<Impl, internal::RawPtrNoOpImpl>;
 
diff --git a/base/metrics/sample_vector.cc b/base/metrics/sample_vector.cc
index e3cdc5d..2a46445 100644
--- a/base/metrics/sample_vector.cc
+++ b/base/metrics/sample_vector.cc
@@ -276,6 +276,12 @@
   if (sample.count == 0)
     return;
 
+  // Stop here if the sample bucket would be out of range for the AtomicCount
+  // array.
+  if (sample.bucket >= counts_size()) {
+    return;
+  }
+
   // Move the value into storage. Sum and redundant-count already account
   // for this entry so no need to call IncreaseSumAndCount().
   subtle::NoBarrier_AtomicIncrement(&counts()[sample.bucket], sample.count);
diff --git a/build/config/compiler/BUILD.gn b/build/config/compiler/BUILD.gn
index fc668a6a..9afb442 100644
--- a/build/config/compiler/BUILD.gn
+++ b/build/config/compiler/BUILD.gn
@@ -1432,22 +1432,10 @@
 }
 
 config("rustc_revision") {
-  # Use the rustc version as an input to all rustc invovations if a custom
-  # toolchain is not being used (`rust_sysroot_absolute`).
-  if (toolchain_has_rust && rust_sysroot_absolute == "") {
-    if (use_chromium_rust_toolchain) {
-      update_rust_args = [ "--print-package-version" ]
-      rustc_revision = exec_script("//tools/rust/update_rust.py",
-                                   update_rust_args,
-                                   "trim string")
-    } else {
-      # Android toolchain version.
-      rustc_revision =
-          "rustc 1.64.0-dev (Android Rust Toolchain version 9099361)"
-    }
-
-    # Similar to the above config, this is here so that all files get
-    # recompiled after a rustc roll. Nothing should ever read this cfg.
+  if (rustc_revision != "") {
+    # Similar to the above config, this is here so that all files get recompiled
+    # after a rustc roll. Nothing should ever read this cfg. This will not be
+    # set if a custom toolchain is used.
     rustflags = [
       "--cfg",
       "cr_rustc_revision=\"$rustc_revision\"",
diff --git a/build/config/rust.gni b/build/config/rust.gni
index cf62550..b3e501149 100644
--- a/build/config/rust.gni
+++ b/build/config/rust.gni
@@ -94,6 +94,22 @@
      (!use_chromium_rust_toolchain && android_toolchain_supports_platform) ||
      (rust_sysroot_absolute != "" && custom_toolchain_supports_platform))
 
+# The rustc_revision is used to introduce a dependency on the toolchain version
+# (so e.g. rust targets are rebuilt, and the standard library is re-copied when
+# the toolchain changes). It is left empty for custom toolchains.
+rustc_revision = ""
+if (toolchain_has_rust && rust_sysroot_absolute == "") {
+  if (use_chromium_rust_toolchain) {
+    update_rust_args = [ "--print-package-version" ]
+    rustc_revision = exec_script("//tools/rust/update_rust.py",
+                                 update_rust_args,
+                                 "trim string")
+  } else {
+    # Android toolchain version.
+    rustc_revision = "rustc 1.64.0-dev (Android Rust Toolchain version 9099361)"
+  }
+}
+
 # TODO(crbug.com/1278030): To build unit tests for Android we need to build
 # them as a dylib and put them into an APK. We should reuse all the same logic
 # for gtests from the `//testing/test:test` template.
diff --git a/build/dotfile_settings.gni b/build/dotfile_settings.gni
index 7caa778..94b4fb0 100644
--- a/build/dotfile_settings.gni
+++ b/build/dotfile_settings.gni
@@ -23,6 +23,7 @@
     "//build/config/mac/mac_sdk.gni",
     "//build/config/mac/rules.gni",
     "//build/config/posix/BUILD.gn",
+    "//build/config/rust.gni",
     "//build/config/sysroot.gni",
     "//build/config/win/BUILD.gn",
     "//build/config/win/visual_studio_version.gni",
diff --git a/build/lacros/test_runner.py b/build/lacros/test_runner.py
index bdbd5c92..9b1349a86 100755
--- a/build/lacros/test_runner.py
+++ b/build/lacros/test_runner.py
@@ -608,8 +608,6 @@
     if enable_mojo_crosapi:
       forward_args.append(lacros_mojo_socket_arg)
 
-    forward_args.append("--ash-chrome-path=%s" % ash_chrome_file)
-    forward_args.append("--ash-user-data-dir=%s" % tmp_ash_data_dir_name)
     test_env = os.environ.copy()
     test_env['WAYLAND_DISPLAY'] = ash_wayland_socket_name
     test_env['EGL_PLATFORM'] = 'surfaceless'
diff --git a/build/lacros/test_runner_test.py b/build/lacros/test_runner_test.py
index 9d5ea38..da79423 100755
--- a/build/lacros/test_runner_test.py
+++ b/build/lacros/test_runner_test.py
@@ -80,10 +80,6 @@
   @mock.patch.object(os.path, 'exists', return_value=True)
   @mock.patch.object(os.path, 'isfile', return_value=True)
   @mock.patch.object(os.path, 'abspath', return_value='/a/b/filter')
-  @mock.patch.object(os.path, 'dirname', return_value='/some/dir')
-  @mock.patch.object(test_runner,
-                     '_GetAshChromeDirPath',
-                     return_value='/some/dir')
   @mock.patch.object(test_runner,
                      '_GetLatestVersionOfAshChrome',
                      return_value='793554')
@@ -101,7 +97,8 @@
       self.assertEqual(2, mock_popen.call_count)
 
       ash_chrome_args = mock_popen.call_args_list[0][0][0]
-      self.assertTrue(ash_chrome_args[0].endswith('/some/dir/test_ash_chrome'))
+      self.assertTrue(ash_chrome_args[0].endswith(
+          'build/lacros/prebuilt_ash_chrome/793554/test_ash_chrome'))
       expected_ash_chrome_args = [
           '--user-data-dir=/tmp/ash-data',
           '--enable-wayland-server',
@@ -128,8 +125,6 @@
             command,
             '--test-launcher-filter-file=/a/b/filter',
             '--lacros-mojo-socket-for-testing=/tmp/ash-data/lacros.sock',
-            '--ash-chrome-path=/some/dir/test_ash_chrome',
-            '--ash-user-data-dir=/tmp/ash-data',
         ], test_args)
       else:
         self.assertListEqual(test_args[:len(command_parts)], command_parts)
diff --git a/build/rust/std/BUILD.gn b/build/rust/std/BUILD.gn
index 71a393a..b5fe564b 100644
--- a/build/rust/std/BUILD.gn
+++ b/build/rust/std/BUILD.gn
@@ -123,6 +123,11 @@
       # related to the Rust standard library, we ensure libstd.rlib is first.
       "--depfile-target",
       stdlib_files[0],
+
+      # Create a dependency on the rustc version so this action is re-run when
+      # it changes. This argument is not actually read by the script.
+      "--rustc-revision",
+      rustc_revision,
     ]
 
     if (!use_unverified_rust_toolchain) {
diff --git a/build/rust/std/find_std_rlibs.py b/build/rust/std/find_std_rlibs.py
index e15efb7..b91e631 100755
--- a/build/rust/std/find_std_rlibs.py
+++ b/build/rust/std/find_std_rlibs.py
@@ -37,8 +37,9 @@
                       help="Expected list of standard library libraries")
   parser.add_argument("--extra-libs",
                       help="List of extra non-libstd sysroot libraries")
-  parser.add_argument("--expected-rustc-version",
-                      help="The string we expect to be reported by 'rustc -V'")
+  parser.add_argument("--rustc-revision",
+                      help="Not used, just passed from GN to add a dependency"
+                      " on the rustc version.")
   args = parser.parse_args()
 
   # Expected rlibs by concise name (the crate name, plus a disambiguating suffix
@@ -61,19 +62,8 @@
     for lib in args.extra_libs.split(','):
       extra_libs.add(lib)
 
-  # First, ask rustc to confirm it's the version expected.
-  rustc = os.path.join(args.rust_bin_dir, "rustc")
-  if args.expected_rustc_version:
-    proc = subprocess.run([rustc, "-V"], capture_output=True, text=True)
-    proc.check_returncode()
-    rustc_version = proc.stdout.rstrip()
-    if rustc_version != args.expected_rustc_version:
-      raise Exception("gn arguments state that the rustc_version is %s "
-                      "but it was actually %s. Please adjust your "
-                      "gn arguments to match." %
-                      (args.expected_rustc_version, rustc_version))
-
   # Ask rustc where to find the stdlib for this target.
+  rustc = os.path.join(args.rust_bin_dir, "rustc")
   rustc_args = [rustc, "--print", "target-libdir"]
   if args.target:
     rustc_args.extend(["--target", args.target])
diff --git a/build/rust/std/gnrt_config.toml b/build/rust/std/gnrt_config.toml
new file mode 100644
index 0000000..40fb850
--- /dev/null
+++ b/build/rust/std/gnrt_config.toml
@@ -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.
+
+[libc]
+# Requires:
+# * cfg(libc_align) for new enough rustc, which is normally provided by build.rs
+#   but we don't run build scripts for std crates.
+# * cfg(libc_priv_mod_use) is required for the below to work properly.
+# * cfg(libc_core_cvoid) to use the same ffi c_void definition as libcore.
+#
+# See https://github.com/rust-lang/libc/blob/master/build.rs
+cfg = ['libc_align', 'libc_priv_mod_use', 'libc_core_cvoid']
+
+[std]
+# Requires:
+# * cfg(backtrace_in_libstd) because it directly includes .rs files from the
+#   backtrace code rather than including it as a dependency. backtrace's
+#   implementation has special-purpose code to handle this.
+# * STD_ENV_ARCH is referenced in architecture-dependent code. Note this is the
+#   target arch, and as such `$rust_target_arch` is passed literally to GN. This
+#   variable is set at build time in build/config/rust.gni
+#
+# See https://github.com/rust-lang/rust/blob/master/library/std/build.rs
+cfg = ['backtrace_in_libstd']
+env = ['STD_ENV_ARCH=$rust_target_arch']
+
+[test]
+# Requires:
+# * CFG_DISABLE_UNSTABLE_FEATURES=0 to match how it's built by x.py.
+env = ['CFG_DISABLE_UNSTABLE_FEATURES=0']
diff --git a/build/rust/std/rules/BUILD.gn b/build/rust/std/rules/BUILD.gn
index 26459d2..8bdb2ed 100644
--- a/build/rust/std/rules/BUILD.gn
+++ b/build/rust/std/rules/BUILD.gn
@@ -711,6 +711,8 @@
     ":proc_macro",
     ":std",
   ]
+
+  rustenv = [ "CFG_DISABLE_UNSTABLE_FEATURES=0" ]
 }
 cargo_crate("unicode_width") {
   crate_type = "rlib"
diff --git a/cc/BUILD.gn b/cc/BUILD.gn
index 48def44ab..e1762803 100644
--- a/cc/BUILD.gn
+++ b/cc/BUILD.gn
@@ -398,7 +398,6 @@
     "trees/presentation_time_callback_buffer.h",
     "trees/property_animation_state.cc",
     "trees/property_animation_state.h",
-    "trees/property_ids.h",
     "trees/property_tree.cc",
     "trees/property_tree.h",
     "trees/property_tree_builder.cc",
@@ -784,6 +783,7 @@
     "resources/resource_pool_unittest.cc",
     "scheduler/scheduler_state_machine_unittest.cc",
     "scheduler/scheduler_unittest.cc",
+    "slim/slim_layer_tree_compositor_frame_unittest.cc",
     "slim/slim_layer_tree_unittest.cc",
     "slim/slim_layer_unittest.cc",
     "slim/test_frame_sink_impl.cc",
diff --git a/cc/base/rtree.h b/cc/base/rtree.h
index cc94de2c..c91d226 100644
--- a/cc/base/rtree.h
+++ b/cc/base/rtree.h
@@ -17,6 +17,7 @@
 #include "base/check_op.h"
 #include "base/memory/raw_ptr.h"
 #include "base/numerics/clamped_math.h"
+#include "base/trace_event/trace_event.h"
 #include "ui/gfx/geometry/rect.h"
 
 namespace cc {
@@ -178,6 +179,7 @@
 void RTree<T>::Build(const Container& items,
                      const BoundsFunctor& bounds_getter,
                      const PayloadFunctor& payload_getter) {
+  TRACE_EVENT1("cc", "RTree::Build", "size", items.size());
   DCHECK_EQ(0u, num_data_elements_);
 
   std::vector<Branch<T>> branches;
@@ -317,11 +319,14 @@
   results->clear();
   if (num_data_elements_ == 0)
     return;
+
+  TRACE_EVENT_BEGIN1("cc", "RTree::Search", "size", num_data_elements_);
   if (!has_valid_bounds_) {
     SearchRecursiveFallback(root_.subtree.get(), query, results, rects);
   } else if (query.Intersects(root_.bounds)) {
     SearchRecursive(root_.subtree.get(), query, results, rects);
   }
+  TRACE_EVENT_END1("cc", "RTree::Search", "result_size", results->size());
 }
 
 template <typename T>
diff --git a/cc/slim/frame_sink_impl.cc b/cc/slim/frame_sink_impl.cc
index c83845b2..3efcf98 100644
--- a/cc/slim/frame_sink_impl.cc
+++ b/cc/slim/frame_sink_impl.cc
@@ -85,7 +85,8 @@
       base::BindOnce(&FrameSinkImpl::OnContextLost, base::Unretained(this)));
   client_receiver_.Bind(std::move(pending_client_receiver_), task_runner_);
 
-  frame_sink_remote_->InitializeCompositorFrameSinkType(
+  frame_sink_ = frame_sink_remote_.get();
+  frame_sink_->InitializeCompositorFrameSinkType(
       viz::mojom::CompositorFrameSinkType::kLayerTree);
 
 #if BUILDFLAG(IS_ANDROID)
@@ -94,7 +95,7 @@
   if (io_thread_id_ != base::kInvalidThreadId) {
     thread_ids.push_back(io_thread_id_);
   }
-  frame_sink_remote_->SetThreadIds(thread_ids);
+  frame_sink_->SetThreadIds(thread_ids);
 #endif
   return true;
 }
@@ -108,7 +109,7 @@
     return;
   }
   needs_begin_frame_ = needs_begin_frame;
-  frame_sink_remote_->SetNeedsBeginFrame(needs_begin_frame);
+  frame_sink_->SetNeedsBeginFrame(needs_begin_frame);
 }
 
 void FrameSinkImpl::UploadUIResource(cc::UIResourceId resource_id,
@@ -225,7 +226,7 @@
   }
 
   if (!local_surface_id_.is_valid()) {
-    frame_sink_remote_->DidNotProduceFrame(
+    frame_sink_->DidNotProduceFrame(
         viz::BeginFrameAck(begin_frame_args, false));
     return;
   }
@@ -235,11 +236,19 @@
   viz::HitTestRegionList hit_test_region_list;
   if (!client_->BeginFrame(begin_frame_args, frame, viz_resource_ids,
                            hit_test_region_list)) {
-    frame_sink_remote_->DidNotProduceFrame(
+    frame_sink_->DidNotProduceFrame(
         viz::BeginFrameAck(begin_frame_args, false));
     return;
   }
 
+  if (local_surface_id_ == last_submitted_local_surface_id_) {
+    DCHECK_EQ(last_submitted_device_scale_factor_, frame.device_scale_factor());
+    DCHECK_EQ(last_submitted_size_in_pixels_.height(),
+              frame.size_in_pixels().height());
+    DCHECK_EQ(last_submitted_size_in_pixels_.width(),
+              frame.size_in_pixels().width());
+  }
+
   resource_provider_.PrepareSendToParent(std::move(viz_resource_ids).extract(),
                                          &frame.resource_list,
                                          context_provider_.get());
@@ -254,7 +263,7 @@
 
   {
     TRACE_EVENT0("cc", "SubmitCompositorFrame");
-    frame_sink_remote_->SubmitCompositorFrame(
+    frame_sink_->SubmitCompositorFrame(
         local_surface_id_, std::move(frame),
         send_new_hit_test_region_list ? hit_test_region_list_ : absl::nullopt,
         0);
diff --git a/cc/slim/frame_sink_impl.h b/cc/slim/frame_sink_impl.h
index 61db3293..4d7c5ff 100644
--- a/cc/slim/frame_sink_impl.h
+++ b/cc/slim/frame_sink_impl.h
@@ -122,6 +122,8 @@
       pending_client_receiver_;
 
   mojo::AssociatedRemote<viz::mojom::CompositorFrameSink> frame_sink_remote_;
+  // Separate from AssociatedRemote above for testing.
+  viz::mojom::CompositorFrameSink* frame_sink_ = nullptr;
   mojo::Receiver<viz::mojom::CompositorFrameSinkClient> client_receiver_{this};
   scoped_refptr<viz::ContextProvider> context_provider_;
   raw_ptr<FrameSinkImplClient> client_ = nullptr;
@@ -133,6 +135,10 @@
   absl::optional<viz::HitTestRegionList> hit_test_region_list_;
   base::PlatformThreadId io_thread_id_;
 
+  viz::LocalSurfaceId last_submitted_local_surface_id_;
+  float last_submitted_device_scale_factor_ = 1.f;
+  gfx::Size last_submitted_size_in_pixels_;
+
   bool needs_begin_frame_ = false;
 };
 
diff --git a/cc/slim/layer.cc b/cc/slim/layer.cc
index 4ccb227..e226867 100644
--- a/cc/slim/layer.cc
+++ b/cc/slim/layer.cc
@@ -11,7 +11,6 @@
 #include "base/atomic_sequence_num.h"
 #include "base/check.h"
 #include "base/containers/cxx20_erase_vector.h"
-#include "base/feature_list.h"
 #include "base/ranges/algorithm.h"
 #include "cc/layers/layer.h"
 #include "cc/paint/filter_operation.h"
@@ -20,7 +19,6 @@
 #include "cc/slim/layer_tree.h"
 #include "cc/slim/layer_tree_impl.h"
 #include "components/viz/common/quads/shared_quad_state.h"
-#include "components/viz/common/quads/solid_color_draw_quad.h"
 
 namespace cc::slim {
 
@@ -58,7 +56,7 @@
 
 Layer::Layer(scoped_refptr<cc::Layer> cc_layer)
     : cc_layer_(std::move(cc_layer)),
-      id_(g_next_id.GetNext()),
+      id_(g_next_id.GetNext() + 1),
       is_drawable_(false),
       contents_opaque_(false),
       draws_content_(false),
@@ -375,6 +373,23 @@
   return is_drawable_;
 }
 
+gfx::Transform Layer::ComputeTransformToParent() {
+  // Layer transform is:
+  // position x transform_origin x transform x -transform_origin
+  gfx::Transform transform =
+      gfx::Transform::MakeTranslation(position_.x(), position_.y());
+  transform.Translate3d(transform_origin_.x(), transform_origin_.y(),
+                        transform_origin_.z());
+  transform.PreConcat(transform_);
+  transform.Translate3d(-transform_origin_.x(), -transform_origin_.y(),
+                        -transform_origin_.z());
+  return transform;
+}
+
+void Layer::AppendQuads(viz::CompositorRenderPass& render_pass,
+                        const gfx::Transform& transform,
+                        const gfx::Rect* clip) {}
+
 void Layer::NotifyTreeChanged() {
   if (cc_layer()) {
     return;
@@ -391,4 +406,23 @@
   }
 }
 
+viz::SharedQuadState* Layer::CreateAndAppendSharedQuadState(
+    viz::CompositorRenderPass& render_pass,
+    const gfx::Transform& transform,
+    const gfx::Rect* clip) {
+  viz::SharedQuadState* quad_state =
+      render_pass.CreateAndAppendSharedQuadState();
+  const gfx::Rect rect{bounds()};
+  absl::optional<gfx::Rect> clip_opt;
+  if (clip) {
+    clip_opt = *clip;
+  }
+  // TODO(crbug.com/1408128): Set visible_layer_rect properly.
+  quad_state->SetAll(transform, /*layer_rect=*/rect,
+                     /*visible_layer_rect=*/rect, gfx::MaskFilterInfo(),
+                     std::move(clip_opt), contents_opaque(), opacity(),
+                     SkBlendMode::kSrcOver, 0);
+  return quad_state;
+}
+
 }  // namespace cc::slim
diff --git a/cc/slim/layer.h b/cc/slim/layer.h
index e0c64bc..cd9b1774 100644
--- a/cc/slim/layer.h
+++ b/cc/slim/layer.h
@@ -23,10 +23,16 @@
 class Layer;
 }
 
+namespace viz {
+class CompositorRenderPass;
+class SharedQuadState;
+}  // namespace viz
+
 namespace cc::slim {
 
 class LayerTree;
 class LayerTreeCcWrapper;
+class LayerTreeImpl;
 
 // Base class for composited layers. Special layer types are derived from
 // this class. Each layer is an independent unit in the compositor, be that
@@ -178,15 +184,24 @@
 
  protected:
   friend class LayerTreeCcWrapper;
+  friend class LayerTreeImpl;
 
   explicit Layer(scoped_refptr<cc::Layer> cc_layer);
   virtual ~Layer();
 
   // Called by LayerTree.
+  gfx::Transform ComputeTransformToParent();
   virtual bool HasDrawableContent() const;
+  virtual void AppendQuads(viz::CompositorRenderPass& render_pass,
+                           const gfx::Transform& transform,
+                           const gfx::Rect* clip);
 
   void NotifyTreeChanged();
   void NotifyPropertyChanged();
+  virtual viz::SharedQuadState* CreateAndAppendSharedQuadState(
+      viz::CompositorRenderPass& render_pass,
+      const gfx::Transform& transform,
+      const gfx::Rect* clip);
 
   const scoped_refptr<cc::Layer> cc_layer_;
 
diff --git a/cc/slim/layer_tree_impl.cc b/cc/slim/layer_tree_impl.cc
index b0c3821d..6a849b2 100644
--- a/cc/slim/layer_tree_impl.cc
+++ b/cc/slim/layer_tree_impl.cc
@@ -6,15 +6,24 @@
 
 #include <algorithm>
 #include <memory>
+#include <vector>
 
 #include "base/auto_reset.h"
+#include "base/containers/adapters.h"
+#include "base/trace_event/trace_event.h"
 #include "cc/slim/frame_sink_impl.h"
 #include "cc/slim/layer.h"
 #include "cc/slim/layer_tree_client.h"
 #include "components/viz/common/frame_sinks/begin_frame_args.h"
 #include "components/viz/common/hit_test/hit_test_region_list.h"
+#include "components/viz/common/quads/compositor_frame.h"
+#include "components/viz/common/quads/compositor_frame_metadata.h"
+#include "components/viz/common/quads/compositor_render_pass.h"
+#include "components/viz/common/quads/draw_quad.h"
 #include "third_party/skia/include/core/SkColor.h"
 #include "ui/gfx/geometry/rect.h"
+#include "ui/gfx/geometry/size.h"
+#include "ui/gfx/geometry/transform.h"
 
 namespace cc::slim {
 
@@ -76,7 +85,7 @@
 }
 
 void LayerTreeImpl::set_display_transform_hint(gfx::OverlayTransform hint) {
-  // TODO(crbug.com/1408128): Implement.
+  display_transform_hint_ = hint;
 }
 
 void LayerTreeImpl::RequestCopyOfOutput(
@@ -182,9 +191,16 @@
   // changes made by client `BeginFrame` are about to be drawn, so there is no
   // need for another frame.
   needs_draw_ = false;
-  // TODO(crbug.com/1408128): Implement frame production here.
+
+  if (!root_) {
+    UpdateNeedsBeginFrame();
+    return false;
+  }
+
+  GenerateCompositorFrame(args, out_frame, out_resource_ids,
+                          out_hit_test_region_list);
   UpdateNeedsBeginFrame();
-  return false;
+  return true;
 }
 
 void LayerTreeImpl::DidReceiveCompositorFrameAck() {
@@ -262,4 +278,88 @@
   return client_needs_one_begin_frame_ || needs_draw_;
 }
 
+void LayerTreeImpl::GenerateCompositorFrame(
+    const viz::BeginFrameArgs& args,
+    viz::CompositorFrame& out_frame,
+    base::flat_set<viz::ResourceId>& out_resource_ids,
+    viz::HitTestRegionList& out_hit_test_region_list) {
+  // TODO(crbug.com/1408128): Only has a very simple and basic compositor frame
+  // generation. Some missing features include:
+  // * Support multiple render passes (non-axis aligned clip, filters)
+  // * Damage tracking
+  // * Occlusion culling
+  // * Visible rect (ie clip) on quads
+  // * Surface embedding fields (referenced surfaces, activation dependency,
+  //   deadline)
+  TRACE_EVENT0("cc", "slim::LayerTreeImpl::ProduceFrame");
+  auto render_pass = viz::CompositorRenderPass::Create();
+  render_pass->SetNew(viz::CompositorRenderPassId(root_->id()),
+                      /*output_rect=*/device_viewport_rect_,
+                      /*damage_rect=*/device_viewport_rect_,
+                      /*transform_to_root_target=*/gfx::Transform());
+
+  out_frame.metadata.frame_token = ++next_frame_token_;
+  out_frame.metadata.begin_frame_ack =
+      viz::BeginFrameAck(args, /*has_damage=*/true);
+  out_frame.metadata.device_scale_factor = device_scale_factor_;
+  out_frame.metadata.root_background_color = background_color_;
+  out_frame.metadata.referenced_surfaces = std::vector<viz::SurfaceRange>(
+      referenced_surfaces_.begin(), referenced_surfaces_.end());
+  out_frame.metadata.top_controls_visible_height = top_controls_visible_height_;
+  top_controls_visible_height_.reset();
+  out_frame.metadata.display_transform_hint = display_transform_hint_;
+
+  Draw(*root_, *render_pass, /*transform_to_target=*/gfx::Transform(),
+       /*clip_from_parent=*/nullptr);
+
+  out_frame.render_pass_list.push_back(std::move(render_pass));
+
+  for (const auto& pass : out_frame.render_pass_list) {
+    for (const auto* quad : pass->quad_list) {
+      for (viz::ResourceId resource_id : quad->resources) {
+        out_resource_ids.insert(resource_id);
+      }
+    }
+  }
+}
+
+void LayerTreeImpl::Draw(Layer& layer,
+                         viz::CompositorRenderPass& parent_pass,
+                         const gfx::Transform& transform_to_target,
+                         const gfx::Rect* clip_from_parent) {
+  if (layer.hide_layer_and_subtree()) {
+    return;
+  }
+
+  gfx::Transform transform_to_parent = layer.ComputeTransformToParent();
+
+  // New transform is: parent transform x layer transform.
+  gfx::Transform new_transform_to_target = transform_to_target;
+  new_transform_to_target.PreConcat(transform_to_parent);
+
+  bool use_new_clip = false;
+  gfx::Rect new_clip;
+  // Drop non-axis aligned clip instead of using new render pass.
+  // TODO(crbug.com/1408128): Clip in layer space (visible rect) for clip
+  // that is not an exact integer.
+  if (layer.masks_to_bounds() &&
+      new_transform_to_target.Preserves2dAxisAlignment()) {
+    new_clip.set_size(layer.bounds());
+    new_clip = new_transform_to_target.MapRect(new_clip);
+    if (clip_from_parent) {
+      new_clip.Intersect(*clip_from_parent);
+    }
+    use_new_clip = true;
+  }
+  const gfx::Rect* clip = use_new_clip ? &new_clip : clip_from_parent;
+
+  for (auto& child : base::Reversed(layer.children())) {
+    Draw(*child, parent_pass, new_transform_to_target, clip);
+  }
+
+  if (!layer.bounds().IsEmpty() && layer.HasDrawableContent()) {
+    layer.AppendQuads(parent_pass, new_transform_to_target, clip);
+  }
+}
+
 }  // namespace cc::slim
diff --git a/cc/slim/layer_tree_impl.h b/cc/slim/layer_tree_impl.h
index 8c3809e..58e40a6 100644
--- a/cc/slim/layer_tree_impl.h
+++ b/cc/slim/layer_tree_impl.h
@@ -32,6 +32,10 @@
 class UIResourceManager;
 }  // namespace cc
 
+namespace viz {
+class CompositorRenderPass;
+}  // namespace viz
+
 namespace cc::slim {
 
 class FrameSinkImpl;
@@ -104,6 +108,15 @@
   // submitted in a CompositorFrame.
   void SetNeedsDraw();
   bool NeedsBeginFrames() const;
+  void GenerateCompositorFrame(
+      const viz::BeginFrameArgs& args,
+      viz::CompositorFrame& out_frame,
+      base::flat_set<viz::ResourceId>& out_resource_ids,
+      viz::HitTestRegionList& out_hit_test_region_list);
+  void Draw(Layer& layer,
+            viz::CompositorRenderPass& render_pass,
+            const gfx::Transform& transform_to_target,
+            const gfx::Rect* clip_from_parent);
 
   const raw_ptr<LayerTreeClient> client_;
   scoped_refptr<Layer> root_;
@@ -132,6 +145,8 @@
   SkColor4f background_color_ = SkColors::kWhite;
   absl::optional<float> top_controls_visible_height_;
   base::flat_set<viz::SurfaceRange> referenced_surfaces_;
+  viz::FrameTokenGenerator next_frame_token_;
+  gfx::OverlayTransform display_transform_hint_ = gfx::OVERLAY_TRANSFORM_NONE;
 
   base::WeakPtrFactory<LayerTreeImpl> weak_factory_{this};
 };
diff --git a/cc/slim/slim_layer_tree_compositor_frame_unittest.cc b/cc/slim/slim_layer_tree_compositor_frame_unittest.cc
new file mode 100644
index 0000000..8fe76a85
--- /dev/null
+++ b/cc/slim/slim_layer_tree_compositor_frame_unittest.cc
@@ -0,0 +1,310 @@
+// 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 <utility>
+
+#include "base/memory/weak_ptr.h"
+#include "base/test/scoped_feature_list.h"
+#include "base/time/time.h"
+#include "base/unguessable_token.h"
+#include "cc/slim/features.h"
+#include "cc/slim/layer.h"
+#include "cc/slim/solid_color_layer.h"
+#include "cc/slim/test_frame_sink_impl.h"
+#include "cc/slim/test_layer_tree_client.h"
+#include "cc/slim/test_layer_tree_impl.h"
+#include "components/viz/common/frame_sinks/begin_frame_args.h"
+#include "components/viz/common/quads/compositor_frame.h"
+#include "components/viz/common/quads/solid_color_draw_quad.h"
+#include "components/viz/common/surfaces/local_surface_id.h"
+#include "components/viz/test/draw_quad_matchers.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "ui/gfx/geometry/rect.h"
+
+namespace cc::slim {
+
+namespace {
+
+using testing::AllOf;
+using testing::ElementsAre;
+
+class SlimLayerTreeCompositorFrameTest : public testing::Test {
+ public:
+  void SetUp() override {
+    scoped_feature_list_.InitAndEnableFeature(features::kSlimCompositor);
+    layer_tree_ = std::make_unique<TestLayerTreeImpl>(&client_);
+    layer_tree_->SetVisible(true);
+
+    auto frame_sink = TestFrameSinkImpl::Create();
+    frame_sink_ = frame_sink->GetWeakPtr();
+    layer_tree_->SetFrameSink(std::move(frame_sink));
+
+    viewport_ = gfx::Rect(100, 100);
+    base::UnguessableToken token = base::UnguessableToken::Create();
+    local_surface_id_ = viz::LocalSurfaceId(1u, 2u, token);
+    EXPECT_TRUE(local_surface_id_.is_valid());
+    layer_tree_->SetViewportRectAndScale(
+        viewport_, /*device_scale_factor=*/1.0f, local_surface_id_);
+  }
+
+  void IncrementLocalSurfaceId() {
+    DCHECK(local_surface_id_.is_valid());
+    local_surface_id_ =
+        viz::LocalSurfaceId(local_surface_id_.parent_sequence_number(),
+                            local_surface_id_.child_sequence_number() + 1,
+                            local_surface_id_.embed_token());
+    DCHECK(local_surface_id_.is_valid());
+  }
+
+  viz::CompositorFrame ProduceFrame() {
+    layer_tree_->SetNeedsRedraw();
+    EXPECT_TRUE(layer_tree_->NeedsBeginFrames());
+    base::TimeTicks frame_time = base::TimeTicks::Now();
+    base::TimeDelta interval = viz::BeginFrameArgs::DefaultInterval();
+    viz::BeginFrameArgs begin_frame_args = viz::BeginFrameArgs::Create(
+        BEGINFRAME_FROM_HERE,
+        /*source_id=*/1, ++sequence_id_, frame_time, frame_time + interval,
+        interval, viz::BeginFrameArgs::NORMAL);
+    frame_sink_->OnBeginFrame(begin_frame_args, {}, /*frame_ack=*/false, {});
+    viz::CompositorFrame frame = frame_sink_->TakeLastFrame();
+    frame_sink_->DidReceiveCompositorFrameAck({});
+    return frame;
+  }
+
+  scoped_refptr<SolidColorLayer> CreateSolidColorLayer(const gfx::Size& bounds,
+                                                       SkColor4f color) {
+    auto solid_color_layer = SolidColorLayer::Create();
+    solid_color_layer->SetBounds(bounds);
+    solid_color_layer->SetBackgroundColor(color);
+    solid_color_layer->SetIsDrawable(true);
+    return solid_color_layer;
+  }
+
+ protected:
+  base::test::ScopedFeatureList scoped_feature_list_;
+  TestLayerTreeClient client_;
+  std::unique_ptr<TestLayerTreeImpl> layer_tree_;
+  base::WeakPtr<TestFrameSinkImpl> frame_sink_;
+
+  uint64_t sequence_id_ = 0;
+
+  gfx::Rect viewport_;
+  viz::LocalSurfaceId local_surface_id_;
+};
+
+TEST_F(SlimLayerTreeCompositorFrameTest, CompositorFrameMetadataBasics) {
+  auto solid_color_layer =
+      CreateSolidColorLayer(viewport_.size(), SkColors::kGray);
+  layer_tree_->SetRoot(solid_color_layer);
+
+  // TODO(crbug.com/1408128): Add tests for features once implemented:
+  // * reference_surfaces
+  // * activation_dependencies
+  // * deadline
+  uint32_t first_frame_token = 0u;
+  {
+    viz::CompositorFrame frame = ProduceFrame();
+    viz::CompositorFrameMetadata& metadata = frame.metadata;
+    EXPECT_NE(0u, metadata.frame_token);
+    first_frame_token = metadata.frame_token;
+    EXPECT_EQ(sequence_id_, metadata.begin_frame_ack.frame_id.sequence_number);
+    EXPECT_EQ(1.0f, metadata.device_scale_factor);
+    EXPECT_EQ(SkColors::kWhite, metadata.root_background_color);
+    EXPECT_EQ(gfx::OVERLAY_TRANSFORM_NONE, metadata.display_transform_hint);
+    EXPECT_EQ(absl::nullopt, metadata.top_controls_visible_height);
+  }
+
+  IncrementLocalSurfaceId();
+  layer_tree_->SetViewportRectAndScale(viewport_, /*device_scale_factor=*/2.0f,
+                                       local_surface_id_);
+  layer_tree_->set_background_color(SkColors::kBlue);
+  layer_tree_->set_display_transform_hint(gfx::OVERLAY_TRANSFORM_ROTATE_90);
+  layer_tree_->UpdateTopControlsVisibleHeight(5.0f);
+  {
+    viz::CompositorFrame frame = ProduceFrame();
+    viz::CompositorFrameMetadata& metadata = frame.metadata;
+    EXPECT_NE(0u, metadata.frame_token);
+    EXPECT_NE(first_frame_token, metadata.frame_token);
+    EXPECT_EQ(sequence_id_, metadata.begin_frame_ack.frame_id.sequence_number);
+    EXPECT_EQ(2.0f, metadata.device_scale_factor);
+    EXPECT_EQ(SkColors::kBlue, metadata.root_background_color);
+    EXPECT_EQ(gfx::OVERLAY_TRANSFORM_ROTATE_90,
+              metadata.display_transform_hint);
+    EXPECT_EQ(5.0f, metadata.top_controls_visible_height);
+  }
+}
+
+TEST_F(SlimLayerTreeCompositorFrameTest, OneSolidColorQuad) {
+  auto solid_color_layer =
+      CreateSolidColorLayer(viewport_.size(), SkColors::kGray);
+  layer_tree_->SetRoot(solid_color_layer);
+
+  viz::CompositorFrame frame = ProduceFrame();
+
+  ASSERT_EQ(frame.render_pass_list.size(), 1u);
+  auto& pass = frame.render_pass_list.back();
+  EXPECT_EQ(pass->output_rect, viewport_);
+  EXPECT_EQ(pass->damage_rect, viewport_);
+  EXPECT_EQ(pass->transform_to_root_target, gfx::Transform());
+
+  ASSERT_THAT(
+      pass->quad_list,
+      ElementsAre(AllOf(viz::IsSolidColorQuad(SkColors::kGray),
+                        viz::HasRect(viewport_), viz::HasVisibleRect(viewport_),
+                        viz::HasTransform(gfx::Transform()))));
+  auto* quad = pass->quad_list.back();
+  auto* shared_quad_state = quad->shared_quad_state;
+
+  EXPECT_EQ(shared_quad_state->quad_layer_rect, viewport_);
+  EXPECT_EQ(shared_quad_state->visible_quad_layer_rect, viewport_);
+  EXPECT_EQ(shared_quad_state->clip_rect, absl::nullopt);
+  EXPECT_EQ(shared_quad_state->are_contents_opaque, true);
+  EXPECT_EQ(shared_quad_state->opacity, 1.0f);
+  EXPECT_EQ(shared_quad_state->blend_mode, SkBlendMode::kSrcOver);
+}
+
+TEST_F(SlimLayerTreeCompositorFrameTest, LayerTransform) {
+  auto root_layer = CreateSolidColorLayer(viewport_.size(), SkColors::kGray);
+  layer_tree_->SetRoot(root_layer);
+
+  auto child = CreateSolidColorLayer(gfx::Size(10, 20), SkColors::kGreen);
+  root_layer->AddChild(child);
+
+  auto check_child_quad = [&](gfx::Rect expected_rect_in_root) {
+    viz::CompositorFrame frame = ProduceFrame();
+    ASSERT_EQ(frame.render_pass_list.size(), 1u);
+    auto& pass = frame.render_pass_list.back();
+    ASSERT_THAT(pass->quad_list,
+                ElementsAre(AllOf(viz::IsSolidColorQuad(SkColors::kGreen),
+                                  viz::HasRect(gfx::Rect(10, 20)),
+                                  viz::HasVisibleRect(gfx::Rect(10, 20))),
+                            AllOf(viz::IsSolidColorQuad(SkColors::kGray),
+                                  viz::HasRect(viewport_),
+                                  viz::HasVisibleRect(viewport_))));
+
+    auto* quad = pass->quad_list.front();
+    auto* shared_quad_state = quad->shared_quad_state;
+
+    EXPECT_EQ(shared_quad_state->quad_layer_rect, gfx::Rect(10, 20));
+    EXPECT_EQ(shared_quad_state->visible_quad_layer_rect, gfx::Rect(10, 20));
+
+    gfx::Rect rect_in_root =
+        shared_quad_state->quad_to_target_transform.MapRect(quad->rect);
+    EXPECT_EQ(expected_rect_in_root, rect_in_root);
+  };
+
+  child->SetPosition(gfx::PointF(30.0f, 30.0f));
+  check_child_quad(gfx::Rect(30, 30, 10, 20));
+
+  child->SetTransform(gfx::Transform::MakeTranslation(10.0f, 10.0f));
+  check_child_quad(gfx::Rect(40, 40, 10, 20));
+
+  // Rotate about top left corner.
+  child->SetTransform(gfx::Transform::Make90degRotation());
+  check_child_quad(gfx::Rect(10, 30, 20, 10));
+
+  // Rotate about the center.
+  child->SetTransformOrigin(gfx::Point3F(5.0f, 10.0f, 0.0f));
+  check_child_quad(gfx::Rect(25, 35, 20, 10));
+}
+
+TEST_F(SlimLayerTreeCompositorFrameTest, ChildOrder) {
+  auto root_layer = CreateSolidColorLayer(viewport_.size(), SkColors::kGray);
+  layer_tree_->SetRoot(root_layer);
+
+  scoped_refptr<SolidColorLayer> children[] = {
+      CreateSolidColorLayer(gfx::Size(10, 10), SkColors::kBlue),
+      CreateSolidColorLayer(gfx::Size(10, 10), SkColors::kGreen),
+      CreateSolidColorLayer(gfx::Size(10, 10), SkColors::kMagenta),
+      CreateSolidColorLayer(gfx::Size(10, 10), SkColors::kRed),
+      CreateSolidColorLayer(gfx::Size(10, 10), SkColors::kYellow)};
+
+  // Build tree such that quads appear in child order.
+  // Quads are appended post order depth first, in reverse child order.
+  // root <- child4 <- child3
+  //                <- child2
+  //      <- child1 <- child0
+  root_layer->AddChild(children[4]);
+  root_layer->AddChild(children[1]);
+  children[4]->AddChild(children[3]);
+  children[4]->AddChild(children[2]);
+  children[1]->AddChild(children[0]);
+
+  // Add offsets so they do not cover each other.
+  children[3]->SetPosition(gfx::PointF(10.0f, 10.0f));
+  children[2]->SetPosition(gfx::PointF(20.0f, 20.0f));
+  children[1]->SetPosition(gfx::PointF(30.0f, 30.0f));
+  children[0]->SetPosition(gfx::PointF(10.0f, 10.0f));
+
+  gfx::Point expected_origins[] = {
+      gfx::Point(40.0f, 40.0f), gfx::Point(30.0f, 30.0f),
+      gfx::Point(20.0f, 20.0f), gfx::Point(10.0f, 10.0f),
+      gfx::Point(00.0f, 00.0f)};
+
+  viz::CompositorFrame frame = ProduceFrame();
+  ASSERT_EQ(frame.render_pass_list.size(), 1u);
+  auto& pass = frame.render_pass_list.back();
+  ASSERT_THAT(pass->quad_list,
+              ElementsAre(viz::IsSolidColorQuad(SkColors::kBlue),
+                          viz::IsSolidColorQuad(SkColors::kGreen),
+                          viz::IsSolidColorQuad(SkColors::kMagenta),
+                          viz::IsSolidColorQuad(SkColors::kRed),
+                          viz::IsSolidColorQuad(SkColors::kYellow),
+                          viz::IsSolidColorQuad(SkColors::kGray)));
+
+  for (size_t i = 0; i < std::size(expected_origins); ++i) {
+    auto* quad = pass->quad_list.ElementAt(i);
+    EXPECT_EQ(quad->shared_quad_state->quad_to_target_transform.MapPoint(
+                  gfx::Point()),
+              expected_origins[i]);
+  }
+}
+
+TEST_F(SlimLayerTreeCompositorFrameTest, AxisAlignedClip) {
+  auto root_layer = CreateSolidColorLayer(viewport_.size(), SkColors::kGray);
+  layer_tree_->SetRoot(root_layer);
+
+  auto clip_layer = Layer::Create();
+  clip_layer->SetBounds(gfx::Size(10, 20));
+  clip_layer->SetMasksToBounds(true);
+
+  auto draw_layer = CreateSolidColorLayer(gfx::Size(30, 30), SkColors::kRed);
+
+  root_layer->AddChild(clip_layer);
+  clip_layer->AddChild(draw_layer);
+
+  {
+    viz::CompositorFrame frame = ProduceFrame();
+    ASSERT_EQ(frame.render_pass_list.size(), 1u);
+    auto& pass = frame.render_pass_list.back();
+    ASSERT_THAT(pass->quad_list,
+                ElementsAre(viz::IsSolidColorQuad(SkColors::kRed),
+                            viz::IsSolidColorQuad(SkColors::kGray)));
+
+    auto* quad = pass->quad_list.front();
+    ASSERT_TRUE(quad->shared_quad_state->clip_rect);
+    EXPECT_EQ(quad->shared_quad_state->clip_rect.value(), gfx::Rect(10, 20));
+  }
+
+  clip_layer->SetPosition(gfx::PointF(5, 5));
+  {
+    viz::CompositorFrame frame = ProduceFrame();
+    ASSERT_EQ(frame.render_pass_list.size(), 1u);
+    auto& pass = frame.render_pass_list.back();
+    ASSERT_THAT(pass->quad_list,
+                ElementsAre(viz::IsSolidColorQuad(SkColors::kRed),
+                            viz::IsSolidColorQuad(SkColors::kGray)));
+
+    auto* quad = pass->quad_list.front();
+    ASSERT_TRUE(quad->shared_quad_state->clip_rect);
+    // Clip is in target space.
+    EXPECT_EQ(quad->shared_quad_state->clip_rect.value(),
+              gfx::Rect(5, 5, 10, 20));
+  }
+}
+
+}  // namespace
+
+}  // namespace cc::slim
diff --git a/cc/slim/solid_color_layer.cc b/cc/slim/solid_color_layer.cc
index dc4e6f5..ece3586 100644
--- a/cc/slim/solid_color_layer.cc
+++ b/cc/slim/solid_color_layer.cc
@@ -7,6 +7,7 @@
 #include <utility>
 
 #include "cc/layers/solid_color_layer.h"
+#include "cc/slim/features.h"
 #include "components/viz/common/quads/compositor_render_pass.h"
 #include "components/viz/common/quads/solid_color_draw_quad.h"
 
@@ -15,7 +16,9 @@
 // static
 scoped_refptr<SolidColorLayer> SolidColorLayer::Create() {
   scoped_refptr<cc::SolidColorLayer> cc_layer;
-  cc_layer = cc::SolidColorLayer::Create();
+  if (!features::IsSlimCompositorEnabled()) {
+    cc_layer = cc::SolidColorLayer::Create();
+  }
   return base::AdoptRef(new SolidColorLayer(std::move(cc_layer)));
 }
 
@@ -29,7 +32,24 @@
 }
 
 void SolidColorLayer::SetBackgroundColor(SkColor4f color) {
-  cc_layer()->SetBackgroundColor(color);
+  if (cc_layer()) {
+    cc_layer()->SetBackgroundColor(color);
+    return;
+  }
+  SetContentsOpaque(color.isOpaque());
+  Layer::SetBackgroundColor(color);
+}
+
+void SolidColorLayer::AppendQuads(viz::CompositorRenderPass& render_pass,
+                                  const gfx::Transform& transform,
+                                  const gfx::Rect* clip) {
+  viz::SharedQuadState* quad_state =
+      CreateAndAppendSharedQuadState(render_pass, transform, clip);
+  viz::SolidColorDrawQuad* quad =
+      render_pass.CreateAndAppendDrawQuad<viz::SolidColorDrawQuad>();
+  quad->SetNew(quad_state, quad_state->quad_layer_rect,
+               quad_state->visible_quad_layer_rect, background_color(),
+               /*anti_aliasing_off=*/true);
 }
 
 }  // namespace cc::slim
diff --git a/cc/slim/solid_color_layer.h b/cc/slim/solid_color_layer.h
index c3c91b5..abbc41d 100644
--- a/cc/slim/solid_color_layer.h
+++ b/cc/slim/solid_color_layer.h
@@ -26,6 +26,10 @@
   explicit SolidColorLayer(scoped_refptr<cc::SolidColorLayer> cc_layer);
   ~SolidColorLayer() override;
 
+  void AppendQuads(viz::CompositorRenderPass& render_pass,
+                   const gfx::Transform& transform,
+                   const gfx::Rect* clip) override;
+
   cc::SolidColorLayer* cc_layer() const;
 };
 
diff --git a/cc/slim/test_frame_sink_impl.cc b/cc/slim/test_frame_sink_impl.cc
index 67608ef..e764043 100644
--- a/cc/slim/test_frame_sink_impl.cc
+++ b/cc/slim/test_frame_sink_impl.cc
@@ -6,8 +6,11 @@
 
 #include <memory>
 #include <utility>
+#include <vector>
 
 #include "base/task/single_thread_task_runner.h"
+#include "build/build_config.h"
+#include "components/viz/common/quads/compositor_frame.h"
 #include "components/viz/test/test_context_provider.h"
 #include "mojo/public/cpp/bindings/pending_associated_receiver.h"
 #include "mojo/public/cpp/bindings/pending_associated_remote.h"
@@ -16,6 +19,41 @@
 
 namespace cc::slim {
 
+class TestFrameSinkImpl::TestMojoCompositorFrameSink
+    : public viz::mojom::CompositorFrameSink {
+ public:
+  TestMojoCompositorFrameSink() = default;
+  void SetNeedsBeginFrame(bool needs_begin_frame) override {}
+  void SetWantsAnimateOnlyBeginFrames() override {}
+  void SubmitCompositorFrame(
+      const viz::LocalSurfaceId& local_surface_id,
+      viz::CompositorFrame frame,
+      absl::optional<::viz::HitTestRegionList> hit_test_region_list,
+      uint64_t submit_time) override {
+    last_frame_ = std::move(frame);
+  }
+  void SubmitCompositorFrameSync(
+      const viz::LocalSurfaceId& local_surface_id,
+      viz::CompositorFrame frame,
+      absl::optional<::viz::HitTestRegionList> hit_test_region_list,
+      uint64_t submit_time,
+      SubmitCompositorFrameSyncCallback callback) override {}
+  void DidNotProduceFrame(const viz::BeginFrameAck& ack) override {}
+  void DidAllocateSharedBitmap(base::ReadOnlySharedMemoryRegion region,
+                               const gpu::Mailbox& id) override {}
+  void DidDeleteSharedBitmap(const gpu::Mailbox& id) override {}
+  void InitializeCompositorFrameSinkType(
+      viz::mojom::CompositorFrameSinkType type) override {}
+#if BUILDFLAG(IS_ANDROID)
+  void SetThreadIds(const std::vector<int32_t>& thread_ids) override {}
+#endif
+
+  viz::CompositorFrame TakeLastFrame() { return std::move(last_frame_); }
+
+ private:
+  viz::CompositorFrame last_frame_;
+};
+
 // static
 std::unique_ptr<TestFrameSinkImpl> TestFrameSinkImpl::Create() {
   auto task_runner = base::SingleThreadTaskRunner::GetCurrentDefault();
@@ -44,13 +82,18 @@
                     std::move(client_receiver),
                     std::move(context_provider),
                     base::kInvalidThreadId),
-      pending_sink_receiver_(std::move(sink_receiver)) {}
+      mojo_sink_(std::make_unique<TestMojoCompositorFrameSink>()) {}
 
 TestFrameSinkImpl::~TestFrameSinkImpl() = default;
 
+viz::CompositorFrame TestFrameSinkImpl::TakeLastFrame() {
+  return mojo_sink_->TakeLastFrame();
+}
+
 bool TestFrameSinkImpl::BindToClient(FrameSinkImplClient* client) {
   DCHECK(!bind_to_client_called_);
   client_ = client;
+  frame_sink_ = mojo_sink_.get();
   bind_to_client_called_ = true;
   return bind_to_client_result_;
 }
diff --git a/cc/slim/test_frame_sink_impl.h b/cc/slim/test_frame_sink_impl.h
index a1a5b725..e74ac326 100644
--- a/cc/slim/test_frame_sink_impl.h
+++ b/cc/slim/test_frame_sink_impl.h
@@ -24,6 +24,7 @@
     DCHECK(!bind_to_client_called_);
     bind_to_client_result_ = result;
   }
+  viz::CompositorFrame TakeLastFrame();
   bool bind_to_client_called() const { return bind_to_client_called_; }
   bool needs_begin_frames() const { return needs_begin_frames_; }
 
@@ -32,6 +33,7 @@
   void SetNeedsBeginFrame(bool needs_begin_frame) override;
 
  private:
+  class TestMojoCompositorFrameSink;
   TestFrameSinkImpl(
       scoped_refptr<base::SingleThreadTaskRunner> task_runner,
       mojo::PendingAssociatedRemote<viz::mojom::CompositorFrameSink>
@@ -42,8 +44,7 @@
       mojo::PendingAssociatedReceiver<viz::mojom::CompositorFrameSink>
           sink_receiver);
 
-  mojo::PendingAssociatedReceiver<viz::mojom::CompositorFrameSink>
-      pending_sink_receiver_;
+  std::unique_ptr<TestMojoCompositorFrameSink> mojo_sink_;
 
   bool bind_to_client_called_ = false;
   bool bind_to_client_result_ = true;
diff --git a/cc/trees/clip_node.cc b/cc/trees/clip_node.cc
index 7bf24fa..9318ece 100644
--- a/cc/trees/clip_node.cc
+++ b/cc/trees/clip_node.cc
@@ -11,9 +11,16 @@
 
 namespace cc {
 
-ClipNode::ClipNode() = default;
+ClipNode::ClipNode()
+    : id(kInvalidPropertyNodeId),
+      parent_id(kInvalidPropertyNodeId),
+      pixel_moving_filter_id(kInvalidPropertyNodeId),
+      transform_id(kInvalidPropertyNodeId) {}
+
 ClipNode::ClipNode(const ClipNode& other) = default;
+
 ClipNode& ClipNode::operator=(const ClipNode& other) = default;
+
 ClipNode::~ClipNode() = default;
 
 bool ClipNode::AppliesLocalClip() const {
diff --git a/cc/trees/clip_node.h b/cc/trees/clip_node.h
index bab772d4..0d941eaa7 100644
--- a/cc/trees/clip_node.h
+++ b/cc/trees/clip_node.h
@@ -7,7 +7,6 @@
 
 #include "base/containers/stack_container.h"
 #include "cc/cc_export.h"
-#include "cc/trees/property_ids.h"
 #include "ui/gfx/geometry/rect_f.h"
 
 namespace base {
@@ -41,9 +40,9 @@
   bool AppliesLocalClip() const;
 
   // The node index of this node in the clip tree node vector.
-  int id = kInvalidPropertyNodeId;
+  int id;
   // The node index of the parent node in the clip tree node vector.
-  int parent_id = kInvalidPropertyNodeId;
+  int parent_id;
 
   // The clip rect that this node contributes, expressed in the space of its
   // transform node. This field is ignored if AppliesLocalClip() is false.
@@ -66,10 +65,10 @@
   // Instead of applying |clip|, this clip node expands the accumulated clip
   // to include any pixels in the contents that can affect the rendering result
   // with the filter.
-  int pixel_moving_filter_id = kInvalidPropertyNodeId;
+  int pixel_moving_filter_id;
 
   // The id of the transform node that defines the clip node's local space.
-  int transform_id = kInvalidPropertyNodeId;
+  int transform_id;
 
 #if DCHECK_IS_ON()
   bool operator==(const ClipNode& other) const;
diff --git a/cc/trees/effect_node.cc b/cc/trees/effect_node.cc
index 5b048cf..9e3aabb 100644
--- a/cc/trees/effect_node.cc
+++ b/cc/trees/effect_node.cc
@@ -12,12 +12,101 @@
 
 namespace cc {
 
-EffectNode::EffectNode() = default;
+EffectNode::EffectNode()
+    : id(kInvalidPropertyNodeId),
+      parent_id(kInvalidPropertyNodeId),
+      stable_id(INVALID_STABLE_ID),
+      opacity(1.f),
+      screen_space_opacity(1.f),
+      backdrop_filter_quality(1.f),
+      blend_mode(SkBlendMode::kSrcOver),
+      cache_render_surface(false),
+      has_copy_request(false),
+      hidden_by_backface_visibility(false),
+      double_sided(true),
+      trilinear_filtering(false),
+      is_drawn(true),
+      only_draws_visible_content(true),
+      subtree_hidden(false),
+      has_potential_filter_animation(false),
+      has_potential_backdrop_filter_animation(false),
+      has_potential_opacity_animation(false),
+      is_currently_animating_filter(false),
+      is_currently_animating_backdrop_filter(false),
+      is_currently_animating_opacity(false),
+      has_masking_child(false),
+      effect_changed(false),
+      subtree_has_copy_request(false),
+      is_fast_rounded_corner(false),
+      node_or_ancestor_has_filters(false),
+      affected_by_backdrop_filter(false),
+      render_surface_reason(RenderSurfaceReason::kNone),
+      transform_id(0),
+      clip_id(0),
+      target_id(1),
+      closest_ancestor_with_cached_render_surface_id(-1),
+      closest_ancestor_with_copy_request_id(-1),
+      closest_ancestor_being_captured_id(-1),
+      closest_ancestor_with_shared_element_id(-1) {}
+
 EffectNode::EffectNode(const EffectNode& other) = default;
+
 EffectNode::~EffectNode() = default;
 
 #if DCHECK_IS_ON()
-bool EffectNode::operator==(const EffectNode& other) const = default;
+bool EffectNode::operator==(const EffectNode& other) const {
+  return id == other.id && parent_id == other.parent_id &&
+         stable_id == other.stable_id && opacity == other.opacity &&
+         screen_space_opacity == other.screen_space_opacity &&
+         backdrop_filter_quality == other.backdrop_filter_quality &&
+         subtree_capture_id == other.subtree_capture_id &&
+         subtree_size == other.subtree_size &&
+         cache_render_surface == other.cache_render_surface &&
+         has_copy_request == other.has_copy_request &&
+         filters == other.filters &&
+         backdrop_filters == other.backdrop_filters &&
+         backdrop_filter_bounds == other.backdrop_filter_bounds &&
+         backdrop_mask_element_id == other.backdrop_mask_element_id &&
+         mask_filter_info == other.mask_filter_info &&
+         is_fast_rounded_corner == other.is_fast_rounded_corner &&
+         node_or_ancestor_has_filters == other.node_or_ancestor_has_filters &&
+         affected_by_backdrop_filter == other.affected_by_backdrop_filter &&
+         // The specific reason is just for tracing/testing/debugging, so just
+         // check whether a render surface is needed.
+         HasRenderSurface() == other.HasRenderSurface() &&
+         blend_mode == other.blend_mode &&
+         surface_contents_scale == other.surface_contents_scale &&
+         hidden_by_backface_visibility == other.hidden_by_backface_visibility &&
+         double_sided == other.double_sided &&
+         trilinear_filtering == other.trilinear_filtering &&
+         is_drawn == other.is_drawn &&
+         only_draws_visible_content == other.only_draws_visible_content &&
+         subtree_hidden == other.subtree_hidden &&
+         has_potential_filter_animation ==
+             other.has_potential_filter_animation &&
+         has_potential_backdrop_filter_animation ==
+             other.has_potential_backdrop_filter_animation &&
+         has_potential_opacity_animation ==
+             other.has_potential_opacity_animation &&
+         is_currently_animating_filter == other.is_currently_animating_filter &&
+         is_currently_animating_backdrop_filter ==
+             other.is_currently_animating_backdrop_filter &&
+         is_currently_animating_opacity ==
+             other.is_currently_animating_opacity &&
+         has_masking_child == other.has_masking_child &&
+         effect_changed == other.effect_changed &&
+         subtree_has_copy_request == other.subtree_has_copy_request &&
+         transform_id == other.transform_id && clip_id == other.clip_id &&
+         target_id == other.target_id &&
+         closest_ancestor_with_cached_render_surface_id ==
+             other.closest_ancestor_with_cached_render_surface_id &&
+         closest_ancestor_with_copy_request_id ==
+             other.closest_ancestor_with_copy_request_id &&
+         closest_ancestor_being_captured_id ==
+             other.closest_ancestor_being_captured_id &&
+         closest_ancestor_with_shared_element_id ==
+             other.closest_ancestor_with_shared_element_id;
+}
 #endif  // DCHECK_IS_ON()
 
 const char* RenderSurfaceReasonToString(RenderSurfaceReason reason) {
diff --git a/cc/trees/effect_node.h b/cc/trees/effect_node.h
index b5a2eae2..c329bea7 100644
--- a/cc/trees/effect_node.h
+++ b/cc/trees/effect_node.h
@@ -8,7 +8,6 @@
 #include "cc/cc_export.h"
 #include "cc/paint/element_id.h"
 #include "cc/paint/filter_operations.h"
-#include "cc/trees/property_ids.h"
 #include "cc/view_transition/view_transition_element_id.h"
 #include "components/viz/common/surfaces/subtree_capture_id.h"
 #include "components/viz/common/view_transition_element_resource_id.h"
@@ -65,25 +64,25 @@
   EffectNode(const EffectNode& other);
   ~EffectNode();
 
-  enum StableIdLabels { kInvalidStableId = 0 };
+  enum StableIdLabels { INVALID_STABLE_ID = 0 };
 
   // The node index of this node in the effect tree node vector.
-  int id = kInvalidPropertyNodeId;
+  int id;
   // The node index of the parent node in the effect tree node vector.
-  int parent_id = kInvalidPropertyNodeId;
-  // An opaque, unique, stable identifier for this effect that persists across
+  int parent_id;
+  // An opaque, unique, stable identifer for this effect that persists across
   // frame commits. This id is used only for internal implementation
   // details such as RenderSurface and RenderPass ids, and should not
   // be assumed to have semantic meaning.
-  uint64_t stable_id = kInvalidStableId;
+  uint64_t stable_id;
 
-  float opacity = 1.f;
-  float screen_space_opacity = 1.f;
+  float opacity;
+  float screen_space_opacity;
 
   FilterOperations filters;
   FilterOperations backdrop_filters;
   absl::optional<gfx::RRectF> backdrop_filter_bounds;
-  float backdrop_filter_quality = 1.f;
+  float backdrop_filter_quality;
   gfx::PointF filters_origin;
 
   // The element id corresponding to the mask to apply to the filtered backdrop
@@ -95,79 +94,79 @@
   // effect node.
   gfx::MaskFilterInfo mask_filter_info;
 
-  SkBlendMode blend_mode = SkBlendMode::kSrcOver;
+  SkBlendMode blend_mode;
 
   gfx::Vector2dF surface_contents_scale;
 
   viz::SubtreeCaptureId subtree_capture_id;
   gfx::Size subtree_size;
 
-  bool cache_render_surface : 1 = false;
-  bool has_copy_request : 1 = false;
-  bool hidden_by_backface_visibility : 1 = false;
+  bool cache_render_surface : 1;
+  bool has_copy_request : 1;
+  bool hidden_by_backface_visibility : 1;
   // Whether the contents should continue to be visible when rotated such that
   // its back face is facing toward the camera. It's true by default.
-  bool double_sided : 1 = true;
-  bool trilinear_filtering : 1 = false;
-  bool is_drawn : 1 = true;
+  bool double_sided : 1;
+  bool trilinear_filtering : 1;
+  bool is_drawn : 1;
   // In most cases we only need to draw the visible part of any content
   // contributing to the effect. For copy request case, we would need to copy
   // the entire content, and could not only draw the visible part. In the rare
   // case of a backdrop zoom filter we need to take into consideration the
   // content offscreen to make sure the backdrop zoom filter is applied with the
   // correct center.
-  bool only_draws_visible_content : 1 = true;
+  bool only_draws_visible_content : 1;
   // TODO(jaydasika) : Delete this after implementation of
   // SetHideLayerAndSubtree is cleaned up. (crbug.com/595843)
-  bool subtree_hidden : 1 = false;
+  bool subtree_hidden : 1;
   // Whether this node has a potentially running (i.e., irrespective
   // of exact timeline) filter animation.
-  bool has_potential_filter_animation : 1 = false;
+  bool has_potential_filter_animation : 1;
   // Whether this node has a potentially running (i.e., irrespective
   // of exact timeline) backdrop-filter animation.
-  bool has_potential_backdrop_filter_animation : 1 = false;
+  bool has_potential_backdrop_filter_animation : 1;
   // Whether this node has a potentially running (i.e., irrespective
   // of exact timeline) opacity animation.
-  bool has_potential_opacity_animation : 1 = false;
+  bool has_potential_opacity_animation : 1;
   // Whether this node has a currently running filter animation.
-  bool is_currently_animating_filter : 1 = false;
+  bool is_currently_animating_filter : 1;
   // Whether this node has a currently running backdrop-filter animation.
-  bool is_currently_animating_backdrop_filter : 1 = false;
+  bool is_currently_animating_backdrop_filter : 1;
   // Whether this node has a currently running opacity animation.
-  bool is_currently_animating_opacity : 1 = false;
+  bool is_currently_animating_opacity : 1;
   // Whether this node has a child node with kDstIn blend mode.
-  bool has_masking_child : 1 = false;
+  bool has_masking_child : 1;
   // Whether this node's effect has been changed since the last
   // frame. Needed in order to compute damage rect.
-  bool effect_changed : 1 = false;
-  bool subtree_has_copy_request : 1 = false;
+  bool effect_changed : 1;
+  bool subtree_has_copy_request : 1;
   // If set, the effect node tries to not trigger a render surface due to it
   // having a rounded corner.
-  bool is_fast_rounded_corner : 1 = false;
+  bool is_fast_rounded_corner : 1;
   // If the node or it's parent has the filters, it sets to true.
-  bool node_or_ancestor_has_filters : 1 = false;
+  bool node_or_ancestor_has_filters : 1;
   // All node in the subtree starting from the containing render surface, and
   // before the backdrop filter node in pre tree order.
   // This is set and used for the impl-side effect tree only.
-  bool affected_by_backdrop_filter : 1 = false;
+  bool affected_by_backdrop_filter: 1;
   // RenderSurfaceReason::kNone if this effect node should not create a render
   // surface, or the reason that this effect node should create one.
-  RenderSurfaceReason render_surface_reason = RenderSurfaceReason::kNone;
+  RenderSurfaceReason render_surface_reason;
   // The transform node index of the transform to apply to this effect
   // node's content when rendering to a surface.
-  int transform_id = kRootPropertyNodeId;
+  int transform_id;
   // The clip node index of the clip to apply to this effect node's
   // content when rendering to a surface.
-  int clip_id = kRootPropertyNodeId;
+  int clip_id;
 
   // This is the id of the ancestor effect node that induces a
   // RenderSurfaceImpl.
   // This is set and used for the impl-side effect tree only.
-  int target_id = 1;
-  int closest_ancestor_with_cached_render_surface_id = kInvalidPropertyNodeId;
-  int closest_ancestor_with_copy_request_id = kInvalidPropertyNodeId;
-  int closest_ancestor_being_captured_id = kInvalidPropertyNodeId;
-  int closest_ancestor_with_shared_element_id = kInvalidPropertyNodeId;
+  int target_id;
+  int closest_ancestor_with_cached_render_surface_id;
+  int closest_ancestor_with_copy_request_id;
+  int closest_ancestor_being_captured_id;
+  int closest_ancestor_with_shared_element_id;
 
   // Represents a DOM element id for the view transition API.
   ViewTransitionElementId view_transition_shared_element_id;
diff --git a/cc/trees/mutator_host.h b/cc/trees/mutator_host.h
index e5bdf98..74fddb4 100644
--- a/cc/trees/mutator_host.h
+++ b/cc/trees/mutator_host.h
@@ -26,7 +26,7 @@
 
 // Used as the return value of GetAnimationScales() to indicate that there is
 // no active transform animation or the scale cannot be computed.
-inline constexpr float kInvalidScale = 0.f;
+constexpr float kInvalidScale = 0.f;
 
 // A MutatorHost owns all the animation and mutation effects.
 // There is just one MutatorHost for LayerTreeHost on main renderer thread
diff --git a/cc/trees/property_ids.h b/cc/trees/property_ids.h
deleted file mode 100644
index 7a838b90..0000000
--- a/cc/trees/property_ids.h
+++ /dev/null
@@ -1,21 +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 CC_TREES_PROPERTY_IDS_H_
-#define CC_TREES_PROPERTY_IDS_H_
-
-namespace cc {
-
-// Ids of property tree nodes, starting from 0.
-enum {
-  kInvalidPropertyNodeId = -1,
-  kRootPropertyNodeId = 0,
-  kSecondaryRootPropertyNodeId = 1,
-  kContentsRootPropertyNodeId = kSecondaryRootPropertyNodeId,
-  kViewportPropertyNodeId = kSecondaryRootPropertyNodeId
-};
-
-}  // namespace cc
-
-#endif  // CC_TREES_PROPERTY_IDS_H_
diff --git a/cc/trees/property_tree.h b/cc/trees/property_tree.h
index 820fd53..627a9ca 100644
--- a/cc/trees/property_tree.h
+++ b/cc/trees/property_tree.h
@@ -28,7 +28,6 @@
 #include "cc/trees/clip_node.h"
 #include "cc/trees/effect_node.h"
 #include "cc/trees/mutator_host.h"
-#include "cc/trees/property_ids.h"
 #include "cc/trees/scroll_node.h"
 #include "cc/trees/sticky_position_constraint.h"
 #include "cc/trees/transform_node.h"
@@ -62,6 +61,16 @@
 
 class PropertyTrees;
 
+// Property tree node starts from index 0. See equivalent constants in
+// property_tree_manager.cc for comments.
+enum {
+  kInvalidPropertyNodeId = -1,
+  kRootPropertyNodeId = 0,
+  kSecondaryRootPropertyNodeId = 1,
+  kContentsRootPropertyNodeId = kSecondaryRootPropertyNodeId,
+  kViewportPropertyNodeId = kSecondaryRootPropertyNodeId
+};
+
 template <typename T>
 class CC_EXPORT PropertyTree {
   friend class PropertyTrees;
diff --git a/cc/trees/scroll_node.cc b/cc/trees/scroll_node.cc
index 5588ab5c..15819b23 100644
--- a/cc/trees/scroll_node.cc
+++ b/cc/trees/scroll_node.cc
@@ -5,6 +5,7 @@
 #include "cc/trees/scroll_node.h"
 
 #include "cc/base/math_util.h"
+#include "cc/input/main_thread_scrolling_reason.h"
 #include "cc/layers/layer.h"
 #include "cc/paint/element_id.h"
 #include "cc/trees/property_tree.h"
@@ -13,12 +14,48 @@
 
 namespace cc {
 
-ScrollNode::ScrollNode() = default;
+ScrollNode::ScrollNode()
+    : id(kInvalidPropertyNodeId),
+      parent_id(kInvalidPropertyNodeId),
+      main_thread_scrolling_reasons(
+          MainThreadScrollingReason::kNotScrollingOnMain),
+      scrollable(false),
+      max_scroll_offset_affected_by_page_scale(false),
+      scrolls_inner_viewport(false),
+      scrolls_outer_viewport(false),
+      prevent_viewport_scrolling_from_inner(false),
+      should_flatten(false),
+      user_scrollable_horizontal(false),
+      user_scrollable_vertical(false),
+      transform_id(0),
+      overscroll_behavior(OverscrollBehavior::Type::kAuto),
+      is_composited(false) {}
+
 ScrollNode::ScrollNode(const ScrollNode& other) = default;
+
 ScrollNode::~ScrollNode() = default;
 
 #if DCHECK_IS_ON()
-bool ScrollNode::operator==(const ScrollNode& other) const = default;
+bool ScrollNode::operator==(const ScrollNode& other) const {
+  return id == other.id && parent_id == other.parent_id &&
+         scrollable == other.scrollable &&
+         main_thread_scrolling_reasons == other.main_thread_scrolling_reasons &&
+         container_bounds == other.container_bounds && bounds == other.bounds &&
+         max_scroll_offset_affected_by_page_scale ==
+             other.max_scroll_offset_affected_by_page_scale &&
+         scrolls_inner_viewport == other.scrolls_inner_viewport &&
+         prevent_viewport_scrolling_from_inner ==
+             other.prevent_viewport_scrolling_from_inner &&
+         scrolls_outer_viewport == other.scrolls_outer_viewport &&
+         offset_to_transform_parent == other.offset_to_transform_parent &&
+         should_flatten == other.should_flatten &&
+         user_scrollable_horizontal == other.user_scrollable_horizontal &&
+         user_scrollable_vertical == other.user_scrollable_vertical &&
+         element_id == other.element_id && transform_id == other.transform_id &&
+         overscroll_behavior == other.overscroll_behavior &&
+         snap_container_data == other.snap_container_data &&
+         is_composited == other.is_composited;
+}
 #endif
 
 void ScrollNode::AsValueInto(base::trace_event::TracedValue* value) const {
diff --git a/cc/trees/scroll_node.h b/cc/trees/scroll_node.h
index 3ecc057..c8edf41 100644
--- a/cc/trees/scroll_node.h
+++ b/cc/trees/scroll_node.h
@@ -7,12 +7,10 @@
 
 #include "cc/base/region.h"
 #include "cc/cc_export.h"
-#include "cc/input/main_thread_scrolling_reason.h"
 #include "cc/input/overscroll_behavior.h"
 #include "cc/input/scroll_snap_data.h"
 #include "cc/paint/element_id.h"
 #include "cc/paint/filter_operations.h"
-#include "cc/trees/property_ids.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
 #include "ui/gfx/geometry/size.h"
 
@@ -30,12 +28,11 @@
   ~ScrollNode();
 
   // The node index of this node in the scroll tree node vector.
-  int id = kInvalidPropertyNodeId;
+  int id;
   // The node index of the parent node in the scroll tree node vector.
-  int parent_id = kInvalidPropertyNodeId;
+  int parent_id;
 
-  uint32_t main_thread_scrolling_reasons =
-      MainThreadScrollingReason::kNotScrollingOnMain;
+  uint32_t main_thread_scrolling_reasons;
 
   // Size of the container area that the contents scrolls in, not including
   // non-overlay scrollbars. Overlay scrollbars do not affect these bounds.
@@ -50,27 +47,28 @@
   // This is used for subtrees that should not be scrolled independently. For
   // example, when there is a layer that is not scrollable itself but is inside
   // a scrolling layer.
-  bool scrollable : 1 = false;
-  bool max_scroll_offset_affected_by_page_scale : 1 = false;
-  bool scrolls_inner_viewport : 1 = false;
-  bool scrolls_outer_viewport : 1 = false;
-  bool prevent_viewport_scrolling_from_inner : 1 = false;
-  bool should_flatten : 1 = false;
-  bool user_scrollable_horizontal : 1 = false;
-  bool user_scrollable_vertical : 1 = false;
-  bool is_composited : 1 = false;
+  bool scrollable : 1;
+  bool max_scroll_offset_affected_by_page_scale : 1;
+  bool scrolls_inner_viewport : 1;
+  bool scrolls_outer_viewport : 1;
+  bool prevent_viewport_scrolling_from_inner : 1;
+  bool should_flatten : 1;
+  bool user_scrollable_horizontal : 1;
+  bool user_scrollable_vertical : 1;
 
   // This offset is used when |scrollable| is false and there isn't a transform
   // node already present that covers this offset. For layer tree mode only.
   gfx::Vector2dF offset_to_transform_parent;
 
   ElementId element_id;
-  int transform_id = kRootPropertyNodeId;
+  int transform_id;
 
-  OverscrollBehavior overscroll_behavior{OverscrollBehavior::Type::kAuto};
+  OverscrollBehavior overscroll_behavior;
 
   absl::optional<SnapContainerData> snap_container_data;
 
+  bool is_composited : 1;
+
 #if DCHECK_IS_ON()
   bool operator==(const ScrollNode& other) const;
 #endif
diff --git a/cc/trees/transform_node.cc b/cc/trees/transform_node.cc
index 907e0d4e6..d5f1a929 100644
--- a/cc/trees/transform_node.cc
+++ b/cc/trees/transform_node.cc
@@ -12,12 +12,74 @@
 
 namespace cc {
 
-TransformNode::TransformNode() = default;
+TransformNode::TransformNode()
+    : id(kInvalidPropertyNodeId),
+      parent_id(kInvalidPropertyNodeId),
+      parent_frame_id(kInvalidPropertyNodeId),
+      sticky_position_constraint_id(-1),
+      anchor_scroll_containers_data_id(-1),
+      sorting_context_id(0),
+      needs_local_transform_update(true),
+      node_and_ancestors_are_animated_or_invertible(true),
+      is_invertible(true),
+      ancestors_are_invertible(true),
+      has_potential_animation(false),
+      is_currently_animating(false),
+      to_screen_is_potentially_animated(false),
+      flattens_inherited_transform(true),
+      node_and_ancestors_are_flat(true),
+      scrolls(false),
+      should_undo_overscroll(false),
+      should_be_snapped(false),
+      moved_by_outer_viewport_bounds_delta_y(false),
+      in_subtree_of_page_scale_layer(false),
+      transform_changed(false),
+      delegates_to_parent_for_backface(false),
+      will_change_transform(false),
+      node_or_ancestors_will_change_transform(false),
+      maximum_animation_scale(kInvalidScale) {}
+
 TransformNode::TransformNode(const TransformNode&) = default;
+
 TransformNode& TransformNode::operator=(const TransformNode&) = default;
 
 #if DCHECK_IS_ON()
-bool TransformNode::operator==(const TransformNode& other) const = default;
+bool TransformNode::operator==(const TransformNode& other) const {
+  return id == other.id && parent_id == other.parent_id &&
+         parent_frame_id == other.parent_frame_id &&
+         element_id == other.element_id && local == other.local &&
+         origin == other.origin && post_translation == other.post_translation &&
+         to_parent == other.to_parent &&
+         sorting_context_id == other.sorting_context_id &&
+         needs_local_transform_update == other.needs_local_transform_update &&
+         node_and_ancestors_are_animated_or_invertible ==
+             other.node_and_ancestors_are_animated_or_invertible &&
+         is_invertible == other.is_invertible &&
+         ancestors_are_invertible == other.ancestors_are_invertible &&
+         has_potential_animation == other.has_potential_animation &&
+         is_currently_animating == other.is_currently_animating &&
+         to_screen_is_potentially_animated ==
+             other.to_screen_is_potentially_animated &&
+         flattens_inherited_transform == other.flattens_inherited_transform &&
+         node_and_ancestors_are_flat == other.node_and_ancestors_are_flat &&
+         scrolls == other.scrolls &&
+         should_undo_overscroll == other.should_undo_overscroll &&
+         should_be_snapped == other.should_be_snapped &&
+         moved_by_outer_viewport_bounds_delta_y ==
+             other.moved_by_outer_viewport_bounds_delta_y &&
+         in_subtree_of_page_scale_layer ==
+             other.in_subtree_of_page_scale_layer &&
+         delegates_to_parent_for_backface ==
+             other.delegates_to_parent_for_backface &&
+         will_change_transform == other.will_change_transform &&
+         node_or_ancestors_will_change_transform ==
+             other.node_or_ancestors_will_change_transform &&
+         transform_changed == other.transform_changed &&
+         scroll_offset == other.scroll_offset &&
+         snap_amount == other.snap_amount &&
+         maximum_animation_scale == other.maximum_animation_scale &&
+         visible_frame_element_id == other.visible_frame_element_id;
+}
 #endif  // DCHECK_IS_ON()
 
 void TransformNode::AsValueInto(base::trace_event::TracedValue* value) const {
@@ -35,11 +97,18 @@
   MathUtil::AddToTracedValue("snap_amount", snap_amount, value);
 }
 
-TransformCachedNodeData::TransformCachedNodeData() = default;
+TransformCachedNodeData::TransformCachedNodeData()
+    : is_showing_backface(false) {}
+
 TransformCachedNodeData::TransformCachedNodeData(
     const TransformCachedNodeData& other) = default;
+
 TransformCachedNodeData::~TransformCachedNodeData() = default;
+
 bool TransformCachedNodeData::operator==(
-    const TransformCachedNodeData& other) const = default;
+    const TransformCachedNodeData& other) const {
+  return from_screen == other.from_screen && to_screen == other.to_screen &&
+         is_showing_backface == other.is_showing_backface;
+}
 
 }  // namespace cc
diff --git a/cc/trees/transform_node.h b/cc/trees/transform_node.h
index 8292af42..467f8db 100644
--- a/cc/trees/transform_node.h
+++ b/cc/trees/transform_node.h
@@ -7,8 +7,6 @@
 
 #include "cc/cc_export.h"
 #include "cc/paint/element_id.h"
-#include "cc/trees/mutator_host.h"
-#include "cc/trees/property_ids.h"
 #include "ui/gfx/geometry/point3_f.h"
 #include "ui/gfx/geometry/point_f.h"
 #include "ui/gfx/geometry/transform.h"
@@ -28,12 +26,12 @@
   TransformNode& operator=(const TransformNode&);
 
   // The node index of this node in the transform tree node vector.
-  int id = kInvalidPropertyNodeId;
+  int id;
   // The node index of the parent node in the transform tree node vector.
-  int parent_id = kInvalidPropertyNodeId;
+  int parent_id;
   // The node index of the nearest parent frame node in the transform tree node
   // vector.
-  int parent_frame_id = kInvalidPropertyNodeId;
+  int parent_frame_id;
 
   ElementId element_id;
 
@@ -50,80 +48,80 @@
   gfx::Transform to_parent;
 
   // This is the node which defines the sticky position constraints for this
-  // transform node.
-  int sticky_position_constraint_id = kInvalidPropertyNodeId;
+  // transform node. -1 indicates there are no sticky position constraints.
+  int sticky_position_constraint_id;
 
   // This is the data of the scroll container of the anchor node specified by
-  // the `anchor-scroll` property.
-  int anchor_scroll_containers_data_id = kInvalidPropertyNodeId;
+  // the `anchor-scroll` property. -1 indicates there is no such node.
+  int anchor_scroll_containers_data_id;
 
   // This id determines which 3d rendering context the node is in. 0 is a
   // special value and indicates that the node is not in any 3d rendering
   // context.
-  int sorting_context_id = 0;
+  int sorting_context_id;
 
   // True if |TransformTree::UpdateLocalTransform| needs to be called which
   // will update |to_parent|.
-  bool needs_local_transform_update : 1 = true;
+  bool needs_local_transform_update : 1;
 
   // Whether this node or any ancestor has a potentially running
   // (i.e., irrespective of exact timeline) transform animation or an
   // invertible transform.
-  bool node_and_ancestors_are_animated_or_invertible : 1 = true;
+  bool node_and_ancestors_are_animated_or_invertible : 1;
 
-  bool is_invertible : 1 = true;
+  bool is_invertible : 1;
   // Whether the transform from this node to the screen is
   // invertible.
-  bool ancestors_are_invertible : 1 = true;
+  bool ancestors_are_invertible : 1;
 
   // Whether this node has a potentially running (i.e., irrespective
   // of exact timeline) transform animation.
-  bool has_potential_animation : 1 = false;
+  bool has_potential_animation : 1;
   // Whether this node has a currently running transform animation.
-  bool is_currently_animating : 1 = false;
+  bool is_currently_animating : 1;
   // Whether this node *or an ancestor* has a potentially running
   // (i.e., irrespective of exact timeline) transform
   // animation.
-  bool to_screen_is_potentially_animated : 1 = false;
+  bool to_screen_is_potentially_animated : 1;
 
   // Flattening, when needed, is only applied to a node's inherited transform,
   // never to its local transform. It's true by default.
-  bool flattens_inherited_transform : 1 = true;
+  bool flattens_inherited_transform : 1;
 
   // This is true if the to_parent transform at every node on the path to the
   // root is flat.
-  bool node_and_ancestors_are_flat : 1 = true;
+  bool node_and_ancestors_are_flat : 1;
 
-  bool scrolls : 1 = false;
+  bool scrolls : 1;
 
-  bool should_undo_overscroll : 1 = false;
+  bool should_undo_overscroll : 1;
 
-  bool should_be_snapped : 1 = false;
+  bool should_be_snapped : 1;
 
   // Used by the compositor to determine which layers need to be repositioned by
   // the compositor as a result of browser controls expanding/contracting the
   // outer viewport size before Blink repositions the fixed layers.
-  bool moved_by_outer_viewport_bounds_delta_y : 1 = false;
+  bool moved_by_outer_viewport_bounds_delta_y : 1;
 
   // Layer scale factor is used as a fallback when we either cannot adjust
   // raster scale or if the raster scale cannot be extracted from the screen
   // space transform. For layers in the subtree of the page scale layer, the
   // layer scale factor should include the page scale factor.
-  bool in_subtree_of_page_scale_layer : 1 = false;
+  bool in_subtree_of_page_scale_layer : 1;
 
   // We need to track changes to to_screen transform to compute the damage rect.
-  bool transform_changed : 1 = false;
+  bool transform_changed : 1;
 
   // Whether the parent transform node should be used for checking backface
   // visibility, not this transform one.
-  bool delegates_to_parent_for_backface : 1 = false;
+  bool delegates_to_parent_for_backface : 1;
 
   // Set to true, if the compositing reason is will-change:transform, scale,
   // rotate, or translate (for the CSS property that created this node).
-  bool will_change_transform : 1 = false;
+  bool will_change_transform : 1;
 
   // Set to true, if the node or it's parent |will_change_transform| is true.
-  bool node_or_ancestors_will_change_transform : 1 = false;
+  bool node_or_ancestors_will_change_transform : 1;
 
   gfx::PointF scroll_offset;
 
@@ -135,7 +133,7 @@
   // From MutatorHost::GetMaximuimAnimationScale(). Updated by
   // PropertyTrees::MaximumAnimationScaleChanged() and
   // LayerTreeImpl::UpdateTransformAnimation().
-  float maximum_animation_scale = kInvalidScale;
+  float maximum_animation_scale;
 
   // Set to the element ID of containing document if this transform node is the
   // root of a visible frame subtree.
@@ -162,7 +160,7 @@
   gfx::Transform from_screen;
   gfx::Transform to_screen;
 
-  bool is_showing_backface = false;
+  bool is_showing_backface : 1;
 
   bool operator==(const TransformCachedNodeData& other) const;
 };
diff --git a/chrome/android/chrome_java_sources.gni b/chrome/android/chrome_java_sources.gni
index 30d92f4..8c6760b 100644
--- a/chrome/android/chrome_java_sources.gni
+++ b/chrome/android/chrome_java_sources.gni
@@ -504,6 +504,7 @@
   "java/src/org/chromium/chrome/browser/customtabs/features/sessionrestore/TabFreezer.java",
   "java/src/org/chromium/chrome/browser/customtabs/features/toolbar/BrandingSecurityButtonAnimationDelegate.java",
   "java/src/org/chromium/chrome/browser/customtabs/features/toolbar/CustomTabBrowserControlsVisibilityDelegate.java",
+  "java/src/org/chromium/chrome/browser/customtabs/features/toolbar/CustomTabCaptureStateToken.java",
   "java/src/org/chromium/chrome/browser/customtabs/features/toolbar/CustomTabToolbar.java",
   "java/src/org/chromium/chrome/browser/customtabs/features/toolbar/CustomTabToolbarAnimationDelegate.java",
   "java/src/org/chromium/chrome/browser/customtabs/features/toolbar/CustomTabToolbarColorController.java",
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkItemsAdapter.java b/chrome/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkItemsAdapter.java
index 4f24c76..96dca3d1 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkItemsAdapter.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkItemsAdapter.java
@@ -6,14 +6,13 @@
 
 import android.content.Context;
 import android.text.TextUtils;
-import android.view.LayoutInflater;
 import android.view.MotionEvent;
 import android.view.View;
 import android.view.ViewGroup;
 import android.widget.LinearLayout;
 import android.widget.TextView;
 
-import androidx.annotation.LayoutRes;
+import androidx.annotation.NonNull;
 import androidx.annotation.VisibleForTesting;
 import androidx.recyclerview.widget.RecyclerView;
 import androidx.recyclerview.widget.RecyclerView.ViewHolder;
@@ -26,21 +25,16 @@
 import org.chromium.chrome.browser.flags.ChromeFeatureList;
 import org.chromium.chrome.browser.profiles.Profile;
 import org.chromium.chrome.browser.sync.SyncService;
-import org.chromium.chrome.browser.ui.messages.snackbar.SnackbarManager;
 import org.chromium.chrome.browser.ui.signin.PersonalizedSigninPromoView;
 import org.chromium.chrome.browser.ui.signin.SyncPromoController.SyncPromoState;
 import org.chromium.components.bookmarks.BookmarkId;
 import org.chromium.components.bookmarks.BookmarkItem;
 import org.chromium.components.bookmarks.BookmarkType;
-import org.chromium.components.browser_ui.util.GlobalDiscardableReferencePool;
 import org.chromium.components.browser_ui.widget.dragreorder.DragReorderableListAdapter;
 import org.chromium.components.browser_ui.widget.highlight.ViewHighlighter;
 import org.chromium.components.browser_ui.widget.highlight.ViewHighlighter.HighlightParams;
 import org.chromium.components.browser_ui.widget.highlight.ViewHighlighter.HighlightShape;
 import org.chromium.components.feature_engagement.EventConstants;
-import org.chromium.components.image_fetcher.ImageFetcher;
-import org.chromium.components.image_fetcher.ImageFetcherConfig;
-import org.chromium.components.image_fetcher.ImageFetcherFactory;
 import org.chromium.components.power_bookmarks.PowerBookmarkMeta;
 
 import java.util.ArrayList;
@@ -54,9 +48,19 @@
     private static final int MAXIMUM_NUMBER_OF_SEARCH_RESULTS = 500;
     private static final String EMPTY_QUERY = null;
 
-    private final ImageFetcher mImageFetcher;
+    /** Abstraction around how to build type specific {@link View}s. */
+    interface ViewFactory {
+        /**
+         * @param parent The parent to which the new {@link View} will be added as a child.
+         * @param viewType The type of row being created.
+         * @return A new View that can be added to the view hierarchy.
+         */
+        View buildView(@NonNull ViewGroup parent, @ViewType int viewType);
+    }
+
     private final List<BookmarkId> mTopLevelFolders = new ArrayList<>();
-    private final SnackbarManager mSnackbarManager;
+    private final Profile mProfile;
+    private final SyncService mSyncService;
 
     // There can only be one promo header at a time. This takes on one of the values:
     // ViewType.PERSONALIZED_SIGNIN_PROMO, ViewType.SYNC_PROMO, or ViewType.INVALID.
@@ -64,9 +68,9 @@
     private int mPromoHeaderType = ViewType.INVALID;
     private BookmarkDelegate mDelegate;
     private BookmarkPromoHeader mPromoHeaderManager;
+    private ViewFactory mViewFactory;
     private String mSearchText;
     private BookmarkId mCurrentFolder;
-    private SyncService mSyncService;
 
     // Keep track of the currently highlighted bookmark - used for "show in folder" action.
     private BookmarkId mHighlightedBookmark;
@@ -125,16 +129,11 @@
         }
     };
 
-    BookmarkItemsAdapter(Context context, SnackbarManager snackbarManager) {
+    BookmarkItemsAdapter(Context context, Profile profile) {
         super(context);
+        mProfile = profile;
         mSyncService = SyncService.get();
         mSyncService.addSyncStateChangedListener(this);
-
-        mImageFetcher =
-                ImageFetcherFactory.createImageFetcher(ImageFetcherConfig.IN_MEMORY_WITH_DISK_CACHE,
-                        Profile.getLastUsedRegularProfile().getProfileKey(),
-                        GlobalDiscardableReferencePool.getReferencePool());
-        mSnackbarManager = snackbarManager;
     }
 
     /**
@@ -210,63 +209,9 @@
         return entry.getViewType();
     }
 
-    private ViewHolder createViewHolderHelper(ViewGroup parent, @LayoutRes int layoutId) {
-        // create the row associated with this adapter
-        ViewGroup row = (ViewGroup) LayoutInflater.from(parent.getContext())
-                                .inflate(layoutId, parent, false);
-
-        // ViewHolder is abstract and it cannot be instantiated directly.
-        ViewHolder holder = new ViewHolder(row) {};
-        ((BookmarkRow) row).onDelegateInitialized(mDelegate);
-        return holder;
-    }
-
     @Override
     public ViewHolder onCreateViewHolder(ViewGroup parent, @ViewType int viewType) {
-        assert mDelegate != null;
-
-        // The shopping-specific bookmark row is only shown with the visual refresh. When there's a
-        // mismatch, the ViewType is downgraded to ViewType.BOOKMARK.
-        if (viewType == ViewType.SHOPPING_POWER_BOOKMARK
-                && !BookmarkFeatures.isBookmarksVisualRefreshEnabled()) {
-            viewType = ViewType.BOOKMARK;
-        }
-
-        switch (viewType) {
-            case ViewType.PERSONALIZED_SIGNIN_PROMO:
-                // fall through
-            case ViewType.PERSONALIZED_SYNC_PROMO:
-                return mPromoHeaderManager.createPersonalizedSigninAndSyncPromoHolder(parent);
-            case ViewType.SYNC_PROMO:
-                return mPromoHeaderManager.createSyncPromoHolder(parent);
-            case ViewType.SECTION_HEADER:
-                return createSectionHeaderViewHolder(parent, viewType);
-            case ViewType.FOLDER:
-                return createViewHolderHelper(parent, R.layout.bookmark_folder_row);
-            case ViewType.BOOKMARK:
-                return createViewHolderHelper(parent, R.layout.bookmark_item_row);
-            case ViewType.SHOPPING_POWER_BOOKMARK:
-                ViewHolder vh = null;
-                if (BookmarkFeatures.isBookmarksVisualRefreshEnabled()) {
-                    vh = createViewHolderHelper(parent, R.layout.power_bookmark_shopping_item_row);
-                    ((PowerBookmarkShoppingItemRow) vh.itemView)
-                            .init(mImageFetcher, mDelegate.getModel(), mSnackbarManager);
-                } else {
-                    vh = createViewHolderHelper(parent, R.layout.bookmark_item_row);
-                }
-                return vh;
-            case ViewType.DIVIDER:
-                return new ViewHolder(
-                        LayoutInflater.from(parent.getContext())
-                                .inflate(R.layout.horizontal_divider, parent, false)) {};
-            case ViewType.SHOPPING_FILTER:
-                return new ViewHolder(
-                        LayoutInflater.from(parent.getContext())
-                                .inflate(R.layout.shopping_filter_row, parent, false)) {};
-            default:
-                assert false;
-                return null;
-        }
+        return new ViewHolder(mViewFactory.buildView(parent, viewType)) {};
     }
 
     @Override
@@ -274,8 +219,7 @@
         if (holder.getItemViewType() == ViewType.PERSONALIZED_SIGNIN_PROMO
                 || holder.getItemViewType() == ViewType.PERSONALIZED_SYNC_PROMO) {
             PersonalizedSigninPromoView view =
-                    (PersonalizedSigninPromoView) holder.itemView.findViewById(
-                            R.id.signin_promo_view_container);
+                    holder.itemView.findViewById(R.id.signin_promo_view_container);
             mPromoHeaderManager.setUpSyncPromoView(view);
         } else if (holder.getItemViewType() == ViewType.SECTION_HEADER) {
             bindSectionHeaderViewHolder(holder.itemView, getItemByPosition(position));
@@ -304,19 +248,10 @@
         } else if (holder.getItemViewType() == ViewType.SHOPPING_FILTER) {
             LinearLayout layout = ((LinearLayout) holder.itemView);
             layout.setClickable(true);
-            layout.setOnClickListener(
-                    (view) -> { mDelegate.openFolder(BookmarkId.SHOPPING_FOLDER); });
+            layout.setOnClickListener((view) -> mDelegate.openFolder(BookmarkId.SHOPPING_FOLDER));
         }
     }
 
-    private ViewHolder createSectionHeaderViewHolder(ViewGroup parent, @ViewType int viewType) {
-        ViewGroup sectionHeader = (ViewGroup) LayoutInflater.from(parent.getContext())
-                                          .inflate(R.layout.bookmark_section_header, parent, false);
-
-        // ViewHolder is abstract and it cannot be instantiated directly.
-        return new ViewHolder(sectionHeader) {};
-    }
-
     private void bindSectionHeaderViewHolder(View view, BookmarkListEntry listItem) {
         TextView title = view.findViewById(R.id.title);
         title.setText(listItem.getHeaderTitle());
@@ -344,8 +279,9 @@
      * Sets the delegate to use to handle UI actions related to this adapter.
      *
      * @param delegate A {@link BookmarkDelegate} instance to handle all backend interaction.
+     * @param viewFactory An object to create {@link View}s for each item/row.
      */
-    void onBookmarkDelegateInitialized(BookmarkDelegate delegate) {
+    void onBookmarkDelegateInitialized(BookmarkDelegate delegate, ViewFactory viewFactory) {
         mDelegate = delegate;
         mDelegate.addUIObserver(this);
         mDelegate.getModel().addObserver(mBookmarkModelObserver);
@@ -359,6 +295,8 @@
         mPromoHeaderManager = new BookmarkPromoHeader(mContext, promoHeaderChangeAction);
         populateTopLevelFoldersList();
 
+        mViewFactory = viewFactory;
+
         mElements = new ArrayList<>();
         setDragStateDelegate(delegate.getDragStateDelegate());
         notifyDataSetChanged();
@@ -394,8 +332,8 @@
             mDelegate.getSelectableListLayout().setEmptyViewText(
                     R.string.tracked_products_empty_list_title);
         } else if (folder.getType() == BookmarkType.READING_LIST) {
-            TrackerFactory.getTrackerForProfile(Profile.getLastUsedRegularProfile())
-                    .notifyEvent(EventConstants.READ_LATER_BOOKMARK_FOLDER_OPENED);
+            TrackerFactory.getTrackerForProfile(mProfile).notifyEvent(
+                    EventConstants.READ_LATER_BOOKMARK_FOLDER_OPENED);
             mDelegate.getSelectableListLayout().setEmptyViewText(
                     R.string.reading_list_empty_list_title);
         } else {
@@ -610,6 +548,10 @@
         return entry.getBookmarkItem().getId();
     }
 
+    public BookmarkPromoHeader getPromoHeaderManager() {
+        return mPromoHeaderManager;
+    }
+
     private boolean hasPromoHeader() {
         return mPromoHeaderType != ViewType.INVALID;
     }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkListEntry.java b/chrome/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkListEntry.java
index e920c07..7850f13 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkListEntry.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkListEntry.java
@@ -27,7 +27,8 @@
     @Retention(RetentionPolicy.SOURCE)
     @IntDef({ViewType.INVALID, ViewType.PERSONALIZED_SIGNIN_PROMO, ViewType.PERSONALIZED_SYNC_PROMO,
             ViewType.SYNC_PROMO, ViewType.FOLDER, ViewType.BOOKMARK, ViewType.DIVIDER,
-            ViewType.SECTION_HEADER, ViewType.SHOPPING_POWER_BOOKMARK, ViewType.TAG_CHIP_LIST})
+            ViewType.SECTION_HEADER, ViewType.SHOPPING_POWER_BOOKMARK, ViewType.TAG_CHIP_LIST,
+            ViewType.SHOPPING_FILTER})
     public @interface ViewType {
         int INVALID = -1;
         int PERSONALIZED_SIGNIN_PROMO = 0;
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkManager.java b/chrome/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkManager.java
index ea3cc98f..1b87ac43 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkManager.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkManager.java
@@ -92,9 +92,10 @@
     private boolean mIsIncognito;
     private boolean mIsDestroyed;
 
-    private BookmarkItemsAdapter mAdapter;
-    private BookmarkDragStateDelegate mDragStateDelegate;
-    private AdapterDataObserver mAdapterDataObserver;
+    private final BookmarkItemsAdapter mAdapter;
+    private final BookmarkManagerCoordinator mBookmarkManagerCoordinator;
+    private final BookmarkDragStateDelegate mDragStateDelegate;
+    private final AdapterDataObserver mAdapterDataObserver;
     private final BookmarkOpener mBookmarkOpener;
 
     private final ObservableSupplierImpl<Boolean> mBackPressStateSupplier =
@@ -239,7 +240,8 @@
         mSelectableListLayout.getHandleBackPressChangedSupplier().addObserver(
                 (x) -> onBackPressStateChanged());
 
-        mAdapter = new BookmarkItemsAdapter(mContext, snackbarManager);
+        mAdapter = new BookmarkItemsAdapter(mContext, profile);
+        mBookmarkManagerCoordinator = new BookmarkManagerCoordinator(profile, snackbarManager);
 
         mAdapterDataObserver = new AdapterDataObserver() {
             @Override
@@ -270,7 +272,10 @@
         if (!sPreventLoadingForTesting) {
             Runnable modelLoadedRunnable = () -> {
                 mDragStateDelegate.onBookmarkDelegateInitialized(BookmarkManager.this);
-                mAdapter.onBookmarkDelegateInitialized(BookmarkManager.this);
+                mAdapter.onBookmarkDelegateInitialized(
+                        BookmarkManager.this, mBookmarkManagerCoordinator::createView);
+                mBookmarkManagerCoordinator.onBookmarkDelegateInitialized(
+                        this, mAdapter.getPromoHeaderManager());
                 mToolbar.onBookmarkDelegateInitialized(BookmarkManager.this);
                 mAdapter.addDragListener(mToolbar);
 
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkManagerCoordinator.java b/chrome/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkManagerCoordinator.java
index e335f11..df8f710 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkManagerCoordinator.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkManagerCoordinator.java
@@ -4,5 +4,138 @@
 
 package org.chromium.chrome.browser.bookmarks;
 
+import android.content.Context;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+import androidx.annotation.LayoutRes;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import org.chromium.chrome.browser.bookmarks.BookmarkListEntry.ViewType;
+import org.chromium.chrome.browser.profiles.Profile;
+import org.chromium.chrome.browser.ui.messages.snackbar.SnackbarManager;
+import org.chromium.components.browser_ui.util.GlobalDiscardableReferencePool;
+import org.chromium.components.image_fetcher.ImageFetcher;
+import org.chromium.components.image_fetcher.ImageFetcherConfig;
+import org.chromium.components.image_fetcher.ImageFetcherFactory;
+
 /** Responsible for setting up sub-components and routing incoming/outgoing signals */
-public class BookmarkManagerCoordinator {}
+public class BookmarkManagerCoordinator {
+    private final @NonNull ImageFetcher mImageFetcher;
+    private final @NonNull SnackbarManager mSnackbarManager;
+
+    private @Nullable BookmarkPromoHeader mPromoHeaderManager;
+    private @Nullable BookmarkDelegate mDelegate;
+
+    /**
+     * Creates a partially initialized instance, needs {@link onBookmarkDelegateInitialized}.
+     * @param profile Profile instance corresponding to the active profile.
+     * @param snackbarManager Allows control over the app snackbar.
+     */
+    public BookmarkManagerCoordinator(
+            @NonNull Profile profile, @NonNull SnackbarManager snackbarManager) {
+        mImageFetcher =
+                ImageFetcherFactory.createImageFetcher(ImageFetcherConfig.IN_MEMORY_WITH_DISK_CACHE,
+                        profile.getProfileKey(), GlobalDiscardableReferencePool.getReferencePool());
+        mSnackbarManager = snackbarManager;
+    }
+
+    /**
+     * Fully initializes, ready to be used after this is called.
+     * @param delegate A {@link BookmarkDelegate} instance to handle all backend interaction.
+     * @param bookmarkPromoHeader Used to show the signin promo header.
+     */
+    public void onBookmarkDelegateInitialized(
+            @NonNull BookmarkDelegate delegate, @NonNull BookmarkPromoHeader bookmarkPromoHeader) {
+        mDelegate = delegate;
+        mPromoHeaderManager = bookmarkPromoHeader;
+    }
+
+    /**
+     * Should only be called after fully initialized.
+     * @param parent The parent to which the new {@link View} will be added as a child.
+     * @param viewType The type of row being created.
+     * @return A new View that can be added to the view hierarchy.
+     */
+    public View createView(@NonNull ViewGroup parent, @ViewType int viewType) {
+        assert mDelegate != null;
+
+        // The shopping-specific bookmark row is only shown with the visual refresh. When
+        // there's a mismatch, the ViewType is downgraded to ViewType.BOOKMARK.
+        if (viewType == ViewType.SHOPPING_POWER_BOOKMARK
+                && !BookmarkFeatures.isBookmarksVisualRefreshEnabled()) {
+            viewType = ViewType.BOOKMARK;
+        }
+
+        switch (viewType) {
+            case ViewType.PERSONALIZED_SIGNIN_PROMO:
+            case ViewType.PERSONALIZED_SYNC_PROMO:
+                return buildPersonalizedPromoView(parent);
+            case ViewType.SYNC_PROMO:
+                return buildLegacyPromoView(parent);
+            case ViewType.SECTION_HEADER:
+                return buildSectionHeaderView(parent);
+            case ViewType.FOLDER:
+                return buildBookmarkFolderView(parent);
+            case ViewType.BOOKMARK:
+                return buildBookmarkItemView(parent);
+            case ViewType.SHOPPING_POWER_BOOKMARK:
+                return buildShoppingItemView(parent);
+            case ViewType.DIVIDER:
+                return buildDividerView(parent);
+            case ViewType.SHOPPING_FILTER:
+                return buildShoppingFilterView(parent);
+            default:
+                assert false;
+                return null;
+        }
+    }
+
+    private View buildPersonalizedPromoView(ViewGroup parent) {
+        return mPromoHeaderManager.createPersonalizedSigninAndSyncPromoHolder(parent);
+    }
+
+    private View buildLegacyPromoView(ViewGroup parent) {
+        return mPromoHeaderManager.createSyncPromoHolder(parent);
+    }
+
+    private View buildSectionHeaderView(ViewGroup parent) {
+        return inflate(parent, org.chromium.chrome.R.layout.bookmark_section_header);
+    }
+
+    private View buildBookmarkFolderView(ViewGroup parent) {
+        return inflateBookmarkRow(parent, org.chromium.chrome.R.layout.bookmark_folder_row);
+    }
+
+    private View buildBookmarkItemView(ViewGroup parent) {
+        return inflateBookmarkRow(parent, org.chromium.chrome.R.layout.bookmark_item_row);
+    }
+
+    private View buildShoppingItemView(ViewGroup parent) {
+        PowerBookmarkShoppingItemRow row = (PowerBookmarkShoppingItemRow) inflateBookmarkRow(
+                parent, org.chromium.chrome.R.layout.power_bookmark_shopping_item_row);
+        row.init(mImageFetcher, mDelegate.getModel(), mSnackbarManager);
+        return row;
+    }
+
+    private View buildDividerView(ViewGroup parent) {
+        return inflate(parent, org.chromium.chrome.R.layout.horizontal_divider);
+    }
+
+    private View buildShoppingFilterView(ViewGroup parent) {
+        return inflate(parent, org.chromium.chrome.R.layout.shopping_filter_row);
+    }
+
+    private View inflate(ViewGroup parent, @LayoutRes int layoutId) {
+        Context context = parent.getContext();
+        return LayoutInflater.from(context).inflate(layoutId, parent, false);
+    }
+
+    private View inflateBookmarkRow(ViewGroup parent, @LayoutRes int layoutId) {
+        BookmarkRow row = (BookmarkRow) inflate(parent, layoutId);
+        (row).onDelegateInitialized(mDelegate);
+        return row;
+    }
+}
\ No newline at end of file
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkPromoHeader.java b/chrome/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkPromoHeader.java
index b3adb6a4..692d3a8a 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkPromoHeader.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkPromoHeader.java
@@ -11,8 +11,6 @@
 
 import androidx.annotation.Nullable;
 import androidx.annotation.VisibleForTesting;
-import androidx.recyclerview.widget.RecyclerView;
-import androidx.recyclerview.widget.RecyclerView.ViewHolder;
 
 import org.chromium.chrome.R;
 import org.chromium.chrome.browser.preferences.ChromePreferenceKeys;
@@ -108,28 +106,15 @@
         return mPromoState;
     }
 
-    /**
-     * @return Personalized signin promo header {@link ViewHolder} instance that can be used with
-     *         {@link RecyclerView}.
-     */
-    ViewHolder createPersonalizedSigninAndSyncPromoHolder(ViewGroup parent) {
-        View view = LayoutInflater.from(mContext).inflate(
+    /** Returns personalized signin promo header {@link View}. */
+    View createPersonalizedSigninAndSyncPromoHolder(ViewGroup parent) {
+        return LayoutInflater.from(mContext).inflate(
                 R.layout.sync_promo_view_bookmarks, parent, false);
-
-        // ViewHolder is abstract and it cannot be instantiated directly.
-        return new ViewHolder(view) {};
     }
 
-    /**
-     * @return Sync promo header {@link ViewHolder} instance that can be used with
-     *         {@link RecyclerView}.
-     */
-    ViewHolder createSyncPromoHolder(ViewGroup parent) {
-        LegacySyncPromoView view =
-                LegacySyncPromoView.create(parent, SigninAccessPoint.BOOKMARK_MANAGER);
-
-        // ViewHolder is abstract and it cannot be instantiated directly.
-        return new ViewHolder(view) {};
+    /** Returns sync promo header {@link View}. */
+    View createSyncPromoHolder(ViewGroup parent) {
+        return LegacySyncPromoView.create(parent, SigninAccessPoint.BOOKMARK_MANAGER);
     }
 
     /**
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/BaseCustomTabRootUiCoordinator.java b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/BaseCustomTabRootUiCoordinator.java
index 80c731e..e5afbb1 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/BaseCustomTabRootUiCoordinator.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/BaseCustomTabRootUiCoordinator.java
@@ -298,7 +298,8 @@
                 mActivityLifecycleDispatcher, mFullscreenManager,
                 DeviceFormFactor.isWindowOnTablet(mWindowAndroid),
                 intentDataProvider.canInteractWithBackground(),
-                intentDataProvider.showSideSheetMaximizeButton());
+                intentDataProvider.showSideSheetMaximizeButton(),
+                intentDataProvider.getActivitySideSheetDecorationType());
     }
 
     @Override
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabIntentDataProvider.java b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabIntentDataProvider.java
index 7f39edaa..6f7bc8b 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabIntentDataProvider.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabIntentDataProvider.java
@@ -262,6 +262,13 @@
             "androidx.browser.customtabs.extra.ACTIVITY_SIDE_SHEET_BREAKPOINT_DP";
 
     /**
+     * Extra that, if set, allows you to set how you want to distinguish the PCCT side sheet from
+     * the rest of the display. Options include shadow, a divider line, or no decoration.
+     */
+    public static final String EXTRA_ACTIVITY_SIDE_SHEET_DECORATION_TYPE =
+            "androidx.browser.customtabs.extra.ACTIVITY_SIDE_SHEET_DECORATION_TYPE";
+
+    /**
      * Extra that, if set, makes the toolbar's top corner radii to be x pixels. This will only have
      * effect if the custom tab is behaving as a bottom sheet. Currently, this is capped at 16dp.
      * TODO(jinsukkim): Deprecate this.
@@ -322,6 +329,8 @@
     private List<CustomButtonParams> mToolbarButtons = new ArrayList<>(1);
     private List<CustomButtonParams> mBottombarButtons = new ArrayList<>(2);
     private RemoteViews mRemoteViews;
+    @SideSheetDecorationType
+    private int mSideSheetDecorationType;
     private int[] mClickableViewIds;
     private PendingIntent mRemoteViewsPendingIntent;
     private PendingIntent mSecondaryToolbarSwipeUpPendingIntent;
@@ -425,6 +434,15 @@
         return breakPointDp < 0 ? DEFAULT_BREAKPOINT_DP : breakPointDp;
     }
 
+    private static int getActivitySideSheetDecorationTypeFromIntent(Intent intent) {
+        int decorationType =
+                IntentUtils.safeGetIntExtra(intent, EXTRA_ACTIVITY_SIDE_SHEET_DECORATION_TYPE,
+                        ACTIVITY_SIDE_SHEET_DECORATION_TYPE_DEFAULT);
+        return decorationType < 0 || decorationType > ACTIVITY_SIDE_SHEET_DECORATION_TYPE_MAX
+                ? ACTIVITY_SIDE_SHEET_DECORATION_TYPE_DEFAULT
+                : decorationType;
+    }
+
     /**
      * Get the package name from {@link #getReferrerUriString(Activity)}. If the referrer format
      * is invalid, return an empty string.
@@ -558,6 +576,7 @@
         int backgroundInteractBehavior = IntentUtils.safeGetIntExtra(
                 intent, EXTRA_ENABLE_BACKGROUND_INTERACTION, BACKGROUND_INTERACT_DEFAULT);
         mInteractWithBackground = backgroundInteractBehavior != BACKGROUND_INTERACT_OFF;
+        mSideSheetDecorationType = getActivitySideSheetDecorationTypeFromIntent(intent);
 
         logCustomTabFeatures(intent, colorScheme, usingDynamicFeatures);
     }
@@ -839,6 +858,10 @@
                     intent, CustomTabIntentDataProvider.EXTRA_ACTIVITY_SIDE_SHEET_BREAKPOINT_DP)) {
             featureUsage.log(CustomTabsFeature.EXTRA_ACTIVITY_SIDE_SHEET_BREAKPOINT_DP);
         }
+        if (IntentUtils.safeHasExtra(intent,
+                    CustomTabIntentDataProvider.EXTRA_ACTIVITY_SIDE_SHEET_DECORATION_TYPE)) {
+            featureUsage.log(CustomTabsFeature.EXTRA_ACTIVITY_SIDE_SHEET_DECORATION_TYPE);
+        }
         if (mEnableEmbeddedMediaExperience) {
             featureUsage.log(CustomTabsFeature.EXTRA_ENABLE_EMBEDDED_MEDIA_EXPERIENCE);
         }
@@ -1209,6 +1232,12 @@
         return version;
     }
 
+    @SideSheetDecorationType
+    @Override
+    public int getActivitySideSheetDecorationType() {
+        return mSideSheetDecorationType;
+    }
+
     @Override
     @Nullable
     public int[] getGsaExperimentIds() {
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabsFeatureUsage.java b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabsFeatureUsage.java
index 72d4a21..c1524418 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabsFeatureUsage.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabsFeatureUsage.java
@@ -51,7 +51,8 @@
             CustomTabsFeature.EXTRA_ACTIVITY_SIDE_SHEET_BREAKPOINT_DP,
             CustomTabsFeature.EXTRA_INITIAL_ACTIVITY_WIDTH_PX,
             CustomTabsFeature.EXTRA_ACTIVITY_SIDE_SHEET_ENABLE_MAXIMIZATION,
-            CustomTabsFeature.EXTRA_SECONDARY_TOOLBAR_SWIPE_UP_ACTION, CustomTabsFeature.COUNT})
+            CustomTabsFeature.EXTRA_SECONDARY_TOOLBAR_SWIPE_UP_ACTION,
+            CustomTabsFeature.EXTRA_ACTIVITY_SIDE_SHEET_DECORATION_TYPE, CustomTabsFeature.COUNT})
     @Retention(RetentionPolicy.SOURCE)
     public @interface CustomTabsFeature {
         /** Special enum for the start of a session. */
@@ -104,9 +105,10 @@
         int EXTRA_INITIAL_ACTIVITY_WIDTH_PX = 46;
         int EXTRA_ACTIVITY_SIDE_SHEET_ENABLE_MAXIMIZATION = 47;
         int EXTRA_SECONDARY_TOOLBAR_SWIPE_UP_ACTION = 48;
+        int EXTRA_ACTIVITY_SIDE_SHEET_DECORATION_TYPE = 49;
 
         /** Total count of entries. */
-        int COUNT = 49;
+        int COUNT = 50;
     }
 
     // Whether flag-enabled or not.
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/features/partialcustomtab/CustomTabHeightStrategy.java b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/features/partialcustomtab/CustomTabHeightStrategy.java
index 13df306..c19849e 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/features/partialcustomtab/CustomTabHeightStrategy.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/features/partialcustomtab/CustomTabHeightStrategy.java
@@ -32,7 +32,8 @@
             @Px int initialWidth, int breakPointDp, boolean isPartialCustomTabFixedHeight,
             CustomTabsConnection connection, @Nullable CustomTabsSessionToken session,
             ActivityLifecycleDispatcher lifecycleDispatcher, FullscreenManager fullscreenManager,
-            boolean isTablet, boolean interactWithBackground, boolean showMaximizeButton) {
+            boolean isTablet, boolean interactWithBackground, boolean showMaximizeButton,
+            int decorationType) {
         if (initialHeight <= 0
                 && (!ChromeFeatureList.sCctResizableSideSheet.isEnabled() || initialWidth <= 0)) {
             return new CustomTabHeightStrategy();
@@ -44,7 +45,7 @@
                     (height, width)
                             -> connection.onResized(session, height, width),
                     lifecycleDispatcher, fullscreenManager, isTablet, interactWithBackground,
-                    showMaximizeButton);
+                    showMaximizeButton, decorationType);
         } else {
             return new PartialCustomTabBottomSheetStrategy(activity, initialHeight,
                     isPartialCustomTabFixedHeight,
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/features/partialcustomtab/PartialCustomTabDisplayManager.java b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/features/partialcustomtab/PartialCustomTabDisplayManager.java
index bac9839..70cc86b 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/features/partialcustomtab/PartialCustomTabDisplayManager.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/features/partialcustomtab/PartialCustomTabDisplayManager.java
@@ -32,6 +32,7 @@
 
     private final Activity mActivity;
     private final int mBreakPointDp;
+    private final int mDecorationType;
     private final @Px int mUnclampedInitialHeight;
     private final @Px int mUnclampedInitialWidth;
     private final boolean mIsFixedHeight;
@@ -62,7 +63,7 @@
             @Px int initialWidth, int breakPointDp, boolean isFixedHeight,
             OnResizedCallback onResizedCallback, ActivityLifecycleDispatcher lifecycleDispatcher,
             FullscreenManager fullscreenManager, boolean isTablet, boolean interactWithBackground,
-            boolean showMaximizeButton) {
+            boolean showMaximizeButton, int decorationType) {
         mActivity = activity;
         mUnclampedInitialHeight = initialHeight;
         mUnclampedInitialWidth = initialWidth;
@@ -73,6 +74,7 @@
         mIsTablet = isTablet;
         mInteractWithBackground = interactWithBackground;
         mShowMaximizeButton = showMaximizeButton;
+        mDecorationType = decorationType;
 
         mActivityLifecycleDispatcher = lifecycleDispatcher;
         lifecycleDispatcher.register(this);
@@ -233,7 +235,7 @@
             case PartialCustomTabType.SIDE_SHEET: {
                 return new PartialCustomTabSideSheetStrategy(mActivity, mUnclampedInitialWidth,
                         mOnResizedCallback, mFullscreenManager, mIsTablet, mInteractWithBackground,
-                        mShowMaximizeButton, maximized, mHandleStrategyFactory);
+                        mShowMaximizeButton, maximized, mHandleStrategyFactory, mDecorationType);
             }
             case PartialCustomTabType.FULL_SIZE: {
                 return new PartialCustomTabFullSizeStrategy(mActivity, mOnResizedCallback,
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/features/partialcustomtab/PartialCustomTabSideSheetStrategy.java b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/features/partialcustomtab/PartialCustomTabSideSheetStrategy.java
index c853149..10faad09 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/features/partialcustomtab/PartialCustomTabSideSheetStrategy.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/features/partialcustomtab/PartialCustomTabSideSheetStrategy.java
@@ -6,6 +6,9 @@
 
 import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
 
+import static org.chromium.chrome.browser.browserservices.intents.BrowserServicesIntentDataProvider.ACTIVITY_SIDE_SHEET_DECORATION_TYPE_DIVIDER;
+import static org.chromium.chrome.browser.browserservices.intents.BrowserServicesIntentDataProvider.ACTIVITY_SIDE_SHEET_DECORATION_TYPE_NONE;
+
 import android.animation.ValueAnimator;
 import android.app.Activity;
 import android.graphics.drawable.GradientDrawable;
@@ -33,12 +36,13 @@
     private final boolean mShowMaximizeButton;
 
     private boolean mIsMaximized;
+    private int mDecorationType;
 
     public PartialCustomTabSideSheetStrategy(Activity activity, @Px int initialWidth,
             CustomTabHeightStrategy.OnResizedCallback onResizedCallback,
             FullscreenManager fullscreenManager, boolean isTablet, boolean interactWithBackground,
             boolean showMaximizeButton, boolean startMaximized,
-            PartialCustomTabHandleStrategyFactory handleStrategyFactory) {
+            PartialCustomTabHandleStrategyFactory handleStrategyFactory, int decorationType) {
         super(activity, onResizedCallback, fullscreenManager, isTablet, interactWithBackground,
                 handleStrategyFactory);
 
@@ -46,6 +50,7 @@
         mShowMaximizeButton = showMaximizeButton;
         mPositionUpdater = this::updatePosition;
         mIsMaximized = startMaximized;
+        mDecorationType = decorationType;
 
         setupAnimator();
     }
@@ -104,9 +109,10 @@
 
         // For smooth animation, make the window full-width and then translate it
         // rather than resizing the window itself during the animation.
-        setWindowWidth(mVersionCompat.getDisplayWidth());
+        int displayWidth = mVersionCompat.getDisplayWidth();
+        setWindowWidth(displayWidth);
         int start = mActivity.getWindow().getAttributes().x;
-        int end = mIsMaximized ? 0 : mVersionCompat.getDisplayWidth() - mUnclampedInitialWidth;
+        int end = mIsMaximized ? 0 : displayWidth - calculateWidth(mUnclampedInitialWidth);
         startAnimation(start, end, this::onMaximizeProgress, this::onMaximizeEnd);
         return mIsMaximized;
     }
@@ -186,7 +192,8 @@
     @Override
     protected boolean shouldHaveNoShadowOffset() {
         // We remove shadow in maximized mode.
-        return isMaximized();
+        return isMaximized() || mDecorationType == ACTIVITY_SIDE_SHEET_DECORATION_TYPE_NONE
+                || mDecorationType == ACTIVITY_SIDE_SHEET_DECORATION_TYPE_DIVIDER;
     }
 
     @Override
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/features/toolbar/CustomTabCaptureStateToken.java b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/features/toolbar/CustomTabCaptureStateToken.java
new file mode 100644
index 0000000..ac64a66
--- /dev/null
+++ b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/features/toolbar/CustomTabCaptureStateToken.java
@@ -0,0 +1,59 @@
+// 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.customtabs.features.toolbar;
+
+import androidx.annotation.ColorInt;
+import androidx.annotation.DrawableRes;
+import androidx.annotation.Nullable;
+
+import org.chromium.chrome.browser.toolbar.top.ToolbarSnapshotDifference;
+
+import java.util.Objects;
+
+/**
+ * The idea of this class is to hold all of the properties that materially change the way the
+ * toolbar looks. If two tokens are identical (no difference is found), then there should be
+ * no reason to perform a bitmap capture.
+ */
+class CustomTabCaptureStateToken {
+    private final String mUrl;
+    private final String mTitle;
+    private final @ColorInt int mBackgroundColor;
+    private final @DrawableRes int mSecurityIconRes;
+    private @Nullable final Object mAnimationToken;
+    public CustomTabCaptureStateToken(String url, String title, @ColorInt int backgroundColor,
+            @DrawableRes int securityIconRes, boolean isInAnimation) {
+        mUrl = url;
+        mTitle = title;
+        mBackgroundColor = backgroundColor;
+        mSecurityIconRes = securityIconRes;
+        // When animations are in progress, tokens should never be equal. Object should use
+        // reference equality, resulting in a difference unless both are null or the objects
+        // are actually the same object.
+        mAnimationToken = isInAnimation ? new Object() : null;
+    }
+
+    /**
+     * Compares two tokens and looks for any difference. If multiple are present only one will
+     * be returned. ToolbarSnapshotDifference.NONE indicates the two tokens are the same.
+     */
+    public @ToolbarSnapshotDifference int getAnyDifference(CustomTabCaptureStateToken that) {
+        if (that == null) {
+            return ToolbarSnapshotDifference.NULL;
+        } else if (!Objects.equals(mUrl, that.mUrl)) {
+            return ToolbarSnapshotDifference.URL_TEXT;
+        } else if (!Objects.equals(mTitle, that.mTitle)) {
+            return ToolbarSnapshotDifference.TITLE_TEXT;
+        } else if (mBackgroundColor != that.mBackgroundColor) {
+            return ToolbarSnapshotDifference.TINT;
+        } else if (mSecurityIconRes != that.mSecurityIconRes) {
+            return ToolbarSnapshotDifference.SECURITY_ICON;
+        } else if (!Objects.equals(mAnimationToken, that.mAnimationToken)) {
+            return ToolbarSnapshotDifference.CCT_ANIMATION;
+        } else {
+            return ToolbarSnapshotDifference.NONE;
+        }
+    }
+}
\ No newline at end of file
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/features/toolbar/CustomTabToolbar.java b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/features/toolbar/CustomTabToolbar.java
index 26d3aa17..4ad6356 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/features/toolbar/CustomTabToolbar.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/features/toolbar/CustomTabToolbar.java
@@ -80,7 +80,7 @@
 import org.chromium.chrome.browser.toolbar.top.CaptureReadinessResult.TopToolbarBlockCaptureReason;
 import org.chromium.chrome.browser.toolbar.top.ToolbarLayout;
 import org.chromium.chrome.browser.toolbar.top.ToolbarPhone;
-import org.chromium.chrome.browser.toolbar.top.ToolbarSnapshotState.ToolbarSnapshotDifference;
+import org.chromium.chrome.browser.toolbar.top.ToolbarSnapshotDifference;
 import org.chromium.chrome.browser.ui.native_page.NativePage;
 import org.chromium.chrome.browser.ui.theme.BrandedColorScheme;
 import org.chromium.components.browser_ui.styles.ChromeColors;
@@ -101,8 +101,6 @@
 import org.chromium.ui.widget.Toast;
 import org.chromium.url.GURL;
 
-import java.util.Objects;
-
 /**
  * The Toolbar layout to be used for a custom tab. This is used for both phone and tablet UIs.
  */
@@ -128,7 +126,7 @@
     private final CustomTabLocationBar mLocationBar = new CustomTabLocationBar();
     private LocationBarModel mLocationBarModel;
     private BrowserStateBrowserControlsVisibilityDelegate mBrowserControlsVisibilityDelegate;
-    private @Nullable CaptureStateToken mLastCaptureStateToken;
+    private @Nullable CustomTabCaptureStateToken mLastCustomTabCaptureStateToken;
 
     /**
      * Whether to use the toolbar as handle to resize the Window height.
@@ -724,9 +722,9 @@
         if (ToolbarFeatures.shouldBlockCapturesForAblation()) {
             return CaptureReadinessResult.notReady(TopToolbarBlockCaptureReason.SCROLL_ABLATION);
         } else if (ToolbarFeatures.shouldSuppressCaptures()) {
-            CaptureStateToken currentToken = generateCaptureStateToken();
+            CustomTabCaptureStateToken currentToken = generateCaptureStateToken();
             final @ToolbarSnapshotDifference int difference =
-                    currentToken.getAnyDifference(mLastCaptureStateToken);
+                    currentToken.getAnyDifference(mLastCustomTabCaptureStateToken);
             if (difference == ToolbarSnapshotDifference.NONE) {
                 return CaptureReadinessResult.notReady(TopToolbarBlockCaptureReason.SNAPSHOT_SAME);
             } else {
@@ -740,61 +738,15 @@
     @Override
     public void setTextureCaptureMode(boolean textureMode) {
         if (textureMode) {
-            mLastCaptureStateToken = generateCaptureStateToken();
+            mLastCustomTabCaptureStateToken = generateCaptureStateToken();
         }
     }
 
-    /**
-     * The idea of this class is to hold all of the properties that materially change the way the
-     * toolbar looks. If two tokens are identical (no difference is found), then there should be
-     * no reason to perform a bitmap capture.
-     */
-    private static class CaptureStateToken {
-        private final String mUrl;
-        private final String mTitle;
-        private final @ColorInt int mBackgroundColor;
-        private final @DrawableRes int mSecurityIconRes;
-        private @Nullable final Object mAnimationToken;
-        public CaptureStateToken(String url, String title, @ColorInt int backgroundColor,
-                @DrawableRes int securityIconRes, boolean isInAnimation) {
-            mUrl = url;
-            mTitle = title;
-            mBackgroundColor = backgroundColor;
-            mSecurityIconRes = securityIconRes;
-            // When animations are in progress, tokens should never be equal. Object should use
-            // reference equality, resulting in a difference unless both are null or the objects
-            // are actually the same object.
-            mAnimationToken = isInAnimation ? new Object() : null;
-        }
-
-        /**
-         * Compares two tokens and looks for any difference. If multiple are present only one will
-         * be returned. ToolbarSnapshotDifference.NONE indicates the two tokens are the same.
-         */
-        public @ToolbarSnapshotDifference int getAnyDifference(CaptureStateToken that) {
-            if (that == null) {
-                return ToolbarSnapshotDifference.NULL;
-            } else if (!Objects.equals(mUrl, that.mUrl)) {
-                return ToolbarSnapshotDifference.URL_TEXT;
-            } else if (!Objects.equals(mTitle, that.mTitle)) {
-                return ToolbarSnapshotDifference.TITLE_TEXT;
-            } else if (mBackgroundColor != that.mBackgroundColor) {
-                return ToolbarSnapshotDifference.TINT;
-            } else if (mSecurityIconRes != that.mSecurityIconRes) {
-                return ToolbarSnapshotDifference.SECURITY_ICON;
-            } else if (!Objects.equals(mAnimationToken, that.mAnimationToken)) {
-                return ToolbarSnapshotDifference.CCT_ANIMATION;
-            } else {
-                return ToolbarSnapshotDifference.NONE;
-            }
-        }
-    }
-
-    private CaptureStateToken generateCaptureStateToken() {
+    private CustomTabCaptureStateToken generateCaptureStateToken() {
         // Must convert CharSequence to String in order for equality to be clearly defined.
         String url = mLocationBar.mUrlBar.getText().toString();
         String title = mLocationBar.mTitleBar.getText().toString();
-        return new CaptureStateToken(url, title, getBackground().getColor(),
+        return new CustomTabCaptureStateToken(url, title, getBackground().getColor(),
                 mLocationBar.mAnimDelegate.getSecurityIconRes(),
                 mLocationBar.mAnimDelegate.isInAnimation());
     }
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/customtabs/CustomTabIntentDataProviderTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/customtabs/CustomTabIntentDataProviderTest.java
index f0becac..ebe04ce5 100644
--- a/chrome/android/junit/src/org/chromium/chrome/browser/customtabs/CustomTabIntentDataProviderTest.java
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/customtabs/CustomTabIntentDataProviderTest.java
@@ -640,6 +640,57 @@
         assertNull(provider.getSecondaryToolbarSwipeUpPendingIntent());
     }
 
+    @Test
+    public void testActivityDecorationType_Default() {
+        // Decoration not set
+        Intent intent = new CustomTabsIntent.Builder().build().intent;
+        var dataProvider = new CustomTabIntentDataProvider(intent, mContext, COLOR_SCHEME_LIGHT);
+        assertEquals("Decoration types do not match",
+                CustomTabIntentDataProvider.ACTIVITY_SIDE_SHEET_DECORATION_TYPE_DEFAULT,
+                dataProvider.getActivitySideSheetDecorationType());
+
+        // Decoration set higher than max
+        intent.putExtra(CustomTabIntentDataProvider.EXTRA_ACTIVITY_SIDE_SHEET_DECORATION_TYPE,
+                CustomTabIntentDataProvider.ACTIVITY_SIDE_SHEET_DECORATION_TYPE_MAX + 1);
+        var dataProvider2 = new CustomTabIntentDataProvider(intent, mContext, COLOR_SCHEME_LIGHT);
+        assertEquals("Decoration types do not match",
+                CustomTabIntentDataProvider.ACTIVITY_SIDE_SHEET_DECORATION_TYPE_DEFAULT,
+                dataProvider2.getActivitySideSheetDecorationType());
+    }
+
+    @Test
+    public void testActivityDecorationType_Shadow() {
+        Intent intent = new CustomTabsIntent.Builder().build().intent;
+        intent.putExtra(CustomTabIntentDataProvider.EXTRA_ACTIVITY_SIDE_SHEET_DECORATION_TYPE,
+                CustomTabIntentDataProvider.ACTIVITY_SIDE_SHEET_DECORATION_TYPE_SHADOW);
+        var dataProvider = new CustomTabIntentDataProvider(intent, mContext, COLOR_SCHEME_LIGHT);
+        assertEquals("Decoration types do not match",
+                CustomTabIntentDataProvider.ACTIVITY_SIDE_SHEET_DECORATION_TYPE_SHADOW,
+                dataProvider.getActivitySideSheetDecorationType());
+    }
+
+    @Test
+    public void testActivityDecorationType_DividerLine() {
+        Intent intent = new CustomTabsIntent.Builder().build().intent;
+        intent.putExtra(CustomTabIntentDataProvider.EXTRA_ACTIVITY_SIDE_SHEET_DECORATION_TYPE,
+                CustomTabIntentDataProvider.ACTIVITY_SIDE_SHEET_DECORATION_TYPE_DIVIDER);
+        var dataProvider = new CustomTabIntentDataProvider(intent, mContext, COLOR_SCHEME_LIGHT);
+        assertEquals("Decoration types do not match",
+                CustomTabIntentDataProvider.ACTIVITY_SIDE_SHEET_DECORATION_TYPE_DIVIDER,
+                dataProvider.getActivitySideSheetDecorationType());
+    }
+
+    @Test
+    public void testActivityDecorationType_None() {
+        Intent intent = new CustomTabsIntent.Builder().build().intent;
+        intent.putExtra(CustomTabIntentDataProvider.EXTRA_ACTIVITY_SIDE_SHEET_DECORATION_TYPE,
+                CustomTabIntentDataProvider.ACTIVITY_SIDE_SHEET_DECORATION_TYPE_NONE);
+        var dataProvider = new CustomTabIntentDataProvider(intent, mContext, COLOR_SCHEME_LIGHT);
+        assertEquals("Decoration types do not match",
+                CustomTabIntentDataProvider.ACTIVITY_SIDE_SHEET_DECORATION_TYPE_NONE,
+                dataProvider.getActivitySideSheetDecorationType());
+    }
+
     private Bundle createActionButtonInToolbarBundle() {
         Bundle bundle = new Bundle();
         bundle.putInt(CustomTabsIntent.KEY_ID, CustomTabsIntent.TOOLBAR_ACTION_BUTTON_ID);
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/customtabs/features/partialcustomtab/PartialCustomTabDisplayManagerTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/customtabs/features/partialcustomtab/PartialCustomTabDisplayManagerTest.java
index 3badb36b..50f5475 100644
--- a/chrome/android/junit/src/org/chromium/chrome/browser/customtabs/features/partialcustomtab/PartialCustomTabDisplayManagerTest.java
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/customtabs/features/partialcustomtab/PartialCustomTabDisplayManagerTest.java
@@ -53,7 +53,7 @@
         PartialCustomTabDisplayManager displayManager = new PartialCustomTabDisplayManager(
                 mPCCTTestRule.mActivity, heightPx, widthPx, breakPointDp, false,
                 mPCCTTestRule.mOnResizedCallback, mPCCTTestRule.mActivityLifecycleDispatcher,
-                mPCCTTestRule.mFullscreenManager, false, true, /*showMaximizeButton=*/true);
+                mPCCTTestRule.mFullscreenManager, false, true, /*showMaximizeButton=*/true, 0);
         var sizeStrategyCreator = displayManager.getSizeStrategyCreatorForTesting();
         SizeStrategyCreator testSizeStrategyCreator = (type, maximized) -> {
             var strategy = sizeStrategyCreator.createForType(type, maximized);
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/customtabs/features/partialcustomtab/PartialCustomTabSideSheetStrategyTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/customtabs/features/partialcustomtab/PartialCustomTabSideSheetStrategyTest.java
index eb6860b9..89eb525 100644
--- a/chrome/android/junit/src/org/chromium/chrome/browser/customtabs/features/partialcustomtab/PartialCustomTabSideSheetStrategyTest.java
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/customtabs/features/partialcustomtab/PartialCustomTabSideSheetStrategyTest.java
@@ -58,7 +58,7 @@
         PartialCustomTabSideSheetStrategy pcct = new PartialCustomTabSideSheetStrategy(
                 mPCCTTestRule.mActivity, widthPx, mPCCTTestRule.mOnResizedCallback,
                 mPCCTTestRule.mFullscreenManager, false, true, /*showMaximizedButton=*/true,
-                /*startMaximized=*/false, mPCCTTestRule.mHandleStrategyFactory);
+                /*startMaximized=*/false, mPCCTTestRule.mHandleStrategyFactory, 0);
         pcct.setMockViewForTesting(mPCCTTestRule.mCoordinatorLayout, mPCCTTestRule.mToolbarView,
                 mPCCTTestRule.mToolbarCoordinator);
         return pcct;
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/customtabs/features/toolbar/CustomTabToolbarUnitTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/customtabs/features/toolbar/CustomTabToolbarUnitTest.java
index b8660fc..415fc3b 100644
--- a/chrome/android/junit/src/org/chromium/chrome/browser/customtabs/features/toolbar/CustomTabToolbarUnitTest.java
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/customtabs/features/toolbar/CustomTabToolbarUnitTest.java
@@ -66,7 +66,7 @@
 import org.chromium.chrome.browser.toolbar.menu_button.MenuButtonCoordinator;
 import org.chromium.chrome.browser.toolbar.top.CaptureReadinessResult;
 import org.chromium.chrome.browser.toolbar.top.NavigationPopup.HistoryDelegate;
-import org.chromium.chrome.browser.toolbar.top.ToolbarSnapshotState.ToolbarSnapshotDifference;
+import org.chromium.chrome.browser.toolbar.top.ToolbarSnapshotDifference;
 import org.chromium.chrome.browser.toolbar.top.ToolbarTablet.OfflineDownloader;
 import org.chromium.chrome.test.util.browser.Features;
 import org.chromium.chrome.test.util.browser.Features.DisableFeatures;
diff --git a/chrome/app/generated_resources.grd b/chrome/app/generated_resources.grd
index 1e9cae3ab..a9704ad 100644
--- a/chrome/app/generated_resources.grd
+++ b/chrome/app/generated_resources.grd
@@ -14154,7 +14154,7 @@
     <message name="IDS_VERIFY_SHEET_TITLE" desc="Header shown to the user while signing in happens.">
       Verifying…
     </message>
-    <message name="IDS_VERIFY_SHEET_TITLE_AUTO_REAUTHN" desc="Header for auto re-authentication sheet. Sheet is shown to notify the user that they are re-authenticating into a website using an account from an identity provider." translateable="false">
+    <message name="IDS_VERIFY_SHEET_TITLE_AUTO_REAUTHN" desc="Header for auto re-authentication sheet. Sheet is shown to notify the user that they are re-authenticating into a website using an account from an identity provider.">
       Signing you in…
     </message>
     <message name="IDS_AUTO_REAUTHN_OPTOUT_CHECKBOX" desc="Allows users to opt out of FedCM auto re-authentication by unchecking the checkbox" translateable="false">
diff --git a/chrome/app/generated_resources_grd/IDS_VERIFY_SHEET_TITLE_AUTO_REAUTHN.png.sha1 b/chrome/app/generated_resources_grd/IDS_VERIFY_SHEET_TITLE_AUTO_REAUTHN.png.sha1
new file mode 100644
index 0000000..581e67f
--- /dev/null
+++ b/chrome/app/generated_resources_grd/IDS_VERIFY_SHEET_TITLE_AUTO_REAUTHN.png.sha1
@@ -0,0 +1 @@
+b826261d4f1d3385632bca55233b43f1540abbf3
\ No newline at end of file
diff --git a/chrome/app/vector_icons/letter_spacing.icon b/chrome/app/vector_icons/letter_spacing.icon
index 43ebaef4..ea5eabb 100644
--- a/chrome/app/vector_icons/letter_spacing.icon
+++ b/chrome/app/vector_icons/letter_spacing.icon
@@ -6,30 +6,30 @@
 MOVE_TO, 0, 13,
 LINE_TO, 3, 16,
 V_LINE_TO, 14,
-H_LINE_TO, 11,
+H_LINE_TO, 13,
 V_LINE_TO, 16,
-LINE_TO, 14, 13,
-LINE_TO, 11, 10,
+LINE_TO, 16, 13,
+LINE_TO, 13, 10,
 V_LINE_TO, 12,
 H_LINE_TO, 3,
 V_LINE_TO, 10,
 LINE_TO, 0, 13,
 CLOSE,
-MOVE_TO, 2, 9,
+MOVE_TO, 3, 9,
 V_LINE_TO, 0,
-H_LINE_TO, 4,
+H_LINE_TO, 5,
 V_LINE_TO, 9,
-H_LINE_TO, 2,
+H_LINE_TO, 3,
 CLOSE,
-MOVE_TO, 6, 9,
+MOVE_TO, 7, 9,
 V_LINE_TO, 0,
-H_LINE_TO, 8,
+H_LINE_TO, 9,
 V_LINE_TO, 9,
-H_LINE_TO, 6,
+H_LINE_TO, 7,
 CLOSE,
-MOVE_TO, 10, 9,
+MOVE_TO, 11, 9,
 V_LINE_TO, 0,
-H_LINE_TO, 12,
+H_LINE_TO, 13,
 V_LINE_TO, 9,
-H_LINE_TO, 10,
+H_LINE_TO, 11,
 CLOSE
diff --git a/chrome/browser/about_flags.cc b/chrome/browser/about_flags.cc
index 4434338..6ad5e7c 100644
--- a/chrome/browser/about_flags.cc
+++ b/chrome/browser/about_flags.cc
@@ -4041,10 +4041,6 @@
      flag_descriptions::kScreenshotsForAndroidV2Name,
      flag_descriptions::kScreenshotsForAndroidV2Description, kOsAndroid,
      FEATURE_VALUE_TYPE(share::kScreenshotsForAndroidV2)},
-    {"voice-button-in-top-toolbar",
-     flag_descriptions::kVoiceButtonInTopToolbarName,
-     flag_descriptions::kVoiceButtonInTopToolbarDescription, kOsAndroid,
-     FEATURE_VALUE_TYPE(chrome::android::kVoiceButtonInTopToolbar)},
     {"assistant-consent-simplified-text",
      flag_descriptions::kAssistantConsentSimplifiedTextName,
      flag_descriptions::kAssistantConsentSimplifiedTextDescription, kOsAndroid,
diff --git a/chrome/browser/android/browserservices/intents/java/src/org/chromium/chrome/browser/browserservices/intents/BrowserServicesIntentDataProvider.java b/chrome/browser/android/browserservices/intents/java/src/org/chromium/chrome/browser/browserservices/intents/BrowserServicesIntentDataProvider.java
index 807f754..c6043ca 100644
--- a/chrome/browser/android/browserservices/intents/java/src/org/chromium/chrome/browser/browserservices/intents/BrowserServicesIntentDataProvider.java
+++ b/chrome/browser/android/browserservices/intents/java/src/org/chromium/chrome/browser/browserservices/intents/BrowserServicesIntentDataProvider.java
@@ -82,6 +82,17 @@
      */
     public static final int ACTIVITY_HEIGHT_FIXED = 2;
 
+    @IntDef({ACTIVITY_SIDE_SHEET_DECORATION_TYPE_DEFAULT, ACTIVITY_SIDE_SHEET_DECORATION_TYPE_NONE,
+            ACTIVITY_SIDE_SHEET_DECORATION_TYPE_SHADOW,
+            ACTIVITY_SIDE_SHEET_DECORATION_TYPE_DIVIDER})
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface SideSheetDecorationType {}
+    public static final int ACTIVITY_SIDE_SHEET_DECORATION_TYPE_DEFAULT = 0;
+    public static final int ACTIVITY_SIDE_SHEET_DECORATION_TYPE_NONE = 1;
+    public static final int ACTIVITY_SIDE_SHEET_DECORATION_TYPE_SHADOW = 2;
+    public static final int ACTIVITY_SIDE_SHEET_DECORATION_TYPE_DIVIDER = 3;
+    public static final int ACTIVITY_SIDE_SHEET_DECORATION_TYPE_MAX = 3;
+
     /**
      * @return The type of the Activity;
      */
@@ -529,6 +540,13 @@
     }
 
     /**
+     * @return An int representing the side sheet decoration type for the Activity.
+     */
+    public int getActivitySideSheetDecorationType() {
+        return ACTIVITY_SIDE_SHEET_DECORATION_TYPE_DEFAULT;
+    }
+
+    /**
      * Returns the {@link CloseButtonPosition}.
      */
     public @CloseButtonPosition int getCloseButtonPosition() {
diff --git a/chrome/browser/android/compositor/scene_layer/top_toolbar_scene_layer.cc b/chrome/browser/android/compositor/scene_layer/top_toolbar_scene_layer.cc
index 87ea9a0..923e2ec 100644
--- a/chrome/browser/android/compositor/scene_layer/top_toolbar_scene_layer.cc
+++ b/chrome/browser/android/compositor/scene_layer/top_toolbar_scene_layer.cc
@@ -6,8 +6,10 @@
 
 #include "base/android/jni_android.h"
 #include "base/android/jni_array.h"
+#include "base/feature_list.h"
 #include "cc/slim/solid_color_layer.h"
 #include "chrome/browser/android/compositor/layer/toolbar_layer.h"
+#include "chrome/browser/browser_features.h"
 #include "chrome/browser/ui/android/toolbar/jni_headers/TopToolbarSceneLayer_jni.h"
 #include "ui/android/resources/resource_manager_impl.h"
 #include "ui/gfx/android/java_bitmap.h"
@@ -42,18 +44,25 @@
     bool show_shadow,
     bool visible,
     bool anonymize) {
+  ui::ResourceManager* resource_manager =
+      ui::ResourceManagerImpl::FromJavaObject(jresource_manager);
   // If the toolbar layer has not been created yet, create it.
   if (!toolbar_layer_) {
-    ui::ResourceManager* resource_manager =
-        ui::ResourceManagerImpl::FromJavaObject(jresource_manager);
     toolbar_layer_ = ToolbarLayer::Create(resource_manager);
     toolbar_layer_->layer()->SetHideLayerAndSubtree(true);
     layer_->AddChild(toolbar_layer_->layer());
   }
 
   toolbar_layer_->layer()->SetHideLayerAndSubtree(!visible);
-  if (!visible)
+  if (!visible) {
+    if (base::FeatureList::IsEnabled(features::kKeepToolbarTexture)) {
+      // Uploading the Toolbar texture on scroll is a source of jank, and the
+      // toolbar becomes visible frequently enough for it to be worth holding
+      // onto the texture even when not visible.
+      resource_manager->MarkTintNonDiscardable(url_bar_color);
+    }
     return;
+  }
 
   toolbar_layer_->PushResource(toolbar_resource_id, toolbar_background_color,
                                anonymize, url_bar_color, url_bar_resource_id,
diff --git a/chrome/browser/android/customtabs/tab_interaction_recorder_android.cc b/chrome/browser/android/customtabs/tab_interaction_recorder_android.cc
index e20f982..320b234b 100644
--- a/chrome/browser/android/customtabs/tab_interaction_recorder_android.cc
+++ b/chrome/browser/android/customtabs/tab_interaction_recorder_android.cc
@@ -54,19 +54,20 @@
   Invalidate();
 }
 
-void AutofillObserverImpl::OnFormSubmitted() {
+void AutofillObserverImpl::OnFormSubmitted(autofill::AutofillManager&) {
   OnFormInteraction();
 }
 
-void AutofillObserverImpl::OnSelectControlDidChange() {
+void AutofillObserverImpl::OnSelectControlDidChange(
+    autofill::AutofillManager&) {
   OnFormInteraction();
 }
 
-void AutofillObserverImpl::OnTextFieldDidChange() {
+void AutofillObserverImpl::OnTextFieldDidChange(autofill::AutofillManager&) {
   OnFormInteraction();
 }
 
-void AutofillObserverImpl::OnTextFieldDidScroll() {
+void AutofillObserverImpl::OnTextFieldDidScroll(autofill::AutofillManager&) {
   OnFormInteraction();
 }
 
diff --git a/chrome/browser/android/customtabs/tab_interaction_recorder_android.h b/chrome/browser/android/customtabs/tab_interaction_recorder_android.h
index cc5757e..6b667dc9 100644
--- a/chrome/browser/android/customtabs/tab_interaction_recorder_android.h
+++ b/chrome/browser/android/customtabs/tab_interaction_recorder_android.h
@@ -34,10 +34,10 @@
   ~AutofillObserverImpl() override;
 
   // AutofillManager::Observer:
-  void OnFormSubmitted() override;
-  void OnSelectControlDidChange() override;
-  void OnTextFieldDidChange() override;
-  void OnTextFieldDidScroll() override;
+  void OnFormSubmitted(autofill::AutofillManager&) override;
+  void OnSelectControlDidChange(autofill::AutofillManager&) override;
+  void OnTextFieldDidChange(autofill::AutofillManager&) override;
+  void OnTextFieldDidScroll(autofill::AutofillManager&) override;
 
  private:
   void OnFormInteraction();
diff --git a/chrome/browser/ash/BUILD.gn b/chrome/browser/ash/BUILD.gn
index ffdc8a1..2542872 100644
--- a/chrome/browser/ash/BUILD.gn
+++ b/chrome/browser/ash/BUILD.gn
@@ -3375,6 +3375,7 @@
     "//chrome/browser/ash/fusebox:fusebox_staging_proto",
     "//chrome/browser/ash/guest_os:guest_os_diagnostics_mojom",
     "//chrome/browser/ash/login/oobe_quick_start",
+    "//chrome/browser/ash/login/oobe_quick_start/logging",
     "//chrome/browser/ash/power/ml/smart_dim",
     "//chrome/browser/ash/system_web_apps/types",
     "//chrome/browser/ash/video_conference",
diff --git a/chrome/browser/ash/crosapi/feedback_ash.cc b/chrome/browser/ash/crosapi/feedback_ash.cc
index 4b73ef407..0b15c73 100644
--- a/chrome/browser/ash/crosapi/feedback_ash.cc
+++ b/chrome/browser/ash/crosapi/feedback_ash.cc
@@ -26,6 +26,8 @@
       return chrome::kFeedbackSourceChromeLabs;
     case mojom::LacrosFeedbackSource::kLacrosQuickAnswers:
       return chrome::kFeedbackSourceQuickAnswers;
+    case mojom::LacrosFeedbackSource::kLacrosWindowLayoutMenu:
+      return chrome::kFeedbackSourceWindowLayoutMenu;
     case mojom::LacrosFeedbackSource::kUnknown:
       return chrome::kFeedbackSourceUnknownLacrosSource;
   }
diff --git a/chrome/browser/ash/login/gaia_reauth_token_fetcher.cc b/chrome/browser/ash/login/gaia_reauth_token_fetcher.cc
index b7afa64..6e9d2e6 100644
--- a/chrome/browser/ash/login/gaia_reauth_token_fetcher.cc
+++ b/chrome/browser/ash/login/gaia_reauth_token_fetcher.cc
@@ -16,7 +16,6 @@
 #include "chrome/browser/browser_process.h"
 #include "chrome/browser/net/system_network_context_manager.h"
 #include "chromeos/ash/components/login/auth/recovery/service_constants.h"
-#include "google_apis/credentials_mode.h"
 #include "google_apis/google_api_keys.h"
 #include "net/base/load_flags.h"
 #include "net/base/url_util.h"
@@ -60,8 +59,7 @@
   resource_request->url = GetFetchReauthTokenUrl();
   resource_request->load_flags =
       net::LOAD_DISABLE_CACHE | net::LOAD_DO_NOT_SAVE_COOKIES;
-  resource_request->credentials_mode =
-      google_apis::GetOmitCredentialsModeForGaiaRequests();
+  resource_request->credentials_mode = network::mojom::CredentialsMode::kOmit;
   resource_request->method = "GET";
 
   // TODO(b/197615068): Update the "policy" field in the traffic
diff --git a/chrome/browser/ash/login/marketing_backend_connector.cc b/chrome/browser/ash/login/marketing_backend_connector.cc
index b97cd0b..5a05a5e 100644
--- a/chrome/browser/ash/login/marketing_backend_connector.cc
+++ b/chrome/browser/ash/login/marketing_backend_connector.cc
@@ -22,7 +22,6 @@
 #include "components/signin/public/identity_manager/scope_set.h"
 #include "components/user_manager/user.h"
 #include "components/user_manager/user_manager.h"
-#include "google_apis/credentials_mode.h"
 #include "net/base/load_flags.h"
 #include "net/http/http_request_headers.h"
 #include "net/http/http_status_code.h"
@@ -66,8 +65,7 @@
   auto resource_request = std::make_unique<network::ResourceRequest>();
   resource_request->url = GetChromebookServiceEndpoint();
   resource_request->load_flags = net::LOAD_DISABLE_CACHE;
-  resource_request->credentials_mode =
-      google_apis::GetOmitCredentialsModeForGaiaRequests();
+  resource_request->credentials_mode = network::mojom::CredentialsMode::kOmit;
   resource_request->method = "POST";
   return resource_request;
 }
diff --git a/chrome/browser/ash/login/oobe_quick_start/target_device_bootstrap_controller.cc b/chrome/browser/ash/login/oobe_quick_start/target_device_bootstrap_controller.cc
index a1a80def6..79b11c8 100644
--- a/chrome/browser/ash/login/oobe_quick_start/target_device_bootstrap_controller.cc
+++ b/chrome/browser/ash/login/oobe_quick_start/target_device_bootstrap_controller.cc
@@ -5,6 +5,7 @@
 #include "chrome/browser/ash/login/oobe_quick_start/target_device_bootstrap_controller.h"
 
 #include "base/check_op.h"
+#include "base/command_line.h"
 #include "base/containers/contains.h"
 #include "base/functional/bind.h"
 #include "chrome/browser/ash/login/oobe_quick_start/connectivity/authenticated_connection.h"
@@ -19,6 +20,15 @@
 
 namespace {
 
+// Passing "--quick-start-phone-instance-id" on the command line will implement
+// the Unified Setup UI enhancements with the ID provided in the switch. This is
+// for testing only and in the future this ID will be received during the Gaia
+// credentials exchange.
+// TODO(b/234655072): Delete this and get ID from the |bootstrap_controller_|
+// Gaia credentials exchange instead.
+constexpr char kQuickStartPhoneInstanceIDSwitch[] =
+    "quick-start-phone-instance-id";
+
 TargetDeviceBootstrapController::QRCodePixelData GenerateQRCode(
     std::vector<uint8_t> blob) {
   QRCodeGenerator qr_generator;
@@ -58,6 +68,17 @@
   connection_broker_->GetFeatureSupportStatusAsync(std::move(callback));
 }
 
+std::string TargetDeviceBootstrapController::GetPhoneInstanceId() {
+  // TODO(b/234655072): Delete kQuickStartPhoneInstanceIDSwitch and get the ID
+  // from the Gaia credentials exchange instead.
+  base::CommandLine* command_line = base::CommandLine::ForCurrentProcess();
+  if (command_line->HasSwitch(kQuickStartPhoneInstanceIDSwitch)) {
+    return command_line->GetSwitchValueASCII(kQuickStartPhoneInstanceIDSwitch);
+  }
+
+  return "";
+}
+
 base::WeakPtr<TargetDeviceBootstrapController>
 TargetDeviceBootstrapController::GetAsWeakPtrForClient() {
   // Only one client at a time should have a pointer.
diff --git a/chrome/browser/ash/login/oobe_quick_start/target_device_bootstrap_controller.h b/chrome/browser/ash/login/oobe_quick_start/target_device_bootstrap_controller.h
index 7e7903a..62dda382 100644
--- a/chrome/browser/ash/login/oobe_quick_start/target_device_bootstrap_controller.h
+++ b/chrome/browser/ash/login/oobe_quick_start/target_device_bootstrap_controller.h
@@ -6,6 +6,7 @@
 #define CHROME_BROWSER_ASH_LOGIN_OOBE_QUICK_START_TARGET_DEVICE_BOOTSTRAP_CONTROLLER_H_
 
 #include <memory>
+#include <string>
 
 #include "base/memory/weak_ptr.h"
 #include "base/observer_list.h"
@@ -36,6 +37,7 @@
     ADVERTISING,
     QR_CODE_VERIFICATION,
     CONNECTED,
+    GAIA_CREDENTIALS,
   };
 
   enum class ErrorCode {
@@ -69,6 +71,10 @@
   void GetFeatureSupportStatusAsync(
       TargetDeviceConnectionBroker::FeatureSupportStatusCallback callback);
 
+  // Returns the CryptAuth Instance ID of the connected remote device. Returns
+  // an empty string if no ID is available.
+  std::string GetPhoneInstanceId();
+
   // This function would crash (if DCHECKs are on) in case there are existing
   // valid weakptrs.
   base::WeakPtr<TargetDeviceBootstrapController> GetAsWeakPtrForClient();
diff --git a/chrome/browser/ash/login/oobe_quick_start/target_device_bootstrap_controller_unittest.cc b/chrome/browser/ash/login/oobe_quick_start/target_device_bootstrap_controller_unittest.cc
index 03e10cd..bde7f7a 100644
--- a/chrome/browser/ash/login/oobe_quick_start/target_device_bootstrap_controller_unittest.cc
+++ b/chrome/browser/ash/login/oobe_quick_start/target_device_bootstrap_controller_unittest.cc
@@ -3,8 +3,11 @@
 // found in the LICENSE file.
 
 #include "chrome/browser/ash/login/oobe_quick_start/target_device_bootstrap_controller.h"
-#include <memory>
 
+#include <memory>
+#include <string>
+
+#include "base/command_line.h"
 #include "base/test/bind.h"
 #include "chrome/browser/ash/login/oobe_quick_start/connectivity/fake_target_device_connection_broker.h"
 #include "chrome/browser/nearby_sharing/fake_nearby_connections_manager.h"
@@ -186,4 +189,17 @@
             ErrorCode::CONNECTION_CLOSED);
 }
 
+TEST_F(TargetDeviceBootstrapControllerTest, GetPhoneInstanceId) {
+  // Ensure GetPhoneInstanceId() returns an empty string when no command line
+  // switch is set.
+  ASSERT_TRUE(bootstrap_controller_->GetPhoneInstanceId().empty());
+
+  std::string kExpectedPhoneInstanceID = "someArbitraryInstanceID";
+  base::CommandLine::ForCurrentProcess()->InitFromArgv(
+      {"", "--quick-start-phone-instance-id=" + kExpectedPhoneInstanceID});
+
+  EXPECT_EQ(bootstrap_controller_->GetPhoneInstanceId(),
+            kExpectedPhoneInstanceID);
+}
+
 }  // namespace ash::quick_start
diff --git a/chrome/browser/ash/login/saml/password_sync_token_fetcher.cc b/chrome/browser/ash/login/saml/password_sync_token_fetcher.cc
index 27c9874..70fd8ca 100644
--- a/chrome/browser/ash/login/saml/password_sync_token_fetcher.cc
+++ b/chrome/browser/ash/login/saml/password_sync_token_fetcher.cc
@@ -24,7 +24,6 @@
 #include "components/user_manager/known_user.h"
 #include "content/public/browser/browser_context.h"
 #include "content/public/common/url_constants.h"
-#include "google_apis/credentials_mode.h"
 #include "google_apis/gaia/gaia_auth_fetcher.h"
 #include "google_apis/gaia/gaia_constants.h"
 #include "google_apis/gaia/google_service_auth_error.h"
@@ -242,8 +241,7 @@
   }
   resource_request->load_flags =
       net::LOAD_DISABLE_CACHE | net::LOAD_BYPASS_CACHE;
-  resource_request->credentials_mode =
-      google_apis::GetOmitCredentialsModeForGaiaRequests();
+  resource_request->credentials_mode = network::mojom::CredentialsMode::kOmit;
   if (request_type_ == RequestType::kCreateToken) {
     resource_request->method = net::HttpRequestHeaders::kPostMethod;
   } else {
diff --git a/chrome/browser/ash/login/screens/quick_start_screen.cc b/chrome/browser/ash/login/screens/quick_start_screen.cc
index 889ea85..a02fc1f 100644
--- a/chrome/browser/ash/login/screens/quick_start_screen.cc
+++ b/chrome/browser/ash/login/screens/quick_start_screen.cc
@@ -10,9 +10,11 @@
 #include "base/notreached.h"
 #include "base/strings/utf_string_conversions.h"
 #include "base/time/time.h"
+#include "chrome/browser/ash/login/oobe_quick_start/logging/logging.h"
 #include "chrome/browser/ash/login/oobe_quick_start/target_device_bootstrap_controller.h"
 #include "chrome/browser/ash/login/oobe_quick_start/verification_shapes.h"
 #include "chrome/browser/ash/login/ui/login_display_host.h"
+#include "chrome/browser/ash/login/wizard_context.h"
 #include "chrome/browser/ui/webui/ash/login/quick_start_screen_handler.h"
 #include "third_party/abseil-cpp/absl/types/variant.h"
 
@@ -79,6 +81,23 @@
       view_->SetQRCode(std::move(qr_code_list));
       return;
     }
+    case Step::GAIA_CREDENTIALS: {
+      std::string phone_instance_id =
+          bootstrap_controller_->GetPhoneInstanceId();
+
+      if (phone_instance_id.empty()) {
+        return;
+      }
+
+      quick_start::QS_LOG(INFO)
+          << "Adding Phone Instance ID to Wizard Object for Unified "
+             "Setup UI enhancements. quick_start_phone_instance_id: "
+          << phone_instance_id;
+      LoginDisplayHost::default_host()
+          ->GetWizardContext()
+          ->quick_start_phone_instance_id = phone_instance_id;
+      return;
+    }
     case Step::NONE:
     case Step::ERROR:
     case Step::ADVERTISING:
diff --git a/chrome/browser/ash/login/users/avatar/user_image_loader.cc b/chrome/browser/ash/login/users/avatar/user_image_loader.cc
index 4bb3eed1..5c450b1b 100644
--- a/chrome/browser/ash/login/users/avatar/user_image_loader.cc
+++ b/chrome/browser/ash/login/users/avatar/user_image_loader.cc
@@ -21,7 +21,6 @@
 #include "chrome/browser/browser_process.h"
 #include "chrome/browser/ui/ash/image_downloader_impl.h"
 #include "components/user_manager/user_image/user_image.h"
-#include "google_apis/credentials_mode.h"
 #include "ipc/ipc_channel.h"
 #include "net/traffic_annotation/network_traffic_annotation.h"
 #include "services/data_decoder/public/cpp/data_decoder.h"
@@ -439,8 +438,7 @@
 
   auto request = std::make_unique<network::ResourceRequest>();
   request->url = default_image_url;
-  request->credentials_mode =
-      google_apis::GetOmitCredentialsModeForGaiaRequests();
+  request->credentials_mode = network::mojom::CredentialsMode::kOmit;
 
   auto loader = network::SimpleURLLoader::Create(std::move(request),
                                                  kNetworkTrafficAnnotationTag);
diff --git a/chrome/browser/autofill/autofill_across_iframes_browsertest.cc b/chrome/browser/autofill/autofill_across_iframes_browsertest.cc
index 4ff1bcab..b6feccb 100644
--- a/chrome/browser/autofill/autofill_across_iframes_browsertest.cc
+++ b/chrome/browser/autofill/autofill_across_iframes_browsertest.cc
@@ -109,10 +109,10 @@
  private:
   TestAutofillManagerWaiter did_autofill_{
       *this,
-      {&AutofillManager::Observer::OnAfterDidFillAutofillFormData}};
+      {AutofillManagerEvent::kDidFillAutofillFormData}};
   TestAutofillManagerWaiter form_submitted_{
       *this,
-      {&AutofillManager::Observer::OnAfterFormSubmitted}};
+      {AutofillManagerEvent::kFormSubmitted}};
   absl::optional<FormData> submitted_form_;
 };
 
diff --git a/chrome/browser/autofill/autofill_browsertest.cc b/chrome/browser/autofill/autofill_browsertest.cc
index 4ff2395..22978eb4 100644
--- a/chrome/browser/autofill/autofill_browsertest.cc
+++ b/chrome/browser/autofill/autofill_browsertest.cc
@@ -130,7 +130,7 @@
    private:
     TestAutofillManagerWaiter forms_seen_waiter_{
         *this,
-        {&AutofillManager::Observer::OnAfterFormsSeen}};
+        {AutofillManagerEvent::kFormsSeen}};
   };
 
   AutofillTest() = default;
diff --git a/chrome/browser/autofill/autofill_uitest_util.cc b/chrome/browser/autofill/autofill_uitest_util.cc
index cb39d428..ddb6079 100644
--- a/chrome/browser/autofill/autofill_uitest_util.cc
+++ b/chrome/browser/autofill/autofill_uitest_util.cc
@@ -140,8 +140,8 @@
       absl::get<AutofillDriver*>(autofill_external_delegate->GetDriver()));
   AutofillManager* manager = driver->autofill_manager();
   mojom::AutofillDriver* mojo_driver = driver;
-  TestAutofillManagerWaiter waiter(
-      *manager, {&AutofillManager::Observer::OnAfterAskForValuesToFill});
+  TestAutofillManagerWaiter waiter(*manager,
+                                   {AutofillManagerEvent::kAskForValuesToFill});
   mojo_driver->AskForValuesToFill(form, form.fields.front(), bounds,
                                   AutoselectFirstSuggestion(false),
                                   FormElementWasClicked(false));
diff --git a/chrome/browser/autofill/captured_sites_test_utils.cc b/chrome/browser/autofill/captured_sites_test_utils.cc
index dfb791e..fb1ecca9 100644
--- a/chrome/browser/autofill/captured_sites_test_utils.cc
+++ b/chrome/browser/autofill/captured_sites_test_utils.cc
@@ -124,6 +124,7 @@
   $ echo where   >%1$s  # prints the current position
   $ echo show -1 >%1$s  # prints last 1 actions
   $ echo show 1  >%1$s  # prints next 1 actions
+  $ echo failure >%1$s  # unpauses execution until failure
   $ echo help    >%1$s  # prints this text
 )";
   LOG(INFO) << base::StringPrintf(msg,
@@ -168,6 +169,7 @@
   kSkipAction,
   kShowAction,
   kWhereAmI,
+  kRunUntilFailure
 };
 
 struct ExecutionCommand {
@@ -207,6 +209,11 @@
         commands.push_back({ExecutionCommandType::kShowAction, GetParamOr(1)});
       } else if (base::StartsWith(command, "where")) {
         commands.push_back({ExecutionCommandType::kWhereAmI});
+      } else if (base::StartsWith(command, "failure")) {
+        commands.push_back({ExecutionCommandType::kRunUntilFailure});
+        // also add an absolute max limit (like a" run" command).
+        commands.push_back({ExecutionCommandType::kAbsoluteLimit,
+                            std::numeric_limits<int>::max()});
       } else if (base::StartsWith(command, "help")) {
         PrintDebugInstructions(command_file_path);
       }
@@ -222,6 +229,8 @@
   int limit = std::numeric_limits<int>::max();
   // The number of actions to be executed.
   int length = 0;
+  // Whether to stop at a step if a failure is detected.
+  bool pause_on_failure = false;
 };
 
 // Blockingly reads the commands from |command_file_path| and executes them.
@@ -266,6 +275,12 @@
           LOG(INFO) << "Next action is at position " << execution_state.index
                     << ", limit (excl) is at " << execution_state.limit
                     << ", last (excl) is at " << execution_state.length;
+          break;
+        }
+        case ExecutionCommandType::kRunUntilFailure: {
+          LOG(INFO) << "Will stop when a failure is found.";
+          execution_state.pause_on_failure = true;
+          break;
         }
       }
     }
@@ -1099,6 +1114,14 @@
   }
 
   while (execution_state.index < execution_state.length) {
+    if (execution_state.pause_on_failure &&
+        (testing::Test::HasNonfatalFailure() ||
+         testing::Test::HasFatalFailure() || validation_failures_.size() > 0)) {
+      // If set to pause on a failure, move limit to current, but then reset
+      // `pause_on_failure` so it can continue if the user requests.
+      execution_state.limit = execution_state.index;
+      execution_state.pause_on_failure = false;
+    }
     if (command_file_path.has_value()) {
       while (execution_state.limit <= execution_state.index) {
         bool thread_finished = false;
diff --git a/chrome/browser/autofill/form_structure_browsertest.cc b/chrome/browser/autofill/form_structure_browsertest.cc
index 9842c012..58bfc69 100644
--- a/chrome/browser/autofill/form_structure_browsertest.cc
+++ b/chrome/browser/autofill/form_structure_browsertest.cc
@@ -180,9 +180,8 @@
     TestAutofillManagerWaiter& waiter() { return waiter_; }
 
    private:
-    TestAutofillManagerWaiter waiter_{
-        *this,
-        {&AutofillManager::Observer::OnAfterFormsSeen}};
+    TestAutofillManagerWaiter waiter_{*this,
+                                      {AutofillManagerEvent::kFormsSeen}};
   };
 
   std::unique_ptr<HttpResponse> HandleRequest(const HttpRequest& request);
diff --git a/chrome/browser/browser_features.cc b/chrome/browser/browser_features.cc
index c62d8bc..f5df391 100644
--- a/chrome/browser/browser_features.cc
+++ b/chrome/browser/browser_features.cc
@@ -42,6 +42,13 @@
              "DevToolsTabTarget",
              base::FEATURE_DISABLED_BY_DEFAULT);
 
+// Normally the toolbar texture is discarded when the toolbar is no longer
+// visible. This feature keeps the texture around so it does not need to get
+// re-uploaded when the toolbar becomes visible again.
+BASE_FEATURE(kKeepToolbarTexture,
+             "KeepToolbarTexture",
+             base::FEATURE_DISABLED_BY_DEFAULT);
+
 // Nukes profile directory before creating a new profile using
 // ProfileManager::CreateMultiProfileAsync().
 BASE_FEATURE(kNukeProfileBeforeCreateMultiAsync,
diff --git a/chrome/browser/browser_features.h b/chrome/browser/browser_features.h
index c148a76..131e64d2 100644
--- a/chrome/browser/browser_features.h
+++ b/chrome/browser/browser_features.h
@@ -25,6 +25,8 @@
 
 BASE_DECLARE_FEATURE(kDevToolsTabTarget);
 
+BASE_DECLARE_FEATURE(kKeepToolbarTexture);
+
 BASE_DECLARE_FEATURE(kNukeProfileBeforeCreateMultiAsync);
 
 BASE_DECLARE_FEATURE(kPromoBrowserCommands);
diff --git a/chrome/browser/devtools/device/port_forwarding_controller.cc b/chrome/browser/devtools/device/port_forwarding_controller.cc
index cd640dc0..f2380b8f 100644
--- a/chrome/browser/devtools/device/port_forwarding_controller.cc
+++ b/chrome/browser/devtools/device/port_forwarding_controller.cc
@@ -67,20 +67,22 @@
 const char kConnectionIdParam[] = "connectionId";
 
 static bool ParseNotification(const std::string& json,
-                              std::string* method,
-                              absl::optional<base::Value>* params) {
+                              std::string& method,
+                              absl::optional<base::Value::Dict>& params) {
   absl::optional<base::Value> value = base::JSONReader::Read(json);
   if (!value || !value->is_dict())
     return false;
 
-  const std::string* method_value = value->FindStringKey(kMethodParam);
+  base::Value::Dict& dict = value->GetDict();
+  std::string* method_value = dict.FindString(kMethodParam);
   if (!method_value)
     return false;
-  *method = *method_value;
+  method = std::move(*method_value);
 
-  auto extracted_param = value->ExtractKey(kParamsParam);
-  if (extracted_param && extracted_param->is_dict())
-    *params = std::move(extracted_param);
+  base::Value::Dict* param_dict = dict.FindDict(kParamsParam);
+  if (param_dict) {
+    params = std::move(*param_dict);
+  }
   return true;
 }
 
@@ -90,12 +92,13 @@
   absl::optional<base::Value> value = base::JSONReader::Read(json);
   if (!value || !value->is_dict())
     return false;
-  absl::optional<int> command_id_opt = value->FindIntKey(kIdParam);
+  const base::Value::Dict& dict = value->GetDict();
+  absl::optional<int> command_id_opt = dict.FindInt(kIdParam);
   if (!command_id_opt)
     return false;
   *command_id = *command_id_opt;
 
-  absl::optional<int> error_value = value->FindIntPath(kErrorCodePath);
+  absl::optional<int> error_value = dict.FindIntByDottedPath(kErrorCodePath);
   if (error_value)
     *error_code = *error_value;
 
@@ -105,10 +108,10 @@
 static std::string SerializeCommand(int command_id,
                                     const std::string& method,
                                     base::Value params) {
-  base::Value command(base::Value::Type::DICT);
-  command.SetIntKey(kIdParam, command_id);
-  command.SetStringKey(kMethodParam, method);
-  command.SetKey(kParamsParam, std::move(params));
+  base::Value::Dict command;
+  command.Set(kIdParam, command_id);
+  command.Set(kMethodParam, method);
+  command.Set(kParamsParam, std::move(params));
 
   std::string json_command;
   base::JSONWriter::Write(command, &json_command);
@@ -493,9 +496,9 @@
 void PortForwardingController::Connection::SendCommand(
     const std::string& method, int port) {
   DCHECK_CURRENTLY_ON(BrowserThread::UI);
-  base::Value params(base::Value::Type::DICT);
+  base::Value::Dict params;
   DCHECK(method == kBindMethod || kUnbindMethod == method);
-  params.SetIntKey(kPortParam, port);
+  params.Set(kPortParam, port);
   int id = ++command_id_;
 
   if (method == kBindMethod) {
@@ -513,7 +516,8 @@
                                             base::Unretained(this), port);
   }
 
-  web_socket_->SendFrame(SerializeCommand(id, method, std::move(params)));
+  web_socket_->SendFrame(
+      SerializeCommand(id, method, base::Value(std::move(params))));
 }
 
 bool PortForwardingController::Connection::ProcessResponse(
@@ -571,17 +575,18 @@
     return;
 
   std::string method;
-  absl::optional<base::Value> params;
-  if (!ParseNotification(message, &method, &params))
+  absl::optional<base::Value::Dict> params;
+  if (!ParseNotification(message, method, params)) {
     return;
+  }
 
   if (method != kAcceptedEvent || !params)
     return;
 
-  absl::optional<int> port = params->FindIntKey(kPortParam);
+  absl::optional<int> port = params->FindInt(kPortParam);
   if (!port)
     return;
-  const std::string* connection_id = params->FindStringKey(kConnectionIdParam);
+  const std::string* connection_id = params->FindString(kConnectionIdParam);
   if (!connection_id)
     return;
 
diff --git a/chrome/browser/enterprise/connectors/device_trust/device_trust_browsertest.cc b/chrome/browser/enterprise/connectors/device_trust/device_trust_browsertest.cc
index 12edb9a..ca51ba4 100644
--- a/chrome/browser/enterprise/connectors/device_trust/device_trust_browsertest.cc
+++ b/chrome/browser/enterprise/connectors/device_trust/device_trust_browsertest.cc
@@ -450,6 +450,9 @@
 
 #if BUILDFLAG(IS_WIN)
     device_trust_test_environment_win_.emplace();
+    device_trust_test_environment_win_->SetExpectedDMToken(kFakeBrowserDMToken);
+    device_trust_test_environment_win_->SetExpectedClientID(
+        kFakeBrowserClientId);
 #else  // BUILDFLAG(IS_WIN)
     scoped_persistence_delegate_factory_.emplace();
     scoped_rotation_command_factory_.emplace();
diff --git a/chrome/browser/enterprise/connectors/device_trust/test/device_trust_test_environment.cc b/chrome/browser/enterprise/connectors/device_trust/test/device_trust_test_environment.cc
index 3413008..117e4ea 100644
--- a/chrome/browser/enterprise/connectors/device_trust/test/device_trust_test_environment.cc
+++ b/chrome/browser/enterprise/connectors/device_trust/test/device_trust_test_environment.cc
@@ -31,4 +31,14 @@
   upload_response_code_ = upload_response_code;
 }
 
+void DeviceTrustTestEnvironment::SetExpectedDMToken(
+    std::string expected_dm_token) {
+  expected_dm_token_ = expected_dm_token;
+}
+
+void DeviceTrustTestEnvironment::SetExpectedClientID(
+    std::string expected_client_id) {
+  expected_client_id_ = expected_client_id;
+}
+
 }  // namespace enterprise_connectors
diff --git a/chrome/browser/enterprise/connectors/device_trust/test/device_trust_test_environment.h b/chrome/browser/enterprise/connectors/device_trust/test/device_trust_test_environment.h
index dec6d80..1e3323af 100644
--- a/chrome/browser/enterprise/connectors/device_trust/test/device_trust_test_environment.h
+++ b/chrome/browser/enterprise/connectors/device_trust/test/device_trust_test_environment.h
@@ -36,6 +36,12 @@
   // KeyNetworkDelegate.
   void SetUploadResult(HttpResponseCode upload_response_code);
 
+  // Set up an expected DM token, which we will check if it's identical to
+  // the DM token we use when uploading to DM server
+  void SetExpectedDMToken(std::string expected_dm_token);
+
+  void SetExpectedClientID(std::string expected_client_id);
+
   // Check if device trust key exists on the device.
   bool KeyExists();
 
@@ -49,6 +55,12 @@
   // KeyNetworkDelegate.
   HttpResponseCode upload_response_code_;
 
+  // Expected dm token to be used when upload to dm server
+  std::string expected_dm_token_;
+
+  // Expected client ID to be included in dm server URL when upload to dm server
+  std::string expected_client_id_;
+
   // Instance of platform-dependent KeyPersistenceDelegate to interact with
   // Device Trust keys.
   std::unique_ptr<KeyPersistenceDelegate> key_persistence_delegate_;
diff --git a/chrome/browser/enterprise/connectors/device_trust/test/device_trust_test_environment_win.cc b/chrome/browser/enterprise/connectors/device_trust/test/device_trust_test_environment_win.cc
index 1436174..1f8d3b4 100644
--- a/chrome/browser/enterprise/connectors/device_trust/test/device_trust_test_environment_win.cc
+++ b/chrome/browser/enterprise/connectors/device_trust/test/device_trust_test_environment_win.cc
@@ -42,6 +42,8 @@
 
 HRESULT MockRunGoogleUpdateElevatedCommandFn(
     HttpResponseCode upload_response_code,
+    std::string expected_dm_token,
+    std::string expected_client_id,
     const wchar_t* command,
     const std::vector<std::string>& args,
     absl::optional<DWORD>* return_code) {
@@ -54,9 +56,15 @@
       std::make_unique<StrictMock<MockKeyNetworkDelegate>>();
   EXPECT_CALL(*mock_network_delegate, SendPublicKeyToDmServer(_, _, _, _))
       .WillOnce(Invoke(
-          [upload_response_code](const GURL& url, const std::string& dm_token,
-                                 const std::string& body,
-                                 base::OnceCallback<void(int)> callback) {
+          [upload_response_code, expected_dm_token, expected_client_id, args](
+              const GURL& url, const std::string& dm_token,
+              const std::string& body, base::OnceCallback<void(int)> callback) {
+            // Check if the DM Server URL contains the correct Client ID
+            CHECK(url.spec().find(expected_client_id) != std::string::npos);
+            // Check if the correct DM Token is being uploaded
+            CHECK_EQ(dm_token, expected_dm_token);
+            // TODO(b/269746642): add a check for the 'body' parameter above
+
             std::move(callback).Run(upload_response_code);
           }));
   const auto result = enterprise_connectors::RotateDeviceTrustKey(
@@ -105,7 +113,8 @@
   }
   return std::make_unique<WinKeyRotationCommand>(
       base::BindRepeating(&MockRunGoogleUpdateElevatedCommandFn,
-                          upload_response_code_),
+                          upload_response_code_, expected_dm_token_,
+                          expected_client_id_),
       worker_thread_.task_runner());
 }
 
diff --git a/chrome/browser/extensions/extension_context_menu_model_unittest.cc b/chrome/browser/extensions/extension_context_menu_model_unittest.cc
index 6234697..7307a7f 100644
--- a/chrome/browser/extensions/extension_context_menu_model_unittest.cc
+++ b/chrome/browser/extensions/extension_context_menu_model_unittest.cc
@@ -63,7 +63,6 @@
 #include "net/disk_cache/blockfile/disk_format_base.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "ui/base/l10n/l10n_util.h"
-#include "ui/display/test/scoped_screen_override.h"
 #include "ui/display/test/test_screen.h"
 #include "ui/gfx/image/image.h"
 #include "url/origin.h"
@@ -74,7 +73,6 @@
 
 namespace extensions {
 
-using display::test::ScopedScreenOverride;
 using mojom::ManifestLocation;
 using ContextMenuSource = ExtensionContextMenuModel::ContextMenuSource;
 using MenuEntries = ExtensionContextMenuModel::MenuEntries;
@@ -300,7 +298,6 @@
   std::unique_ptr<TestBrowserWindow> test_window_;
   std::unique_ptr<Browser> browser_;
   display::test::TestScreen test_screen_;
-  std::unique_ptr<ScopedScreenOverride> scoped_screen_override_;
 };
 
 ExtensionContextMenuModelTest::ExtensionContextMenuModelTest() {}
@@ -445,8 +442,7 @@
 
 void ExtensionContextMenuModelTest::SetUp() {
   ExtensionServiceTestBase::SetUp();
-  scoped_screen_override_ =
-      std::make_unique<ScopedScreenOverride>(&test_screen_);
+  display::Screen::SetScreenInstance(&test_screen_);
 }
 
 void ExtensionContextMenuModelTest::TearDown() {
@@ -456,6 +452,7 @@
       browser_->tab_strip_model()->DetachAndDeleteWebContentsAt(0);
   }
 
+  display::Screen::SetScreenInstance(nullptr);
   ExtensionServiceTestBase::TearDown();
 }
 
diff --git a/chrome/browser/extensions/extension_icon_manager_unittest.cc b/chrome/browser/extensions/extension_icon_manager_unittest.cc
index f38d85e..563a7bc 100644
--- a/chrome/browser/extensions/extension_icon_manager_unittest.cc
+++ b/chrome/browser/extensions/extension_icon_manager_unittest.cc
@@ -24,7 +24,6 @@
 #include "ui/base/layout.h"
 #include "ui/display/display_list.h"
 #include "ui/display/display_switches.h"
-#include "ui/display/test/scoped_screen_override.h"
 #include "ui/display/test/test_screen.h"
 #include "ui/gfx/favicon_size.h"
 #include "ui/gfx/geometry/skia_conversions.h"
@@ -45,8 +44,7 @@
         switches::kForceDeviceScaleFactor, base::StringPrintf("%3.2f", scale));
     // This has to be inited after fiddling with the command line.
     test_screen_ = std::make_unique<display::test::TestScreen>();
-    screen_override_ = std::make_unique<display::test::ScopedScreenOverride>(
-        test_screen_.get());
+    display::Screen::SetScreenInstance(test_screen_.get());
   }
 
   ScopedSetDeviceScaleFactor(const ScopedSetDeviceScaleFactor&) = delete;
@@ -54,12 +52,12 @@
       delete;
 
   ~ScopedSetDeviceScaleFactor() {
+    display::Screen::SetScreenInstance(nullptr);
     display::Display::ResetForceDeviceScaleFactorForTesting();
   }
 
  private:
   std::unique_ptr<display::test::TestScreen> test_screen_;
-  std::unique_ptr<display::test::ScopedScreenOverride> screen_override_;
   base::test::ScopedCommandLine command_line_;
 };
 
diff --git a/chrome/browser/fast_checkout/fast_checkout_client_impl.cc b/chrome/browser/fast_checkout/fast_checkout_client_impl.cc
index dba95aea..186a187 100644
--- a/chrome/browser/fast_checkout/fast_checkout_client_impl.cc
+++ b/chrome/browser/fast_checkout/fast_checkout_client_impl.cc
@@ -293,7 +293,8 @@
          selected_credit_card_guid_;
 }
 
-void FastCheckoutClientImpl::OnAfterLoadedServerPredictions() {
+void FastCheckoutClientImpl::OnAfterLoadedServerPredictions(
+    autofill::AutofillManager& manager) {
   TryToFillForms();
 }
 
@@ -442,7 +443,9 @@
   }
 }
 
-void FastCheckoutClientImpl::OnAfterDidFillAutofillFormData() {
+void FastCheckoutClientImpl::OnAfterDidFillAutofillFormData(
+    autofill::AutofillManager& manager,
+    autofill::FormGlobalId form_id) {
   if (!IsFilling()) {
     return;
   }
@@ -508,7 +511,8 @@
   }
 }
 
-void FastCheckoutClientImpl::OnAutofillManagerDestroyed() {
+void FastCheckoutClientImpl::OnAutofillManagerDestroyed(
+    autofill::AutofillManager& manager) {
   if (IsRunning()) {
     if (GetWebContents().IsBeingDestroyed()) {
       OnRunComplete(FastCheckoutRunOutcome::kTabClosed);
@@ -520,7 +524,8 @@
   Stop(/*allow_further_runs=*/true);
 }
 
-void FastCheckoutClientImpl::OnAutofillManagerReset() {
+void FastCheckoutClientImpl::OnAutofillManagerReset(
+    autofill::AutofillManager& manager) {
   if (IsShowing()) {
     OnRunComplete(FastCheckoutRunOutcome::kNavigationWhileBottomsheetWasShown);
   }
diff --git a/chrome/browser/fast_checkout/fast_checkout_client_impl.h b/chrome/browser/fast_checkout/fast_checkout_client_impl.h
index 6405798..8fee89e1 100644
--- a/chrome/browser/fast_checkout/fast_checkout_client_impl.h
+++ b/chrome/browser/fast_checkout/fast_checkout_client_impl.h
@@ -55,14 +55,16 @@
       std::unique_ptr<autofill::CreditCard> selected_credit_card) override;
   void OnDismiss() override;
 
-  // AutofillManager::Observer:
-  void OnAfterLoadedServerPredictions() override;
-  void OnAfterDidFillAutofillFormData() override;
+  // autofill::AutofillManager::Observer:
+  void OnAfterLoadedServerPredictions(
+      autofill::AutofillManager& manager) override;
+  void OnAfterDidFillAutofillFormData(autofill::AutofillManager& manager,
+                                      autofill::FormGlobalId form_id) override;
   // Is owned by a `ContentAutofillDriver` instance and its lifecycle thus is
   // dependent on the one of `RenderFrameHost`.
-  void OnAutofillManagerDestroyed() override;
+  void OnAutofillManagerDestroyed(autofill::AutofillManager& manager) override;
   // Is called on navigation and resets its internal state.
-  void OnAutofillManagerReset() override;
+  void OnAutofillManagerReset(autofill::AutofillManager& manager) override;
 
   // autofill::payments::FullCardRequest::ResultDelegate:
   void OnFullCardRequestSucceeded(
diff --git a/chrome/browser/fast_checkout/fast_checkout_client_impl_unittest.cc b/chrome/browser/fast_checkout/fast_checkout_client_impl_unittest.cc
index 5facecf..603bf77c 100644
--- a/chrome/browser/fast_checkout/fast_checkout_client_impl_unittest.cc
+++ b/chrome/browser/fast_checkout/fast_checkout_client_impl_unittest.cc
@@ -660,7 +660,7 @@
   EXPECT_CALL(*autofill_manager(),
               SetFastCheckoutRunId(autofill::FieldTypeGroup::kAddressHome,
                                    fast_checkout_client()->run_id_));
-  fast_checkout_client()->OnAfterLoadedServerPredictions();
+  fast_checkout_client()->OnAfterLoadedServerPredictions(*autofill_manager());
   EXPECT_THAT(
       fast_checkout_client()->form_filling_states_,
       UnorderedElementsAre(
@@ -708,7 +708,8 @@
                             autofill::FormType::kCreditCardForm),
                        FastCheckoutClientImpl::FillingState::kFilling)));
 
-  fast_checkout_client()->OnAfterDidFillAutofillFormData();
+  fast_checkout_client()->OnAfterDidFillAutofillFormData(
+      *autofill_manager(), credit_card_form->global_id());
 
   EXPECT_FALSE(fast_checkout_client()->IsRunning());
   ExpectRunOutcomeUkm(FastCheckoutRunOutcome::kSuccess);
@@ -720,7 +721,7 @@
       autofill_manager()->GetWeakPtr()));
 
   EXPECT_TRUE(fast_checkout_client()->IsRunning());
-  fast_checkout_client()->OnAutofillManagerReset();
+  fast_checkout_client()->OnAutofillManagerReset(*autofill_manager());
   EXPECT_FALSE(fast_checkout_client()->IsRunning());
   ExpectRunOutcomeUkm(
       FastCheckoutRunOutcome::kNavigationWhileBottomsheetWasShown);
@@ -732,7 +733,7 @@
       autofill_manager()->GetWeakPtr()));
 
   EXPECT_TRUE(fast_checkout_client()->IsRunning());
-  fast_checkout_client()->OnAutofillManagerDestroyed();
+  fast_checkout_client()->OnAutofillManagerDestroyed(*autofill_manager());
   EXPECT_FALSE(fast_checkout_client()->IsRunning());
   ExpectRunOutcomeUkm(FastCheckoutRunOutcome::kAutofillManagerDestroyed);
 }
@@ -835,7 +836,8 @@
       kAutofillProfileLabel + u" address form filled.";
 
   EXPECT_CALL(*accessibility_service(), Announce(announcement_text));
-  fast_checkout_client()->OnAfterDidFillAutofillFormData();
+  fast_checkout_client()->OnAfterDidFillAutofillFormData(
+      *autofill_manager(), address_form->global_id());
 }
 
 TEST_F(
@@ -850,7 +852,8 @@
   std::u16string announcement_text = u"Email filled.";
 
   EXPECT_CALL(*accessibility_service(), Announce(announcement_text));
-  fast_checkout_client()->OnAfterDidFillAutofillFormData();
+  fast_checkout_client()->OnAfterDidFillAutofillFormData(
+      *autofill_manager(), address_form->global_id());
 }
 
 TEST_F(
@@ -867,7 +870,8 @@
   std::u16string announcement_text = kCreditCardNickname + u" filled.";
 
   EXPECT_CALL(*accessibility_service(), Announce(announcement_text));
-  fast_checkout_client()->OnAfterDidFillAutofillFormData();
+  fast_checkout_client()->OnAfterDidFillAutofillFormData(
+      *autofill_manager(), credit_card_form->global_id());
 }
 
 TEST_F(FastCheckoutClientImplTest,
@@ -883,7 +887,7 @@
   EXPECT_TRUE(fast_checkout_client()->IsRunning());
   EXPECT_CALL(*autofill_manager(), FillProfileFormImpl).Times(0);
 
-  fast_checkout_client()->OnAfterLoadedServerPredictions();
+  fast_checkout_client()->OnAfterLoadedServerPredictions(*autofill_manager());
 
   EXPECT_FALSE(fast_checkout_client()->IsRunning());
 }
@@ -906,7 +910,7 @@
   // `BrowserAutofillManager`.
   EXPECT_CALL(*autofill_manager(), FillCreditCardFormImpl).Times(0);
 
-  fast_checkout_client()->OnAfterLoadedServerPredictions();
+  fast_checkout_client()->OnAfterLoadedServerPredictions(*autofill_manager());
 
   EXPECT_FALSE(fast_checkout_client()->IsRunning());
 }
diff --git a/chrome/browser/feedback/show_feedback_page_lacros.cc b/chrome/browser/feedback/show_feedback_page_lacros.cc
index 2583c66..129a1de 100644
--- a/chrome/browser/feedback/show_feedback_page_lacros.cc
+++ b/chrome/browser/feedback/show_feedback_page_lacros.cc
@@ -26,6 +26,8 @@
       return crosapi::mojom::LacrosFeedbackSource::kLacrosChromeLabs;
     case kFeedbackSourceQuickAnswers:
       return crosapi::mojom::LacrosFeedbackSource::kLacrosQuickAnswers;
+    case kFeedbackSourceWindowLayoutMenu:
+      return crosapi::mojom::LacrosFeedbackSource::kLacrosWindowLayoutMenu;
     default:
       LOG(ERROR) << "ShowFeedbackPage is called by unknown Lacros source: "
                  << static_cast<int>(source);
diff --git a/chrome/browser/feedback/show_feedback_page_lacros_browsertest.cc b/chrome/browser/feedback/show_feedback_page_lacros_browsertest.cc
index 60865b79..f2d59be3 100644
--- a/chrome/browser/feedback/show_feedback_page_lacros_browsertest.cc
+++ b/chrome/browser/feedback/show_feedback_page_lacros_browsertest.cc
@@ -71,3 +71,10 @@
   ShowFeedbackPageWithFeedbackSource(chrome::kFeedbackSourceSadTabPage);
   histogram_tester.ExpectTotalCount("Feedback.RequestSource", 1);
 }
+
+IN_PROC_BROWSER_TEST_F(ShowFeedbackPageBrowserTest,
+                       ShowFeedbackPageFromWindowLayoutMenu) {
+  base::HistogramTester histogram_tester;
+  ShowFeedbackPageWithFeedbackSource(chrome::kFeedbackSourceWindowLayoutMenu);
+  histogram_tester.ExpectTotalCount("Feedback.RequestSource", 1);
+}
diff --git a/chrome/browser/flag-metadata.json b/chrome/browser/flag-metadata.json
index ea8ada0..374d7c7 100644
--- a/chrome/browser/flag-metadata.json
+++ b/chrome/browser/flag-metadata.json
@@ -357,14 +357,6 @@
     "expiry_milestone": 96
   },
   {
-    "name": "assistant-consent-modal",
-    "owners": [
-      "jds@google.com",
-      "chrome-voice@google.com"
-    ],
-    "expiry_milestone": 110
-  },
-  {
     "name": "assistant-consent-simplified-text",
     "owners": [
       "jds@google.com",
@@ -7125,15 +7117,6 @@
     "expiry_milestone": 120
   },
   {
-    "name": "voice-button-in-top-toolbar",
-    "owners": [
-      "jds@google.com",
-      "basiaz@google.com",
-      "chrome-voice@google.com"
-    ],
-    "expiry_milestone": 103
-  },
-  {
     "name": "wait-threshold-seconds-for-capabilities-api",
     "owners": [ "jlebel", "chrome-signin-team" ],
     // Used for manual testing to extend the wait time for capabilities fetching on iOS devices.
diff --git a/chrome/browser/flag_descriptions.cc b/chrome/browser/flag_descriptions.cc
index f87ad5e..cfc29191 100644
--- a/chrome/browser/flag_descriptions.cc
+++ b/chrome/browser/flag_descriptions.cc
@@ -4153,10 +4153,6 @@
     "Adaptive button in top toolbar customization";
 const char kAdaptiveButtonInTopToolbarCustomizationDescription[] =
     "Enables UI for customizing the adaptive action button in the top toolbar";
-const char kVoiceButtonInTopToolbarName[] = "Voice button in top toolbar";
-const char kVoiceButtonInTopToolbarDescription[] =
-    "Enables showing the voice search button in the top toolbar. Enabling "
-    "Adaptive Button overrides this.";
 
 const char kUseToastManagerName[] = "Use Toast manager";
 const char kUseToastManagerDescription[] =
diff --git a/chrome/browser/flag_descriptions.h b/chrome/browser/flag_descriptions.h
index 2053c74..d5167dc 100644
--- a/chrome/browser/flag_descriptions.h
+++ b/chrome/browser/flag_descriptions.h
@@ -2363,8 +2363,6 @@
 extern const char kAdaptiveButtonInTopToolbarDescription[];
 extern const char kAdaptiveButtonInTopToolbarCustomizationName[];
 extern const char kAdaptiveButtonInTopToolbarCustomizationDescription[];
-extern const char kVoiceButtonInTopToolbarName[];
-extern const char kVoiceButtonInTopToolbarDescription[];
 
 extern const char kUseToastManagerName[];
 extern const char kUseToastManagerDescription[];
diff --git a/chrome/browser/k_anonymity_service/k_anonymity_service_storage.cc b/chrome/browser/k_anonymity_service/k_anonymity_service_storage.cc
index 8c40fe08..aac9930 100644
--- a/chrome/browser/k_anonymity_service/k_anonymity_service_storage.cc
+++ b/chrome/browser/k_anonymity_service/k_anonymity_service_storage.cc
@@ -71,13 +71,16 @@
                 table_manager_,
                 trust_token_key_commitment_table_.get(),
                 /*max_num_entries=*/absl::nullopt,
-                kFlushDelay)) {}
+                kFlushDelay)),
+        weak_factory_(this) {}
 
   ~KAnonymityServiceSqlStorage() override {
     DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-    // Enqueue a flush
-    ohttp_key_data_->FlushDataToDisk();
-    trust_token_key_commitment_data_->FlushDataToDisk();
+    if (status_ == kInitOk) {
+      // Enqueue a flush
+      ohttp_key_data_->FlushDataToDisk();
+      trust_token_key_commitment_data_->FlushDataToDisk();
+    }
 
     // Shutdown `table_manager_`, delete database on db
     // sequence, then delete the KeyValueTable/KeyValueData on main sequence.
@@ -103,11 +106,17 @@
 
   void WaitUntilReady(base::OnceCallback<void(InitStatus)> on_ready) override {
     DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-    if (ready_) {
+    if (status_ != kInitNotReady) {
       std::move(on_ready).Run(InitStatus::kInitOk);
       return;
     }
-    ready_ = true;
+
+    waiting_tasks_.emplace_back(std::move(on_ready));
+    if (waiting_tasks_.size() > 1) {
+      // We're in the process of initializing.
+      return;
+    }
+
     // This is safe because tasks are serialized on the db_task_runner sequence
     // and the `table_manager_`, `ohttp_key_data_`, and
     // `trust_token_key_commitment_data_` are only freed after a response from a
@@ -122,7 +131,15 @@
             base::Unretained(table_manager_.get()),
             base::Unretained(ohttp_key_data_.get()),
             base::Unretained(trust_token_key_commitment_data_.get())),
-        std::move(on_ready));
+        base::BindOnce(&KAnonymityServiceSqlStorage::OnReady,
+                       weak_factory_.GetWeakPtr()));
+  }
+
+  void OnReady(InitStatus status) {
+    status_ = status;
+    for (auto& waiting_task : waiting_tasks_) {
+      std::move(waiting_task).Run(status);
+    }
   }
 
   static InitStatus InitializeOnDbSequence(
@@ -148,6 +165,7 @@
   absl::optional<OHTTPKeyAndExpiration> GetOHTTPKeyFor(
       const url::Origin& origin) const override {
     DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+    DCHECK(status_ != kInitNotReady);
     proto::OHTTPKeyAndExpiration result;
     if (!ohttp_key_data_->TryGetData(origin.Serialize(), &result)) {
       return absl::nullopt;
@@ -159,6 +177,7 @@
   void UpdateOHTTPKeyFor(const url::Origin& origin,
                          const OHTTPKeyAndExpiration& value) override {
     DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+    DCHECK(status_ != kInitNotReady);
     proto::OHTTPKeyAndExpiration proto_value;
     proto_value.set_hpke_key(value.key);
     proto_value.set_expiration_us(
@@ -170,6 +189,7 @@
   absl::optional<KeyAndNonUniqueUserIdWithExpiration> GetKeyAndNonUniqueUserId()
       const override {
     DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+    DCHECK(status_ != kInitNotReady);
     proto::TrustTokenKeyCommitmentWithExpiration result;
     if (!trust_token_key_commitment_data_->TryGetData(
             kTrustTokenKeyCommitmentKey, &result)) {
@@ -183,6 +203,7 @@
   void UpdateKeyAndNonUniqueUserId(
       const KeyAndNonUniqueUserIdWithExpiration& value) override {
     DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+    DCHECK(status_ != kInitNotReady);
     proto::TrustTokenKeyCommitmentWithExpiration proto_value;
     proto_value.set_key_commitment(value.key_and_id.key_commitment);
     proto_value.set_non_unique_id(value.key_and_id.non_unique_user_id);
@@ -211,8 +232,11 @@
   std::unique_ptr<
       sqlite_proto::KeyValueData<proto::TrustTokenKeyCommitmentWithExpiration>>
       trust_token_key_commitment_data_;
-  bool ready_ = false;
+
+  InitStatus status_ = kInitNotReady;
+  std::vector<base::OnceCallback<void(InitStatus)>> waiting_tasks_;
   SEQUENCE_CHECKER(sequence_checker_);
+  base::WeakPtrFactory<KAnonymityServiceSqlStorage> weak_factory_;
 };
 
 }  // namespace
diff --git a/chrome/browser/k_anonymity_service/k_anonymity_service_storage.h b/chrome/browser/k_anonymity_service/k_anonymity_service_storage.h
index a97b713..626fdd6 100644
--- a/chrome/browser/k_anonymity_service/k_anonymity_service_storage.h
+++ b/chrome/browser/k_anonymity_service/k_anonymity_service_storage.h
@@ -38,6 +38,9 @@
   enum InitStatus {
     kInitOk,
     kInitError,
+    // kInitNotReady is only be used internally, not as a response to
+    // WaitUntilReady.
+    kInitNotReady,
   };
   virtual ~KAnonymityServiceStorage();
   virtual void WaitUntilReady(
diff --git a/chrome/browser/k_anonymity_service/k_anonymity_service_storage_unittest.cc b/chrome/browser/k_anonymity_service/k_anonymity_service_storage_unittest.cc
index f92bfa2..b4ebfcc 100644
--- a/chrome/browser/k_anonymity_service/k_anonymity_service_storage_unittest.cc
+++ b/chrome/browser/k_anonymity_service/k_anonymity_service_storage_unittest.cc
@@ -66,6 +66,29 @@
   std::unique_ptr<KAnonymityServiceStorage> storage = CreateStorage();
 }
 
+TEST_P(KAnonymityServiceStorageTest, MultipleWaitUntilReady) {
+  std::unique_ptr<KAnonymityServiceStorage> storage;
+  if (StorageIsPersistent()) {
+    storage = CreateKAnonymitySqlStorageForPath(db_path());
+  } else {
+    storage = std::make_unique<KAnonymityServiceMemoryStorage>();
+  }
+  int seq_num = 0;
+  base::RunLoop run_loop;
+  storage->WaitUntilReady(base::BindLambdaForTesting(
+      [&](KAnonymityServiceStorage::InitStatus status) {
+        EXPECT_EQ(status, KAnonymityServiceStorage::InitStatus::kInitOk);
+        EXPECT_EQ(1, ++seq_num);
+      }));
+  storage->WaitUntilReady(base::BindLambdaForTesting(
+      [&](KAnonymityServiceStorage::InitStatus status) {
+        EXPECT_EQ(status, KAnonymityServiceStorage::InitStatus::kInitOk);
+        EXPECT_EQ(2, ++seq_num);
+        run_loop.Quit();
+      }));
+  run_loop.Run();
+}
+
 TEST_P(KAnonymityServiceStorageTest, SaveAndLoadOHTTPKeys) {
   url::Origin test_origin = url::Origin::Create(GURL(kTestOrigin));
   std::string test_ohttp_key(kTestOhttpKey);
diff --git a/chrome/browser/navigation_predictor/navigation_predictor.cc b/chrome/browser/navigation_predictor/navigation_predictor.cc
index 90287b0..f27db0c 100644
--- a/chrome/browser/navigation_predictor/navigation_predictor.cc
+++ b/chrome/browser/navigation_predictor/navigation_predictor.cc
@@ -15,7 +15,6 @@
 #include "base/system/sys_info.h"
 #include "chrome/browser/navigation_predictor/navigation_predictor_keyed_service.h"
 #include "chrome/browser/navigation_predictor/navigation_predictor_keyed_service_factory.h"
-#include "chrome/browser/page_load_metrics/observers/page_anchors_metrics_observer.h"
 #include "chrome/browser/preloading/prefetch/no_state_prefetch/no_state_prefetch_manager_factory.h"
 #include "chrome/browser/profiles/profile.h"
 #include "components/no_state_prefetch/browser/no_state_prefetch_manager.h"
@@ -103,6 +102,21 @@
   return ukm::GetLinearBucketMin(static_cast<int64_t>(value), 5);
 }
 
+PageAnchorsMetricsObserver::UserInteractionsData&
+NavigationPredictor::GetUserInteractionsData() const {
+  // Create the UserInteractionsData object for this WebContents if it doesn't
+  // already exist.
+  content::WebContents* web_contents =
+      content::WebContents::FromRenderFrameHost(&render_frame_host());
+  PageAnchorsMetricsObserver::UserInteractionsData::CreateForWebContents(
+      web_contents);
+  PageAnchorsMetricsObserver::UserInteractionsData* data =
+      PageAnchorsMetricsObserver::UserInteractionsData::FromWebContents(
+          web_contents);
+  DCHECK(data);
+  return *data;
+}
+
 void NavigationPredictor::ReportNewAnchorElements(
     std::vector<blink::mojom::AnchorElementMetricsPtr> elements) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
@@ -204,13 +218,26 @@
   if (it != anchors_.end()) {
     builder.SetHrefUnchanged(it->second->target_url == click->target_url);
   }
+  navigation_start_to_click_ = click->navigation_start_to_click;
+  GetUserInteractionsData().navigation_start_to_click_ =
+      navigation_start_to_click_;
+
+  builder.SetNavigationStartToLinkClickedMs(ukm::GetExponentialBucketMin(
+      navigation_start_to_click_.value().InMilliseconds(), 1.3));
   builder.Record(ukm_recorder_);
 }
 
 void NavigationPredictor::ReportAnchorElementsLeftViewport(
     std::vector<blink::mojom::AnchorElementLeftViewportPtr> elements) {
+  auto& user_interactions_data = GetUserInteractionsData();
+  auto& user_interactions = user_interactions_data.user_interactions_;
   for (const auto& element : elements) {
-    auto& user_interaction = user_interactions_[AnchorId(element->anchor_id)];
+    auto index_it =
+        tracked_anchor_id_to_index_.find(AnchorId(element->anchor_id));
+    if (index_it == tracked_anchor_id_to_index_.end()) {
+      continue;
+    }
+    auto& user_interaction = user_interactions[index_it->second];
     user_interaction.is_in_viewport = false;
     user_interaction.last_navigation_start_to_entered_viewport.reset();
     user_interaction.max_time_in_viewport = std::max(
@@ -221,8 +248,18 @@
 
 void NavigationPredictor::ReportAnchorElementPointerOver(
     blink::mojom::AnchorElementPointerOverPtr pointer_over_event) {
-  auto& user_interaction =
-      user_interactions_[AnchorId(pointer_over_event->anchor_id)];
+  auto& user_interactions_data = GetUserInteractionsData();
+  auto& user_interactions = user_interactions_data.user_interactions_;
+  auto index_it =
+      tracked_anchor_id_to_index_.find(AnchorId(pointer_over_event->anchor_id));
+  if (index_it == tracked_anchor_id_to_index_.end()) {
+    return;
+  }
+
+  auto& user_interaction = user_interactions[index_it->second];
+  if (!user_interaction.is_hovered) {
+    user_interaction.pointer_hovering_over_count++;
+  }
   user_interaction.is_hovered = true;
   user_interaction.last_navigation_start_to_pointer_over =
       pointer_over_event->navigation_start_to_pointer_over;
@@ -230,7 +267,15 @@
 
 void NavigationPredictor::ReportAnchorElementPointerOut(
     blink::mojom::AnchorElementPointerOutPtr hover_event) {
-  auto& user_interaction = user_interactions_[AnchorId(hover_event->anchor_id)];
+  auto& user_interactions_data = GetUserInteractionsData();
+  auto& user_interactions = user_interactions_data.user_interactions_;
+  auto index_it =
+      tracked_anchor_id_to_index_.find(AnchorId(hover_event->anchor_id));
+  if (index_it == tracked_anchor_id_to_index_.end()) {
+    return;
+  }
+
+  auto& user_interaction = user_interactions[index_it->second];
   user_interaction.is_hovered = false;
   user_interaction.last_navigation_start_to_pointer_over.reset();
   user_interaction.max_hover_dwell_time = std::max(
@@ -248,9 +293,17 @@
     return;
   }
 
+  auto& user_interactions_data = GetUserInteractionsData();
+  auto& user_interactions = user_interactions_data.user_interactions_;
   for (const auto& element : elements) {
     AnchorId anchor_id(element->anchor_id);
-    auto& user_interaction = user_interactions_[anchor_id];
+    auto index_it = tracked_anchor_id_to_index_.find(anchor_id);
+    if (index_it == tracked_anchor_id_to_index_.end()) {
+      // We're not tracking this element, no need to generate a
+      // NavigationPredictorAnchorElementMetrics record.
+      continue;
+    }
+    auto& user_interaction = user_interactions[index_it->second];
     user_interaction.is_in_viewport = true;
     user_interaction.last_navigation_start_to_entered_viewport =
         element->navigation_start_to_entered_viewport;
@@ -278,12 +331,6 @@
       continue;
     }
 
-    auto index_it = tracked_anchor_id_to_index_.find(anchor_id);
-    if (index_it == tracked_anchor_id_to_index_.end()) {
-      // We're not tracking this element, no need to generate a
-      // NavigationPredictorAnchorElementMetrics record.
-      continue;
-    }
     ukm::builders::NavigationPredictorAnchorElementMetrics
         anchor_element_builder(ukm_source_id_);
 
diff --git a/chrome/browser/navigation_predictor/navigation_predictor.h b/chrome/browser/navigation_predictor/navigation_predictor.h
index 73a51c3..0a6ed6e 100644
--- a/chrome/browser/navigation_predictor/navigation_predictor.h
+++ b/chrome/browser/navigation_predictor/navigation_predictor.h
@@ -13,6 +13,7 @@
 #include "base/memory/raw_ptr.h"
 #include "base/sequence_checker.h"
 #include "base/time/time.h"
+#include "chrome/browser/page_load_metrics/observers/page_anchors_metrics_observer.h"
 #include "content/public/browser/document_service.h"
 #include "content/public/browser/visibility.h"
 #include "mojo/public/cpp/bindings/pending_receiver.h"
@@ -45,35 +46,6 @@
  private:
   friend class MockNavigationPredictorForTesting;
   using AnchorId = base::StrongAlias<class AnchorId, uint32_t>;
-  // This structure holds the user interactions with a given anchor element.
-  // Whenever, the user clicks on a link, we iterate over all |UserInteractions|
-  // data and check if the anchor element is still in viewport or not. If it is
-  // still in viewport, we use |last_navigation_start_to_entered_viewport| and
-  // |navigation_start_to_click_| to update |max_time_in_viewport|.  Similarly,
-  // we also check if the pointer is still hovering over the anchor element, and
-  // use |last_navigation_start_to_pointer_over| and
-  // |navigation_start_to_click_| to update |max_hover_dwell_time|. We then
-  // record |max_time_in_viewport|, and |max_hover_dwell_time| to UKM.
-  struct UserInteractions {
-    // True if the anchor element is still in viewport, otherwise false.
-    bool is_in_viewport = false;
-    // True if the pointer is still hovering over the anchor element, otherwise
-    // false;
-    bool is_hovered = false;
-    // If the anchor element is still in viewport, it is the TimeDelta between
-    // the navigation start of the anchor element's root document and the last
-    // time the anchor element entered the viewport, otherwise empty.
-    absl::optional<base::TimeDelta> last_navigation_start_to_entered_viewport;
-    // The maximum duration that the anchor element was in the viewport.
-    absl::optional<base::TimeDelta> max_time_in_viewport;
-    // If the anchor element is still in viewport, it is the TimeDelta between
-    // the navigation start of the anchor element's root document and the last
-    // time the pointer started to hover over the anchor element, otherwise
-    // empty.
-    absl::optional<base::TimeDelta> last_navigation_start_to_pointer_over;
-    // The maximum the pointer hover dwell time over the anchor element.
-    absl::optional<base::TimeDelta> max_hover_dwell_time;
-  };
 
   NavigationPredictor(content::RenderFrameHost& render_frame_host,
                       mojo::PendingReceiver<AnchorElementMetricsHost> receiver);
@@ -116,6 +88,10 @@
   // |ratio_area|.
   int GetLinearBucketForRatioArea(int value) const;
 
+  // Returns UserInteractionsData for the current page.
+  PageAnchorsMetricsObserver::UserInteractionsData& GetUserInteractionsData()
+      const;
+
   // A count of clicks to prevent reporting more than 10 clicks to UKM.
   size_t clicked_count_ = 0;
 
@@ -125,10 +101,6 @@
                      typename AnchorId::Hasher>
       anchors_;
 
-  // User interaction data for anchor ID that we track.
-  std::unordered_map<AnchorId, UserInteractions, typename AnchorId::Hasher>
-      user_interactions_;
-
   // The time between navigation start and the last time user clicked on a link.
   absl::optional<base::TimeDelta> navigation_start_to_click_;
 
diff --git a/chrome/browser/navigation_predictor/navigation_predictor_unittest.cc b/chrome/browser/navigation_predictor/navigation_predictor_unittest.cc
index c513b99..813be22 100644
--- a/chrome/browser/navigation_predictor/navigation_predictor_unittest.cc
+++ b/chrome/browser/navigation_predictor/navigation_predictor_unittest.cc
@@ -21,6 +21,7 @@
 #include "mojo/public/cpp/bindings/pending_receiver.h"
 #include "mojo/public/cpp/bindings/receiver.h"
 #include "mojo/public/cpp/bindings/remote.h"
+#include "services/metrics/public/cpp/metrics_utils.h"
 #include "services/metrics/public/cpp/ukm_builders.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
@@ -398,8 +399,11 @@
   predictor_service()->ReportNewAnchorElements(std::move(metrics));
 
   auto click = blink::mojom::AnchorElementClick::New();
+  const long navigation_start_to_click_ms = 333;
   click->anchor_id = anchor_id_0;
   click->target_url = target_url;
+  click->navigation_start_to_click =
+      base::Milliseconds(navigation_start_to_click_ms);
   predictor_service()->ReportAnchorElementClick(std::move(click));
   base::RunLoop().RunUntilIdle();
 
@@ -412,6 +416,8 @@
   };
   EXPECT_EQ(0, get_metric(UkmEntry::kAnchorElementIndexName));
   EXPECT_EQ(1, get_metric(UkmEntry::kHrefUnchangedName));
+  EXPECT_EQ(ukm::GetExponentialBucketMin(navigation_start_to_click_ms, 1.3),
+            get_metric(UkmEntry::kNavigationStartToLinkClickedMsName));
 
   click = blink::mojom::AnchorElementClick::New();
   click->anchor_id = anchor_id_1;
@@ -466,10 +472,20 @@
     return new MockNavigationPredictorForTesting(*render_frame_host,
                                                  std::move(receiver));
   }
-  const std::
-      unordered_map<AnchorId, UserInteractions, typename AnchorId::Hasher>&
-      user_interactions() const {
-    return user_interactions_;
+  void RecordUserInteractionMetrics() {
+    auto& user_interactions = GetUserInteractionsData();
+    user_interactions.RecordUserInteractionMetrics(ukm_source_id_, {});
+  }
+  const std::unordered_map<
+      int,
+      PageAnchorsMetricsObserver::UserInteractionsData::UserInteractions>&
+  user_interactions() const {
+    return GetUserInteractionsData().user_interactions_;
+  }
+  const PageAnchorsMetricsObserver::UserInteractionsData::UserInteractions&
+  user_interaction(AnchorId anchor_id) {
+    auto index_it = tracked_anchor_id_to_index_.find(anchor_id);
+    return GetUserInteractionsData().user_interactions_[index_it->second];
   }
   absl::optional<base::TimeDelta> navigation_start_to_click() {
     return navigation_start_to_click_;
@@ -511,15 +527,26 @@
         base::RunLoop().RunUntilIdle();
       };
 
-  MockNavigationPredictorForTesting::AnchorId anchor_id{1};
+  auto report_new_anchor_element = [&predictor_service, this]() {
+    std::vector<blink::mojom::AnchorElementMetricsPtr> metrics;
+    metrics.push_back(CreateMetricsPtr());
+
+    MockNavigationPredictorForTesting::AnchorId anchor_id(
+        metrics[0]->anchor_id);
+    predictor_service->ReportNewAnchorElements(std::move(metrics));
+    return anchor_id;
+  };
+
+  auto const anchor_id = report_new_anchor_element();
+
   // Anchor element entered the viewport for the first time. Check user
   // interaction data to see if it is registered.
   const auto navigation_start_to_entered_viewport_1 = base::Milliseconds(150);
   report_anchor_element_entered_viewport(
       anchor_id, navigation_start_to_entered_viewport_1);
-  EXPECT_EQ(1u, predictor_service_host->user_interactions().size());
+  ASSERT_EQ(1u, predictor_service_host->user_interactions().size());
   const auto& user_interactions =
-      predictor_service_host->user_interactions().at(anchor_id);
+      predictor_service_host->user_interaction(anchor_id);
   EXPECT_TRUE(user_interactions.is_in_viewport);
   EXPECT_TRUE(
       user_interactions.last_navigation_start_to_entered_viewport.has_value());
@@ -592,14 +619,25 @@
         base::RunLoop().RunUntilIdle();
       };
 
-  MockNavigationPredictorForTesting::AnchorId anchor_id{1};
+  auto report_new_anchor_element = [&predictor_service, this]() {
+    std::vector<blink::mojom::AnchorElementMetricsPtr> metrics;
+    metrics.push_back(CreateMetricsPtr());
+
+    MockNavigationPredictorForTesting::AnchorId anchor_id(
+        metrics[0]->anchor_id);
+    predictor_service->ReportNewAnchorElements(std::move(metrics));
+    return anchor_id;
+  };
+
+  auto const anchor_id = report_new_anchor_element();
+
   // Pointer started hovering over the anchor element for the first time. Check
   // user interaction data to see if it is registered.
   const auto navigation_start_to_pointer_over_1 = base::Milliseconds(150);
   report_pointer_over(anchor_id, navigation_start_to_pointer_over_1);
-  EXPECT_EQ(1u, predictor_service_host->user_interactions().size());
+  ASSERT_EQ(1u, predictor_service_host->user_interactions().size());
   const auto& user_interactions =
-      predictor_service_host->user_interactions().at(anchor_id);
+      predictor_service_host->user_interaction(anchor_id);
   EXPECT_TRUE(user_interactions.is_hovered);
   EXPECT_TRUE(
       user_interactions.last_navigation_start_to_pointer_over.has_value());
@@ -676,3 +714,146 @@
   EXPECT_EQ(navigation_start_to_click,
             predictor_service_host->navigation_start_to_click());
 }
+
+TEST_F(NavigationPredictorTest, RecordUserInteractionMetrics) {
+  using AnchorId = uint32_t;
+  mojo::Remote<blink::mojom::AnchorElementMetricsHost> predictor_service;
+  auto* predictor_service_host = MockNavigationPredictorForTesting::Create(
+      main_rfh(), predictor_service.BindNewPipeAndPassReceiver());
+
+  auto report_anchor_element_left_viewport =
+      [&predictor_service](AnchorId anchor_id,
+                           const base::TimeDelta& time_in_viewport) {
+        std::vector<blink::mojom::AnchorElementLeftViewportPtr> metrics;
+        metrics.push_back(blink::mojom::AnchorElementLeftViewport::New(
+            anchor_id, time_in_viewport));
+        predictor_service->ReportAnchorElementsLeftViewport(std::move(metrics));
+        base::RunLoop().RunUntilIdle();
+      };
+
+  auto report_anchor_element_entered_viewport =
+      [&predictor_service](
+          AnchorId anchor_id,
+          const base::TimeDelta& navigation_start_to_entered_viewport) {
+        std::vector<blink::mojom::AnchorElementEnteredViewportPtr> metrics;
+        metrics.push_back(blink::mojom::AnchorElementEnteredViewport::New(
+            anchor_id, navigation_start_to_entered_viewport));
+        predictor_service->ReportAnchorElementsEnteredViewport(
+            std::move(metrics));
+        base::RunLoop().RunUntilIdle();
+      };
+  auto report_anchor_element_pointer_over =
+      [&predictor_service](
+          AnchorId anchor_id,
+          const base::TimeDelta& navigation_start_to_pinter_over) {
+        blink::mojom::AnchorElementPointerOverPtr metrics =
+            blink::mojom::AnchorElementPointerOver::New(
+                anchor_id, navigation_start_to_pinter_over);
+        predictor_service->ReportAnchorElementPointerOver(std::move(metrics));
+        base::RunLoop().RunUntilIdle();
+      };
+
+  auto report_anchor_element_pointer_hover_dwell_time =
+      [&predictor_service](AnchorId anchor_id,
+                           const base::TimeDelta& hover_dwell_time) {
+        blink::mojom::AnchorElementPointerOutPtr metrics =
+            blink::mojom::AnchorElementPointerOut::New(anchor_id,
+                                                       hover_dwell_time);
+        predictor_service->ReportAnchorElementPointerOut(std::move(metrics));
+        base::RunLoop().RunUntilIdle();
+      };
+
+  ukm::TestAutoSetUkmRecorder ukm_recorder;
+  std::vector<blink::mojom::AnchorElementMetricsPtr> metrics;
+  metrics.push_back(CreateMetricsPtr());
+  metrics.push_back(CreateMetricsPtr());
+
+  int anchor_id_0 = metrics[0]->anchor_id;
+  int anchor_id_1 = metrics[1]->anchor_id;
+  GURL target_url_1 = metrics[1]->target_url;
+  predictor_service->ReportNewAnchorElements(std::move(metrics));
+
+  // Both anchors enter the viewport.
+  const int navigation_start_to_entered_viewport = 30;
+  report_anchor_element_entered_viewport(
+      anchor_id_0, base::Milliseconds(navigation_start_to_entered_viewport));
+  report_anchor_element_entered_viewport(
+      anchor_id_1, base::Milliseconds(navigation_start_to_entered_viewport));
+
+  // Mouse hover over anchor element 0 and moves away.
+  const int navigation_start_to_pinter_over_0 = 140;
+  const int hover_dwell_time_0 = 60;
+  report_anchor_element_pointer_over(
+      anchor_id_0, base::Milliseconds(navigation_start_to_pinter_over_0));
+  report_anchor_element_pointer_hover_dwell_time(
+      anchor_id_0, base::Milliseconds(hover_dwell_time_0));
+
+  // Anchor element 0 leaves the viewport.
+  const int time_in_viewport_0 = 250;
+  report_anchor_element_left_viewport(anchor_id_0,
+                                      base::Milliseconds(time_in_viewport_0));
+
+  // Mouse hover over anchor element 1 and stays there.
+  const int navigation_start_to_pinter_over_1 = 280;
+  report_anchor_element_pointer_over(
+      anchor_id_1, base::Milliseconds(navigation_start_to_pinter_over_1));
+
+  // Mouse clicks on anchor element 1.
+  const int navigation_start_to_click_ms = 430;
+  auto click = blink::mojom::AnchorElementClick::New();
+  click->anchor_id = anchor_id_1;
+  click->target_url = target_url_1;
+  click->navigation_start_to_click =
+      base::Milliseconds(navigation_start_to_click_ms);
+  predictor_service->ReportAnchorElementClick(std::move(click));
+  base::RunLoop().RunUntilIdle();
+
+  predictor_service_host->RecordUserInteractionMetrics();
+  base::RunLoop().RunUntilIdle();
+
+  // Now check the UKM records.
+  using UkmEntry = ukm::builders::NavigationPredictorUserInteractions;
+  auto entries = ukm_recorder.GetEntriesByName(UkmEntry::kEntryName);
+  ASSERT_EQ(2u, entries.size());
+  auto get_metric = [&](auto anchor_id, auto name) {
+    return *ukm_recorder.GetEntryMetric(entries[anchor_id], name);
+  };
+  for (size_t i = 0; i < entries.size(); i++) {
+    int anchor_id = get_metric(i, UkmEntry::kAnchorIndexName);
+    EXPECT_TRUE(anchor_id == 0 || anchor_id == 1);
+    switch (anchor_id) {
+      // Anchor element 0.
+      case 0:
+        EXPECT_EQ(0, get_metric(i, UkmEntry::kIsInViewportName));
+        EXPECT_EQ(0, get_metric(i, UkmEntry::kIsPointerHoveringOverName));
+        EXPECT_EQ(
+            ukm::GetExponentialBucketMin(time_in_viewport_0, 1.3),
+            get_metric(i, UkmEntry::kMaxEnteredViewportToLeftViewportMsName));
+        EXPECT_EQ(ukm::GetExponentialBucketMin(hover_dwell_time_0, 1.3),
+                  get_metric(i, UkmEntry::kMaxHoverDwellTimeMsName));
+        EXPECT_EQ(ukm::GetExponentialBucketMin(1, 1.3),
+                  get_metric(i, UkmEntry::kPointerHoveringOverCountName));
+        break;
+
+      // Anchor element 1.
+      case 1:
+        EXPECT_EQ(1, get_metric(i, UkmEntry::kAnchorIndexName));
+        EXPECT_EQ(1, get_metric(i, UkmEntry::kIsInViewportName));
+        EXPECT_EQ(1, get_metric(i, UkmEntry::kIsPointerHoveringOverName));
+        EXPECT_EQ(
+            ukm::GetExponentialBucketMin(
+                navigation_start_to_click_ms -
+                    navigation_start_to_entered_viewport,
+                1.3),
+            get_metric(i, UkmEntry::kMaxEnteredViewportToLeftViewportMsName));
+        EXPECT_EQ(
+            ukm::GetExponentialBucketMin(navigation_start_to_click_ms -
+                                             navigation_start_to_pinter_over_1,
+                                         1.3),
+            get_metric(i, UkmEntry::kMaxHoverDwellTimeMsName));
+        EXPECT_EQ(ukm::GetExponentialBucketMin(1, 1.3),
+                  get_metric(i, UkmEntry::kPointerHoveringOverCountName));
+        break;
+    }
+  }
+}
diff --git a/chrome/browser/net/cert_verifier_service_browsertest.cc b/chrome/browser/net/cert_verifier_service_browsertest.cc
index a589f94..b48d613 100644
--- a/chrome/browser/net/cert_verifier_service_browsertest.cc
+++ b/chrome/browser/net/cert_verifier_service_browsertest.cc
@@ -2,25 +2,23 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+#include "base/strings/strcat.h"
 #include "base/test/scoped_feature_list.h"
+#include "base/test/test_future.h"
 #include "build/build_config.h"
 #include "chrome/browser/net/cert_verifier_configuration.h"
-#include "chrome/common/buildflags.h"
-#include "net/net_buildflags.h"
-
-#if BUILDFLAG(CHROME_ROOT_STORE_SUPPORTED)
-#include "base/strings/strcat.h"
-#include "base/test/test_future.h"
 #include "chrome/browser/policy/policy_test_utils.h"
+#include "chrome/common/buildflags.h"
 #include "components/policy/core/common/policy_map.h"
 #include "components/policy/policy_constants.h"
 #include "content/public/browser/network_service_instance.h"
 #include "content/public/test/browser_test.h"
 #include "net/base/features.h"
+#include "net/cert/internal/trust_store_features.h"
+#include "net/net_buildflags.h"
 #include "services/cert_verifier/public/mojom/cert_verifier_service_factory.mojom.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
-#endif  // BUILDFLAG(CHROME_ROOT_STORE_SUPPORTED)
 
 #if BUILDFLAG(CHROME_ROOT_STORE_SUPPORTED)
 class CertVerifierServiceChromeRootStoreFeaturePolicyTest
@@ -122,3 +120,112 @@
                : "PolicyNotSet"});
     });
 #endif  // BUILDFLAG(CHROME_ROOT_STORE_SUPPORTED)
+
+class CertVerifierServiceEnforceLocalAnchorConstraintsFeaturePolicyTest
+    : public policy::PolicyTest,
+      public testing::WithParamInterface<
+          std::tuple<bool, absl::optional<bool>>> {
+ public:
+  void SetUpInProcessBrowserTestFixture() override {
+    scoped_feature_list_.InitWithFeatureState(
+        net::features::kEnforceLocalAnchorConstraints,
+        feature_enforce_local_anchor_constraints());
+
+    policy::PolicyTest::SetUpInProcessBrowserTestFixture();
+
+    auto policy_val = policy_enforce_local_anchor_constraints();
+    if (policy_val.has_value()) {
+      SetPolicyValue(*policy_val);
+    }
+  }
+
+  void SetPolicyValue(absl::optional<bool> value) {
+    policy::PolicyMap policies;
+#if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC) || BUILDFLAG(IS_LINUX) || \
+    BUILDFLAG(IS_CHROMEOS) || BUILDFLAG(IS_ANDROID)
+    SetPolicy(&policies, policy::key::kEnforceLocalAnchorConstraintsEnabled,
+              absl::optional<base::Value>(value));
+#endif
+    UpdateProviderPolicy(policies);
+  }
+
+  void ExpectEnforceLocalAnchorConstraintsCorrect(
+      bool enforce_local_anchor_constraints) {
+    EXPECT_EQ(enforce_local_anchor_constraints,
+              net::IsLocalAnchorConstraintsEnforcementEnabled());
+
+#if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC) || BUILDFLAG(IS_LINUX) || \
+    BUILDFLAG(IS_CHROMEOS) || BUILDFLAG(IS_ANDROID)
+    // Set policy to the opposite value, and then test the value returned by
+    // IsLocalAnchorConstraintsEnforcementEnabled has changed.
+    SetPolicyValue(!enforce_local_anchor_constraints);
+    EXPECT_EQ(!enforce_local_anchor_constraints,
+              net::IsLocalAnchorConstraintsEnforcementEnabled());
+
+    // Unset the policy, the value used should go back to the one set by the
+    // feature flag.
+    SetPolicyValue(absl::nullopt);
+    EXPECT_EQ(feature_enforce_local_anchor_constraints(),
+              net::IsLocalAnchorConstraintsEnforcementEnabled());
+#endif
+  }
+
+  bool feature_enforce_local_anchor_constraints() const {
+    return std::get<0>(GetParam());
+  }
+
+  absl::optional<bool> policy_enforce_local_anchor_constraints() const {
+    return std::get<1>(GetParam());
+  }
+
+  bool expected_enforce_local_anchor_constraints() const {
+    auto policy_val = policy_enforce_local_anchor_constraints();
+    if (policy_val.has_value()) {
+      return *policy_val;
+    }
+    return feature_enforce_local_anchor_constraints();
+  }
+
+ private:
+  base::test::ScopedFeatureList scoped_feature_list_;
+};
+
+IN_PROC_BROWSER_TEST_P(
+    CertVerifierServiceEnforceLocalAnchorConstraintsFeaturePolicyTest,
+    Test) {
+#if BUILDFLAG(IS_ANDROID)
+  // TODO(https://crbug.com/1410924): Avoid flake on android browser tests by
+  // requiring the test to always take at least 1 second to finish. Remove this
+  // delay once issue 1410924 is resolved.
+  base::RunLoop run_loop;
+  base::SequencedTaskRunner::GetCurrentDefault()->PostDelayedTask(
+      FROM_HERE, run_loop.QuitClosure(), base::Seconds(1));
+#endif
+  ExpectEnforceLocalAnchorConstraintsCorrect(
+      expected_enforce_local_anchor_constraints());
+#if BUILDFLAG(IS_ANDROID)
+  run_loop.Run();
+#endif
+}
+
+INSTANTIATE_TEST_SUITE_P(
+    All,
+    CertVerifierServiceEnforceLocalAnchorConstraintsFeaturePolicyTest,
+    ::testing::Combine(::testing::Bool(),
+                       ::testing::Values(absl::nullopt
+#if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC) || BUILDFLAG(IS_LINUX) || \
+    BUILDFLAG(IS_CHROMEOS) || BUILDFLAG(IS_ANDROID)
+                                         ,
+                                         false,
+                                         true
+#endif
+                                         )),
+    [](const testing::TestParamInfo<
+        CertVerifierServiceEnforceLocalAnchorConstraintsFeaturePolicyTest::
+            ParamType>& info) {
+      return base::StrCat(
+          {std::get<0>(info.param) ? "FeatureTrue" : "FeatureFalse",
+           std::get<1>(info.param).has_value()
+               ? (*std::get<1>(info.param) ? "PolicyTrue" : "PolicyFalse")
+               : "PolicyNotSet"});
+    });
diff --git a/chrome/browser/net/system_network_context_manager.cc b/chrome/browser/net/system_network_context_manager.cc
index 3f6fcfb..398387d 100644
--- a/chrome/browser/net/system_network_context_manager.cc
+++ b/chrome/browser/net/system_network_context_manager.cc
@@ -66,6 +66,7 @@
 #include "mojo/public/cpp/bindings/receiver_set.h"
 #include "mojo/public/cpp/bindings/self_owned_receiver.h"
 #include "net/base/features.h"
+#include "net/cert/internal/trust_store_features.h"
 #include "net/cookies/cookie_util.h"
 #include "net/net_buildflags.h"
 #include "net/third_party/uri_template/uri_template.h"
@@ -487,6 +488,17 @@
           &SystemNetworkContextManager::UpdateExplicitlyAllowedNetworkPorts,
           base::Unretained(this)));
 
+#if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC) || BUILDFLAG(IS_LINUX) || \
+    BUILDFLAG(IS_CHROMEOS) || BUILDFLAG(IS_ANDROID)
+  pref_change_registrar_.Add(
+      prefs::kEnforceLocalAnchorConstraintsEnabled,
+      base::BindRepeating(&SystemNetworkContextManager::
+                              UpdateEnforceLocalAnchorConstraintsEnabled,
+                          base::Unretained(this)));
+  // Call the update function immediately to set the initial value, if any.
+  UpdateEnforceLocalAnchorConstraintsEnabled();
+#endif
+
   if (content::IsOutOfProcessNetworkService() &&
       base::FeatureList::IsEnabled(
           features::kRestartNetworkServiceUnsandboxedForFailedLaunch)) {
@@ -555,6 +567,13 @@
   // evaluated when it is managed.
   registry->RegisterBooleanPref(prefs::kChromeRootStoreEnabled, false);
 #endif  // BUILDFLAG(CHROME_ROOT_STORE_POLICY_SUPPORTED)
+#if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC) || BUILDFLAG(IS_LINUX) || \
+    BUILDFLAG(IS_CHROMEOS) || BUILDFLAG(IS_ANDROID)
+  // Note that the default value is not relevant because the pref is only
+  // evaluated when it is managed.
+  registry->RegisterBooleanPref(prefs::kEnforceLocalAnchorConstraintsEnabled,
+                                true);
+#endif
 
   registry->RegisterListPref(prefs::kExplicitlyAllowedNetworkPorts);
 
@@ -924,6 +943,25 @@
       ConvertExplicitlyAllowedNetworkPortsPref(local_state_));
 }
 
+#if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC) || BUILDFLAG(IS_LINUX) || \
+    BUILDFLAG(IS_CHROMEOS) || BUILDFLAG(IS_ANDROID)
+void SystemNetworkContextManager::UpdateEnforceLocalAnchorConstraintsEnabled() {
+  const PrefService::Preference* enforce_local_anchor_constraints_enabled_pref =
+      local_state_->FindPreference(
+          prefs::kEnforceLocalAnchorConstraintsEnabled);
+  if (enforce_local_anchor_constraints_enabled_pref->IsManaged()) {
+    // This depends on the CertVerifierService running in the browser process.
+    // If that changes, this would need to become a mojo message.
+    net::SetLocalAnchorConstraintsEnforcementEnabled(
+        enforce_local_anchor_constraints_enabled_pref->GetValue()->GetBool());
+  } else {
+    net::SetLocalAnchorConstraintsEnforcementEnabled(
+        base::FeatureList::IsEnabled(
+            net::features::kEnforceLocalAnchorConstraints));
+  }
+}
+#endif
+
 void SystemNetworkContextManager::UpdateReferrersEnabled() {
   GetContext()->SetEnableReferrers(enable_referrers_.GetValue());
 }
diff --git a/chrome/browser/net/system_network_context_manager.h b/chrome/browser/net/system_network_context_manager.h
index e12acea..48bad7db 100644
--- a/chrome/browser/net/system_network_context_manager.h
+++ b/chrome/browser/net/system_network_context_manager.h
@@ -193,6 +193,13 @@
   // the network process.
   void UpdateExplicitlyAllowedNetworkPorts();
 
+#if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC) || BUILDFLAG(IS_LINUX) || \
+    BUILDFLAG(IS_CHROMEOS) || BUILDFLAG(IS_ANDROID)
+  // Applies the current value of the kEnforceLocalAnchorConstraintsEnabled
+  // pref to the enforcement state.
+  void UpdateEnforceLocalAnchorConstraintsEnabled();
+#endif
+
   // The PrefService to retrieve all the pref values.
   raw_ptr<PrefService> local_state_;
 
diff --git a/chrome/browser/page_load_metrics/observers/formfill_page_load_metrics_observer_browsertest.cc b/chrome/browser/page_load_metrics/observers/formfill_page_load_metrics_observer_browsertest.cc
index 59c6ef6..1f2a9799 100644
--- a/chrome/browser/page_load_metrics/observers/formfill_page_load_metrics_observer_browsertest.cc
+++ b/chrome/browser/page_load_metrics/observers/formfill_page_load_metrics_observer_browsertest.cc
@@ -47,7 +47,7 @@
    private:
     autofill::TestAutofillManagerWaiter waiter_{
         *this,
-        {&AutofillManager::Observer::OnAfterFormsSeen}};
+        {autofill::AutofillManagerEvent::kFormsSeen}};
   };
 
   void SetUpOnMainThread() override {
diff --git a/chrome/browser/page_load_metrics/observers/page_anchors_metrics_observer.cc b/chrome/browser/page_load_metrics/observers/page_anchors_metrics_observer.cc
index 7c02518..78bcbcd 100644
--- a/chrome/browser/page_load_metrics/observers/page_anchors_metrics_observer.cc
+++ b/chrome/browser/page_load_metrics/observers/page_anchors_metrics_observer.cc
@@ -5,6 +5,7 @@
 #include "chrome/browser/page_load_metrics/observers/page_anchors_metrics_observer.h"
 
 #include "content/public/browser/web_contents_user_data.h"
+#include "services/metrics/public/cpp/metrics_utils.h"
 #include "services/metrics/public/cpp/ukm_builders.h"
 
 PageAnchorsMetricsObserver::AnchorsData::AnchorsData(
@@ -37,7 +38,82 @@
 
 WEB_CONTENTS_USER_DATA_KEY_IMPL(PageAnchorsMetricsObserver::AnchorsData);
 
-void PageAnchorsMetricsObserver::RecordUkm() {
+PageAnchorsMetricsObserver::UserInteractionsData::UserInteractionsData(
+    content::WebContents* contents)
+    : content::WebContentsUserData<UserInteractionsData>(*contents) {}
+
+PageAnchorsMetricsObserver::UserInteractionsData::~UserInteractionsData() =
+    default;
+
+void PageAnchorsMetricsObserver::UserInteractionsData::
+    RecordUserInteractionMetrics(
+        ukm::SourceId ukm_source_id,
+        absl::optional<base::TimeDelta> navigation_start_to_now) {
+  // In case we don't have a valid |navigation_start_to_click_|, the best we
+  // could do is to use |navigation_start_to_now|. It may cause some
+  // inconsistency in the measurements but it is better than not recording it.
+  if (!navigation_start_to_click_.has_value()) {
+    navigation_start_to_click_ = navigation_start_to_now;
+  }
+
+  auto get_max_time_ms = [this](auto const& max_time,
+                                auto const last_navigation_start_to) {
+    int64_t max_time_ms = -1;
+    if (last_navigation_start_to.has_value() &&
+        navigation_start_to_click_.has_value()) {
+      max_time_ms = std::max(max_time_ms, (navigation_start_to_click_.value() -
+                                           last_navigation_start_to.value())
+                                              .InMilliseconds());
+    }
+    if (max_time.has_value()) {
+      max_time_ms = std::max(max_time_ms, max_time.value().InMilliseconds());
+    }
+    return max_time_ms;
+  };
+
+  auto* ukm_recorder = ukm::UkmRecorder::Get();
+
+  for (const auto& [anchor_index, user_interaction] : user_interactions_) {
+    ukm::builders::NavigationPredictorUserInteractions builder(ukm_source_id);
+    builder.SetAnchorIndex(anchor_index);
+    builder.SetIsInViewport(user_interaction.is_in_viewport);
+    builder.SetPointerHoveringOverCount(ukm::GetExponentialBucketMin(
+        user_interaction.pointer_hovering_over_count, 1.3));
+    builder.SetIsPointerHoveringOver(user_interaction.is_hovered);
+    builder.SetMaxEnteredViewportToLeftViewportMs(ukm::GetExponentialBucketMin(
+        get_max_time_ms(
+            user_interaction.max_time_in_viewport,
+            user_interaction.last_navigation_start_to_entered_viewport),
+        1.3));
+    builder.SetMaxHoverDwellTimeMs(ukm::GetExponentialBucketMin(
+        get_max_time_ms(user_interaction.max_hover_dwell_time,
+                        user_interaction.last_navigation_start_to_pointer_over),
+        1.3));
+    builder.Record(ukm_recorder);
+  }
+}
+
+WEB_CONTENTS_USER_DATA_KEY_IMPL(
+    PageAnchorsMetricsObserver::UserInteractionsData);
+
+void PageAnchorsMetricsObserver::RecordUserInteractionDataToUkm() {
+  PageAnchorsMetricsObserver::UserInteractionsData* data =
+      PageAnchorsMetricsObserver::UserInteractionsData::FromWebContents(
+          web_contents_);
+  if (!data) {
+    return;
+  }
+  auto ukm_source_id = GetDelegate().GetPageUkmSourceId();
+  absl::optional<base::TimeDelta> navigation_start_to_now;
+  const base::TimeTicks navigation_start_time =
+      GetDelegate().GetNavigationStart();
+  if (!navigation_start_time.is_null()) {
+    navigation_start_to_now = base::TimeTicks::Now() - navigation_start_time;
+  }
+  data->RecordUserInteractionMetrics(ukm_source_id, navigation_start_to_now);
+}
+
+void PageAnchorsMetricsObserver::RecordAnchorDataToUkm() {
   PageAnchorsMetricsObserver::AnchorsData* data =
       PageAnchorsMetricsObserver::AnchorsData::FromWebContents(web_contents_);
   if (!data || data->number_of_anchors_ == 0) {
@@ -85,7 +161,8 @@
   if (is_in_prerendered_page_)
     return;
 
-  RecordUkm();
+  RecordAnchorDataToUkm();
+  RecordUserInteractionDataToUkm();
 }
 page_load_metrics::PageLoadMetricsObserver::ObservePolicy
 PageAnchorsMetricsObserver::FlushMetricsOnAppEnterBackground(
@@ -94,7 +171,8 @@
   if (is_in_prerendered_page_)
     return CONTINUE_OBSERVING;
 
-  RecordUkm();
+  RecordAnchorDataToUkm();
+  RecordUserInteractionDataToUkm();
   return STOP_OBSERVING;
 }
 
diff --git a/chrome/browser/page_load_metrics/observers/page_anchors_metrics_observer.h b/chrome/browser/page_load_metrics/observers/page_anchors_metrics_observer.h
index 607510b6..d22a1ef 100644
--- a/chrome/browser/page_load_metrics/observers/page_anchors_metrics_observer.h
+++ b/chrome/browser/page_load_metrics/observers/page_anchors_metrics_observer.h
@@ -18,6 +18,64 @@
 class PageAnchorsMetricsObserver
     : public page_load_metrics::PageLoadMetricsObserver {
  public:
+  class UserInteractionsData
+      : public content::WebContentsUserData<UserInteractionsData> {
+   public:
+    UserInteractionsData(const UserInteractionsData&) = delete;
+    ~UserInteractionsData() override;
+    UserInteractionsData& operator=(const UserInteractionsData&) = delete;
+
+    // This structure holds the user interactions with a given anchor element.
+    // Whenever, the user clicks on a link, we iterate over all
+    // |UserInteractions| data and check if the anchor element is still in
+    // viewport or not. If it is still in viewport, we use
+    // |last_navigation_start_to_entered_viewport| and
+    // |navigation_start_to_click_| to update |max_time_in_viewport|. Similarly,
+    // we also check if the pointer is still hovering over the anchor element,
+    // and use |last_navigation_start_to_pointer_over| and
+    // |navigation_start_to_click_| to update |max_hover_dwell_time|. We then
+    // record |max_time_in_viewport|, and |max_hover_dwell_time| to UKM.
+    struct UserInteractions {
+      // True if the anchor element is still in viewport, otherwise false.
+      bool is_in_viewport = false;
+      // True if the pointer is still hovering over the anchor element,
+      // otherwise false;
+      bool is_hovered = false;
+      // Number of times the pointer was hovering over the anchor element.
+      int pointer_hovering_over_count = 0;
+      // If the anchor element is still in viewport, it is the TimeDelta between
+      // the navigation start of the anchor element's root document and the last
+      // time the anchor element entered the viewport, otherwise empty.
+      absl::optional<base::TimeDelta> last_navigation_start_to_entered_viewport;
+      // The maximum duration that the anchor element was in the viewport.
+      absl::optional<base::TimeDelta> max_time_in_viewport;
+      // If the anchor element is still in viewport, it is the TimeDelta between
+      // the navigation start of the anchor element's root document and the last
+      // time the pointer started to hover over the anchor element, otherwise
+      // empty.
+      absl::optional<base::TimeDelta> last_navigation_start_to_pointer_over;
+      // The maximum the pointer hover dwell time over the anchor element.
+      absl::optional<base::TimeDelta> max_hover_dwell_time;
+    };
+
+    // Records user interaction metrics to UKM.
+    void RecordUserInteractionMetrics(
+        ukm::SourceId ukm_source_id,
+        absl::optional<base::TimeDelta> navigation_start_to_now);
+
+    // User interaction data for the tracked anchor index.
+    std::unordered_map<int, UserInteractions> user_interactions_;
+
+    // The time between navigation start and the last time user clicked on a
+    // link.
+    absl::optional<base::TimeDelta> navigation_start_to_click_;
+
+   private:
+    friend class content::WebContentsUserData<UserInteractionsData>;
+    explicit UserInteractionsData(content::WebContents* contents);
+    WEB_CONTENTS_USER_DATA_KEY_DECL();
+  };
+
   class AnchorsData : public content::WebContentsUserData<AnchorsData> {
    public:
     AnchorsData(const AnchorsData&) = delete;
@@ -65,7 +123,8 @@
       content::NavigationHandle* navigation_handle) override;
 
  private:
-  void RecordUkm();
+  void RecordAnchorDataToUkm();
+  void RecordUserInteractionDataToUkm();
 
   bool is_in_prerendered_page_ = false;
 
diff --git a/chrome/browser/policy/configuration_policy_handler_list_factory.cc b/chrome/browser/policy/configuration_policy_handler_list_factory.cc
index 28bc307..c8e1c6c 100644
--- a/chrome/browser/policy/configuration_policy_handler_list_factory.cc
+++ b/chrome/browser/policy/configuration_policy_handler_list_factory.cc
@@ -1710,6 +1710,13 @@
     base::Value::Type::BOOLEAN },
 #endif  // BUILDFLAG(CHROME_ROOT_STORE_POLICY_SUPPORTED)
 
+#if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC) || BUILDFLAG(IS_LINUX) || \
+    BUILDFLAG(IS_CHROMEOS) || BUILDFLAG(IS_ANDROID)
+  { key::kEnforceLocalAnchorConstraintsEnabled,
+    prefs::kEnforceLocalAnchorConstraintsEnabled,
+    base::Value::Type::BOOLEAN },
+#endif
+
   { key::kScrollToTextFragmentEnabled,
     prefs::kScrollToTextFragmentEnabled,
     base::Value::Type::BOOLEAN },
diff --git a/chrome/browser/printing/print_browsertest.cc b/chrome/browser/printing/print_browsertest.cc
index 08a143d..304bdbb7 100644
--- a/chrome/browser/printing/print_browsertest.cc
+++ b/chrome/browser/printing/print_browsertest.cc
@@ -2419,11 +2419,13 @@
       std::unique_ptr<PrintingContext::Delegate> printing_context_delegate,
       std::unique_ptr<PrintingContext> printing_context,
       PrintJob* print_job,
+      mojom::PrintTargetType print_target_type,
       bool simulate_spooling_memory_errors,
       TestPrintJobWorkerOop::PrintCallbacks* callbacks)
       : PrintJobWorkerOop(std::move(printing_context_delegate),
                           std::move(printing_context),
                           print_job,
+                          print_target_type,
                           simulate_spooling_memory_errors),
         callbacks_(callbacks) {}
   TestPrintJobWorkerOop(const TestPrintJobWorkerOop&) = delete;
@@ -2511,11 +2513,12 @@
   }
 #endif  // BUILDFLAG(ENABLE_OOP_BASIC_PRINT_DIALOG)
 
-  std::unique_ptr<PrintJobWorker> TransferContextToNewWorker(
+  std::unique_ptr<PrintJobWorkerOop> CreatePrintJobWorker(
       PrintJob* print_job) override {
     return std::make_unique<TestPrintJobWorkerOop>(
         std::move(printing_context_delegate_), std::move(printing_context_),
-        print_job, simulate_spooling_memory_errors_, callbacks_);
+        print_job, print_target_type(), simulate_spooling_memory_errors_,
+        callbacks_);
   }
 
   bool simulate_spooling_memory_errors_;
diff --git a/chrome/browser/printing/print_job_worker_oop.cc b/chrome/browser/printing/print_job_worker_oop.cc
index 4efd0ee..8ba3d0c 100644
--- a/chrome/browser/printing/print_job_worker_oop.cc
+++ b/chrome/browser/printing/print_job_worker_oop.cc
@@ -50,20 +50,23 @@
     std::unique_ptr<PrintingContext> printing_context,
     PrintJob* print_job,
     mojom::PrintTargetType print_target_type)
-    : PrintJobWorker(std::move(printing_context_delegate),
-                     std::move(printing_context),
-                     print_job),
-      print_target_type_(print_target_type) {}
+    : PrintJobWorkerOop(std::move(printing_context_delegate),
+                        std::move(printing_context),
+                        print_job,
+                        print_target_type,
+                        /*simulate_spooling_memory_errors=*/false) {}
 
 PrintJobWorkerOop::PrintJobWorkerOop(
     std::unique_ptr<PrintingContext::Delegate> printing_context_delegate,
     std::unique_ptr<PrintingContext> printing_context,
     PrintJob* print_job,
+    mojom::PrintTargetType print_target_type,
     bool simulate_spooling_memory_errors)
     : PrintJobWorker(std::move(printing_context_delegate),
                      std::move(printing_context),
                      print_job),
-      simulate_spooling_memory_errors_(simulate_spooling_memory_errors) {}
+      simulate_spooling_memory_errors_(simulate_spooling_memory_errors),
+      print_target_type_(print_target_type) {}
 
 PrintJobWorkerOop::~PrintJobWorkerOop() {
   DCHECK(!service_manager_client_id_.has_value());
diff --git a/chrome/browser/printing/print_job_worker_oop.h b/chrome/browser/printing/print_job_worker_oop.h
index aff12ab8..b92e14d 100644
--- a/chrome/browser/printing/print_job_worker_oop.h
+++ b/chrome/browser/printing/print_job_worker_oop.h
@@ -53,6 +53,7 @@
       std::unique_ptr<PrintingContext::Delegate> printing_context_delegate,
       std::unique_ptr<PrintingContext> printing_context,
       PrintJob* print_job,
+      mojom::PrintTargetType print_target_type,
       bool simulate_spooling_memory_errors);
 
   virtual void OnDidStartPrinting(mojom::ResultCode result);
@@ -99,7 +100,7 @@
   void SendCancel(scoped_refptr<PrintJob> job);
 
   // Used to test spooling memory error handling.
-  bool simulate_spooling_memory_errors_ = false;
+  const bool simulate_spooling_memory_errors_;
 
   // Client ID with the print backend service manager for this print job.
   // Used only from UI thread.
diff --git a/chrome/browser/printing/printer_query_oop.cc b/chrome/browser/printing/printer_query_oop.cc
index 7fbf5f9d..24c6772 100644
--- a/chrome/browser/printing/printer_query_oop.cc
+++ b/chrome/browser/printing/printer_query_oop.cc
@@ -42,9 +42,9 @@
 std::unique_ptr<PrintJobWorker> PrinterQueryOop::TransferContextToNewWorker(
     PrintJob* print_job) {
   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
-  return std::make_unique<PrintJobWorkerOop>(
-      std::move(printing_context_delegate_), std::move(printing_context_),
-      print_job, print_target_type_);
+  // TODO(crbug.com/1414968)  Do extra setup on the worker as needed for
+  // supporting OOP system print dialogs.
+  return CreatePrintJobWorker(print_job);
 }
 
 void PrinterQueryOop::OnDidUseDefaultSettings(
@@ -224,4 +224,11 @@
 }
 #endif  // BUILDFLAG(ENABLE_OOP_BASIC_PRINT_DIALOG)
 
+std::unique_ptr<PrintJobWorkerOop> PrinterQueryOop::CreatePrintJobWorker(
+    PrintJob* print_job) {
+  return std::make_unique<PrintJobWorkerOop>(
+      std::move(printing_context_delegate_), std::move(printing_context_),
+      print_job, print_target_type_);
+}
+
 }  // namespace printing
diff --git a/chrome/browser/printing/printer_query_oop.h b/chrome/browser/printing/printer_query_oop.h
index 53aed761..cf09c05 100644
--- a/chrome/browser/printing/printer_query_oop.h
+++ b/chrome/browser/printing/printer_query_oop.h
@@ -10,6 +10,7 @@
 #include "base/functional/callback.h"
 #include "base/values.h"
 #include "build/build_config.h"
+#include "chrome/browser/printing/print_job_worker_oop.h"
 #include "chrome/browser/printing/printer_query.h"
 #include "chrome/services/printing/public/mojom/print_backend_service.mojom.h"
 #include "printing/buildflags/buildflags.h"
@@ -59,6 +60,14 @@
                               SettingsCallback callback);
 #endif
 
+  // Used by `TransferContextToNewWorker()`.  Virtual to support testing.
+  virtual std::unique_ptr<PrintJobWorkerOop> CreatePrintJobWorker(
+      PrintJob* print_job);
+
+  mojom::PrintTargetType print_target_type() const {
+    return print_target_type_;
+  }
+
  private:
   mojom::PrintTargetType print_target_type_ =
       mojom::PrintTargetType::kDirectToDevice;
diff --git a/chrome/browser/resources/chromeos/contact_center_insights/background.js b/chrome/browser/resources/chromeos/contact_center_insights/background.js
index 963078f..9cdbcfdb 100644
--- a/chrome/browser/resources/chromeos/contact_center_insights/background.js
+++ b/chrome/browser/resources/chromeos/contact_center_insights/background.js
@@ -2,107 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-// Bundle needs to be imported first so we can support closure style imports
-// that follow
-importScripts('ccaas_deps.js');
-
-goog.require('proto.reporting.Record');
-goog.require('proto.reporting.Destination');
-goog.require('proto.reporting.Priority');
-goog.require('proto.reporting.MetricData');
-goog.require('proto.reporting.TelemetryData');
-goog.require('proto.reporting.UserStatusTelemetry');
-goog.require('proto.reporting.UserStatusTelemetry.DeviceActivityState');
-
-const DEVICE_ACTIVITY_STATE_ALARM = 'DeviceActivityState';
-const REPORT_DEVICE_ACTIVITY_STATE_PERIOD_MINUTES = 15;
-const IDLE_THRESHOLD_SECONDS = 5 /** minutes **/ * 60;
-
-function reportDeviceActivityState() {
-  chrome.idle.queryState(IDLE_THRESHOLD_SECONDS, (state) => {
-    const userStatusTelemetry = new proto.reporting.UserStatusTelemetry();
-    const mappedState = getMappedDeviceActivityState(state);
-    userStatusTelemetry.setDeviceActivityState(mappedState);
-
-    const telemetryData = new proto.reporting.TelemetryData();
-    telemetryData.setUserStatusTelemetry(userStatusTelemetry);
-
-    reportTelemetryData(telemetryData);
-  });
-}
-
-/**
- * Returns the internal representation for the current device activity state.
- * @param {string} activityState Device activity state.
- * @return {!proto.reporting.UserStatusTelemetry.DeviceActivityState} internal
- *     proto enum representation.
- */
-function getMappedDeviceActivityState(activityState) {
-  switch (activityState) {
-    case 'active':
-      return proto.reporting.UserStatusTelemetry.DeviceActivityState.ACTIVE;
-    case 'idle':
-      return proto.reporting.UserStatusTelemetry.DeviceActivityState.IDLE;
-    case 'locked':
-      return proto.reporting.UserStatusTelemetry.DeviceActivityState.LOCKED;
-    default:
-      return proto.reporting.UserStatusTelemetry.DeviceActivityState
-          .DEVICE_ACTIVITY_STATE_UNKNOWN;
-  }
-}
-
-/**
- * Reports collected telemetry data.
- * @param {!proto.reporting.TelemetryData} telemetryData Data to report.
- */
-function reportTelemetryData(telemetryData) {
-  const metricData = new proto.reporting.MetricData();
-  metricData.setTelemetryData(telemetryData);
-
-  // Also set metric timestamp (in ms) because the server also checks for these
-  // in addition to the ones set with the record (in microseconds) today.
-  const timestampMs = Date.now();
-  metricData.setTimestampMs(timestampMs);
-
-  const record = new proto.reporting.Record();
-  record.setDestination(proto.reporting.Destination.TELEMETRY_METRIC);
-  record.setData(metricData.serializeBinary());
-  record.setTimestampUs(timestampMs * 1000);
-
-  // Prepare enqueue record request
-  const request = {
-    recordData: record.serializeBinary(),
-    priority: proto.reporting.Priority.FAST_BATCH,
-    eventType: chrome.enterprise.reportingPrivate.EventType.USER,
-  };
-
-  // Report prepared request
-  chrome.enterprise.reportingPrivate.enqueueRecord(request);
-}
-
-/**
- * Creates an alarm with specified polling interval if one is not registered
- * already.
- * @param {string} name Alarm name.
- * @param {number} periodInMinutes Polling interval in minutes
- */
-function createAlarm(name, periodInMinutes) {
-  chrome.alarms.get(name, (alarm) => {
-    if (!alarm) {
-      chrome.alarms.create(name, {periodInMinutes});
-    }
-  });
-}
-
-// Global listener for all alarms
-chrome.alarms.onAlarm.addListener((alarm) => {
-  if (alarm.name === DEVICE_ACTIVITY_STATE_ALARM) {
-    reportDeviceActivityState();
-  }
-});
-
-// Register alarms for periodically reporting telemetry data.
-chrome.runtime.onInstalled.addListener(() => {
-  createAlarm(
-      DEVICE_ACTIVITY_STATE_ALARM, REPORT_DEVICE_ACTIVITY_STATE_PERIOD_MINUTES);
-});
+// We have graduated all of the metrics we used to collect using this background
+// service worker.
+// TODO (b/269766156): Delete this file and update the manifest once it is
+// decoupled from grit.
diff --git a/chrome/browser/resources/chromeos/contact_center_insights/manifest.json b/chrome/browser/resources/chromeos/contact_center_insights/manifest.json
index 286aafe..a0c84540 100644
--- a/chrome/browser/resources/chromeos/contact_center_insights/manifest.json
+++ b/chrome/browser/resources/chromeos/contact_center_insights/manifest.json
@@ -2,15 +2,12 @@
   // chrome-extension://oebfonohdfogiaaaelfmjlkjbgdbaahf
   "key": "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxOnBHZ/Nxvc2WeeAxeSNXiOAmOPSeyjC70+3tfpbySO8Pslt/zWJBT76dhM/Gm+OwVoFdB5/C/pXfJGMqPTGFNyMq5MgJuo4giqWA542sKgn4y8lwtN4Z/2XhZPo5BXizyyxRq1lZdV21ZyImmW+3ODkC35CZ/bTXdpHzV0I5hJ14wVRzZ/fS047R5Dx+/JUJCp7uspL8nt00hPuwFmW/PLZjAnmMq3ULW216YP2VhF9ROUwRwvZqgJ5nWmfQj7dlVKstwa1PtQgqe/2p0oVif4NP/hg3+zpz7y9f0kXFRB/QVjg6R9hLGqDuqu5uh5LfTarVMH35zvaiZGhtB4rLQIDAQAB",
   "name": "Contact Center Insights",
-  "version": "1.0.1",
+  "version": "1.0.2",
   "manifest_version": 3,
   "background": {
     "service_worker": "background_wrapper.js"
   },
   "permissions": [
-    "alarms",
-    "background",
-    "enterprise.reportingPrivate",
-    "idle"
+    "background"
   ]
 }
diff --git a/chrome/browser/resources/internals/user_education/BUILD.gn b/chrome/browser/resources/internals/user_education/BUILD.gn
index cce75227..6502d7b2 100644
--- a/chrome/browser/resources/internals/user_education/BUILD.gn
+++ b/chrome/browser/resources/internals/user_education/BUILD.gn
@@ -12,19 +12,28 @@
 preprocess_folder = "$target_gen_dir/preprocessed"
 
 html_to_wrapper("html_wrapper_files") {
-  in_files = [ "user_education_internals.html" ]
+  in_files = [
+    "user_education_internals.html",
+    "user_education_internals_card.html",
+  ]
 }
 
 preprocess_if_expr("preprocess_gen") {
   in_folder = target_gen_dir
   out_folder = preprocess_folder
-  in_files = [ "user_education_internals.html.ts" ]
+  in_files = [
+    "user_education_internals.html.ts",
+    "user_education_internals_card.html.ts",
+  ]
   deps = [ ":html_wrapper_files" ]
 }
 
 preprocess_if_expr("preprocess_src") {
   out_folder = preprocess_folder
-  in_files = [ "user_education_internals.ts" ]
+  in_files = [
+    "user_education_internals.ts",
+    "user_education_internals_card.ts",
+  ]
 }
 
 copy("copy_mojo") {
@@ -39,7 +48,9 @@
   tsconfig_base = "tsconfig_base.json"
   in_files = [
     "user_education_internals.ts",
+    "user_education_internals_card.ts",
     "user_education_internals.html.ts",
+    "user_education_internals_card.html.ts",
     "user_education_internals.mojom-webui.ts",
   ]
   deps = [
diff --git a/chrome/browser/resources/internals/user_education/index.html b/chrome/browser/resources/internals/user_education/index.html
index 9462ae3..ea8aa9c 100644
--- a/chrome/browser/resources/internals/user_education/index.html
+++ b/chrome/browser/resources/internals/user_education/index.html
@@ -13,6 +13,7 @@
 
       html,
       body {
+        height: 100%;
         margin: 0;
       }
   </style>
diff --git a/chrome/browser/resources/internals/user_education/user_education_internals.html b/chrome/browser/resources/internals/user_education/user_education_internals.html
index 73186d6..369822b 100644
--- a/chrome/browser/resources/internals/user_education/user_education_internals.html
+++ b/chrome/browser/resources/internals/user_education/user_education_internals.html
@@ -1,7 +1,8 @@
-<style include="cr-hidden-style">
+<style include="cr-hidden-style cr-shared-style">
 :host {
   display: flex;
   flex-direction: column;
+  height: 100%;
 
   --main-column-max-width: 680px;
   --side-bar-width: 200px;
@@ -9,9 +10,6 @@
 
 cr-toolbar {
   min-height: 56px;
-  position: sticky;
-  top: 0;
-  z-index: 2;
 
   --cr-toolbar-center-basis: var(--main-column-max-width);
 }
@@ -20,12 +18,17 @@
   --cr-toolbar-left-spacer-width: var(--side-bar-width);
 }
 
+#cr-container-shadow-top {
+  /* Needs to be higher than #container's z-index to appear above
+    * scrollbars. */
+  z-index: 2;
+}
+
 #container {
   align-items: flex-start;
   display: flex;
   flex: 1;
-  overflow: overlay;
-  position: relative;
+  overflow: auto;
 }
 
 #left,
@@ -42,6 +45,7 @@
   display: flex;
   flex-basis: var(--main-column-max-width);
   justify-content: center;
+  position: relative;
 }
 
 #content {
@@ -49,30 +53,6 @@
   width: var(--main-column-max-width);
 }
 
-.card {
-  border-top: 1px solid var(--cr-separator-color);
-  display: flex;
-  padding-block: var(--cr-section-vertical-padding);
-}
-
-.card-content {
-  flex: 1;
-}
-
-.card cr-button {
-  flex: 0;
-  margin-block-start: var(--cr-section-vertical-padding);
-  margin-inline-end: var(--cr-section-padding);
-}
-
-.card ol {
-  padding-inline-start: 2ex;
-}
-
-.card li {
-  margin-block: 1ex;
-}
-
 /* Width is left + --cr-section-padding + main. */
 @media (max-width: 920px) {
   #main {
@@ -114,56 +94,21 @@
       -->
       <h2>Tutorials</h2>
       <template id="tutorials" is="dom-repeat" items="[[tutorials_]]">
-        <!-- TODO(dfried): componentize this card object. -->
-        <div class="card" hidden$="[[!promoFilter_(item, filter)]]">
-          <div class="card-content">
-            <h3>[[item.displayTitle]]</h3>
-            <p>Added on [[formatItemDate_(item)]]</p>
-            <p hidden$="[[!showDescription_(item)]]">
-              [[item.displayDescription]]
-            </p>
-            <p><b>Type:</b> [[item.type]]</p>
-            <p><b>OS: </b>[[formatPlatforms_(item)]]</p>
-            <div hidden$="[[!showInstructions_(item)]]">
-              <p><b>Instructions:</b></p>
-              <ol>
-                <template is="dom-repeat" items="[[item.instructions]]">
-                  <li>[[item]]</li>
-                </template>
-              </ol>
-            </div>
-          </div>
-          <cr-button class="action-button" id="[[item.internalName]]"
-                     on-click="startTutorial_">
-            Launch
-          </cr-button>
-        </div>
+        <user-education-internals-card
+            id="[[item.internalName]]"
+            hidden$="[[!promoFilter_(item, filter)]]"
+            promo="[[item]]"
+            on-promo-launch="startTutorial_">
+        </user-education-internals-card>
       </template>
       <h2>Feature Promos</h2>
       <template is="dom-repeat" id="promos" items="[[featurePromos_]]">
-        <div class="card" hidden$="[[!promoFilter_(item, filter)]]">
-          <div class="card-content">
-            <h3>[[item.displayTitle]]</h3>
-            <p>Added on [[formatItemDate_(item)]]</p>
-            <p hidden$="[[!showDescription_(item)]]">
-              [[item.displayDescription]]
-            </p>
-            <p><b>Type:</b> [[item.type]]</p>
-            <p><b>OS:</b> [[formatPlatforms_(item)]]</p>
-            <div hidden$="[[!showInstructions_(item)]]">
-              <p><b>Instructions:</b></p>
-              <ol>
-                <template is="dom-repeat" items="[[item.instructions]]">
-                  <li>[[item]]</li>
-                </template>
-              </ol>
-            </div>
-          </div>
-          <cr-button class="action-button" id="[[item.internalName]]"
-                     on-click="showFeaturePromo_">
-            Launch
-          </cr-button>
-        </div>
+        <user-education-internals-card
+            id="[[item.internalName]]"
+            hidden$="[[!promoFilter_(item, filter)]]"
+            promo="[[item]]"
+            on-promo-launch="showFeaturePromo_">
+        </user-education-internals-card>
       </template>
     </div>
   </div>
diff --git a/chrome/browser/resources/internals/user_education/user_education_internals.ts b/chrome/browser/resources/internals/user_education/user_education_internals.ts
index 22f2207..6815252 100644
--- a/chrome/browser/resources/internals/user_education/user_education_internals.ts
+++ b/chrome/browser/resources/internals/user_education/user_education_internals.ts
@@ -3,25 +3,29 @@
 // found in the LICENSE file.
 
 import 'chrome://resources/cr_components/help_bubble/help_bubble.js';
-import 'chrome://resources/cr_elements/cr_button/cr_button.js';
 import 'chrome://resources/cr_elements/cr_hidden_style.css.js';
+import 'chrome://resources/cr_elements/cr_shared_style.css.js';
 import 'chrome://resources/cr_elements/cr_shared_vars.css.js';
 import 'chrome://resources/cr_elements/cr_toast/cr_toast.js';
 import 'chrome://resources/cr_elements/cr_toolbar/cr_toolbar.js';
 import 'chrome://resources/cr_elements/cr_toolbar/cr_toolbar_search_field.js';
 import 'chrome://resources/polymer/v3_0/iron-icon/iron-icon.js';
+import './user_education_internals_card.js';
 
 import {HelpBubbleMixin, HelpBubbleMixinInterface} from 'chrome://resources/cr_components/help_bubble/help_bubble_mixin.js';
+import {CrContainerShadowMixin, CrContainerShadowMixinInterface} from 'chrome://resources/cr_elements/cr_container_shadow_mixin.js';
 import {CrToastElement} from 'chrome://resources/cr_elements/cr_toast/cr_toast.js';
 import {CrToolbarElement} from 'chrome://resources/cr_elements/cr_toolbar/cr_toolbar.js';
-import {DomRepeat, DomRepeatEvent, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+import {DomRepeat, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
 import {getTemplate} from './user_education_internals.html.js';
 import {FeaturePromoDemoPageInfo, UserEducationInternalsPageHandler, UserEducationInternalsPageHandlerInterface} from './user_education_internals.mojom-webui.js';
 
-const UserEducationInternalsElementBase = HelpBubbleMixin(PolymerElement) as {
-  new (): PolymerElement & HelpBubbleMixinInterface,
-};
+const UserEducationInternalsElementBase =
+    CrContainerShadowMixin(HelpBubbleMixin(PolymerElement)) as {
+      new (): PolymerElement & HelpBubbleMixinInterface &
+          CrContainerShadowMixinInterface,
+    };
 
 interface UserEducationInternalsElement {
   $: {
@@ -89,7 +93,8 @@
     this.$.promos.addEventListener(
         'rendered-item-count-changed', (_: Event) => {
           this.registerHelpBubble(
-              'kWebUIIPHDemoElementIdentifier', '#IPH_WebUiHelpBubbleTest');
+              'kWebUIIPHDemoElementIdentifier',
+              ['#IPH_WebUiHelpBubbleTest', '#launch']);
         }, {
           once: true,
         });
@@ -103,8 +108,8 @@
     this.filter = (e.detail as string).toLowerCase();
   }
 
-  private startTutorial_(e: DomRepeatEvent<FeaturePromoDemoPageInfo>) {
-    const id = e.model.item.internalName;
+  private startTutorial_(e: CustomEvent) {
+    const id = e.detail;
     this.featurePromoErrorMessage_ = '';
 
     this.handler_.startTutorial(id).then(({errorMessage}) => {
@@ -115,8 +120,8 @@
     });
   }
 
-  private showFeaturePromo_(e: DomRepeatEvent<FeaturePromoDemoPageInfo>) {
-    const id = e.model.item.internalName;
+  private showFeaturePromo_(e: CustomEvent) {
+    const id = e.detail;
     this.featurePromoErrorMessage_ = '';
 
     this.handler_.showFeaturePromo(id).then(({errorMessage}) => {
@@ -136,23 +141,6 @@
         promo.supportedPlatforms.find(
             (platform: string) => platform.toLowerCase().includes(filter));
   }
-
-  private showDescription_(promo: FeaturePromoDemoPageInfo) {
-    return promo.displayDescription !== '';
-  }
-
-  private formatItemDate_(promo: FeaturePromoDemoPageInfo) {
-    const date = new Date(Number(promo.addedTimestampMs));
-    return date.toDateString();
-  }
-
-  private formatPlatforms_(promo: FeaturePromoDemoPageInfo) {
-    return promo.supportedPlatforms.join(', ');
-  }
-
-  private showInstructions_(promo: FeaturePromoDemoPageInfo) {
-    return promo.instructions.length;
-  }
 }
 
 customElements.define(
diff --git a/chrome/browser/resources/internals/user_education/user_education_internals_card.html b/chrome/browser/resources/internals/user_education/user_education_internals_card.html
new file mode 100644
index 0000000..4af9c0a
--- /dev/null
+++ b/chrome/browser/resources/internals/user_education/user_education_internals_card.html
@@ -0,0 +1,45 @@
+<style include="cr-hidden-style cr-shared-style">
+:host {
+  border-top: 1px solid var(--cr-separator-color);
+  display: flex;
+  padding-block: var(--cr-section-vertical-padding);
+}
+
+.card-content {
+  flex: 1;
+}
+
+cr-button {
+  flex: 0;
+  margin-block-start: var(--cr-section-vertical-padding);
+  margin-inline-end: var(--cr-section-padding);
+}
+
+ol {
+  padding-inline-start: 2ex;
+}
+
+li {
+  margin-block: 1ex;
+}
+</style>
+<div class="card-content">
+  <h3>[[promo.displayTitle]]</h3>
+  <p>Added on [[formatDate_()]]</p>
+<p hidden$="[[!showDescription_()]]">
+  [[promo.displayDescription]]
+</p>
+<p><b>Type:</b> [[promo.type]]</p>
+<p><b>OS: </b>[[formatPlatforms_()]]</p>
+<div hidden$="[[!showInstructions_()]]">
+  <p><b>Instructions:</b></p>
+  <ol>
+  <template is="dom-repeat" items="[[promo.instructions]]">
+    <li>[[item]]</li>
+  </template>
+  </ol>
+</div>
+</div>
+<cr-button class="action-button" id="launch" on-click="launchPromo_">
+  Launch
+</cr-button>
diff --git a/chrome/browser/resources/internals/user_education/user_education_internals_card.ts b/chrome/browser/resources/internals/user_education/user_education_internals_card.ts
new file mode 100644
index 0000000..e5c812cc
--- /dev/null
+++ b/chrome/browser/resources/internals/user_education/user_education_internals_card.ts
@@ -0,0 +1,59 @@
+// 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 'chrome://resources/cr_elements/cr_button/cr_button.js';
+import 'chrome://resources/cr_elements/cr_hidden_style.css.js';
+import 'chrome://resources/cr_elements/cr_shared_style.css.js';
+import 'chrome://resources/cr_elements/cr_shared_vars.css.js';
+
+import {PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+
+import {FeaturePromoDemoPageInfo} from './user_education_internals.mojom-webui.js';
+import {getTemplate} from './user_education_internals_card.html.js';
+
+const PROMO_LAUNCH_EVENT = 'promo-launch';
+
+class UserEducationInternalsCardElement extends PolymerElement {
+  static get is() {
+    return 'user-education-internals-card';
+  }
+
+  static get template() {
+    return getTemplate();
+  }
+
+  static get properties() {
+    return {
+      promo: Object,
+    };
+  }
+
+  promo: FeaturePromoDemoPageInfo;
+
+  private launchPromo_() {
+    this.dispatchEvent(new CustomEvent(
+        PROMO_LAUNCH_EVENT,
+        {bubbles: true, composed: true, detail: this.promo.internalName}));
+  }
+
+  private showDescription_() {
+    return this.promo.displayDescription !== '';
+  }
+
+  private formatDate_() {
+    const date = new Date(Number(this.promo.addedTimestampMs));
+    return date.toDateString();
+  }
+
+  private formatPlatforms_() {
+    return this.promo.supportedPlatforms.join(', ');
+  }
+
+  private showInstructions_() {
+    return this.promo.instructions.length;
+  }
+}
+
+customElements.define(
+    UserEducationInternalsCardElement.is, UserEducationInternalsCardElement);
diff --git a/chrome/browser/resources/new_tab_page/ts_library_sourcemaps.gni b/chrome/browser/resources/new_tab_page/ts_library_sourcemaps.gni
index 726a7cbf..483689c8 100644
--- a/chrome/browser/resources/new_tab_page/ts_library_sourcemaps.gni
+++ b/chrome/browser/resources/new_tab_page/ts_library_sourcemaps.gni
@@ -31,6 +31,7 @@
       manifest_files = []
       sources = []
       outputs = []
+      out_dir = _out_dir
       foreach(_output, get_target_outputs(":$_ts_library_target_name")) {
         if (get_path_info(_output, "extension") == "manifest") {
           manifest_files += [ _output ]
diff --git a/chrome/browser/resources/side_panel/customize_chrome/categories.html b/chrome/browser/resources/side_panel/customize_chrome/categories.html
index ba8dcb85..47f9c227 100644
--- a/chrome/browser/resources/side_panel/customize_chrome/categories.html
+++ b/chrome/browser/resources/side_panel/customize_chrome/categories.html
@@ -187,7 +187,7 @@
 <cr-grid columns="6" disable-arrow-navigation>
   <div class="tile" tabindex="0" id="classicChromeTile"
       role="button" on-click="onClassicChromeClick_"
-      aria-checked$="[[isClassicChromeSelected_]]">
+      aria-current$="[[boolToString_(isClassicChromeSelected_)]]">
     <customize-chrome-check-mark-wrapper
         checked="[[isClassicChromeSelected_]]">
       <div class="image-container">
@@ -198,7 +198,7 @@
   </div>
   <div class="tile" tabindex="0" id="uploadImageTile"
       role="button" on-click="onUploadImageClick_"
-      aria-checked$="[[isLocalImageSelected_]]">
+      aria-current$="[[boolToString_(isLocalImageSelected_)]]">
     <customize-chrome-check-mark-wrapper
         checked="[[isLocalImageSelected_]]">
       <div class="image-container">
@@ -209,7 +209,7 @@
   </div>
   <div class="tile" tabindex="0" id="chromeColorsTile"
       role="button" on-click="onChromeColorsClick_"
-      aria-checked$="[[isChromeColorsSelected_]]">
+      aria-current$="[[boolToString_(isChromeColorsSelected_)]]">
     <customize-chrome-check-mark-wrapper
         checked="[[isChromeColorsSelected_]]">
       <div class="image-container">
@@ -228,7 +228,8 @@
       on-rendered-item-count-changed="onCollectionsRendered_">
     <div class="tile collection" tabindex="0" role="button"
         on-click="onCollectionClick_"
-        aria-checked$="[[isCollectionSelected_(item.id, selectedCategory_)]]">
+        aria-current$=
+            "[[getCollectionCheckedStatus_(item.id, selectedCategory_)]]">
       <customize-chrome-check-mark-wrapper
           checked="[[isCollectionSelected_(item.id, selectedCategory_)]]">
         <div class="image-container">
diff --git a/chrome/browser/resources/side_panel/customize_chrome/categories.ts b/chrome/browser/resources/side_panel/customize_chrome/categories.ts
index fa02fb9b..00ae021 100644
--- a/chrome/browser/resources/side_panel/customize_chrome/categories.ts
+++ b/chrome/browser/resources/side_panel/customize_chrome/categories.ts
@@ -168,6 +168,14 @@
         this.selectedCategory_.collectionId === id;
   }
 
+  private boolToString_(value: boolean): string {
+    return value ? 'true' : 'false';
+  }
+
+  private getCollectionCheckedStatus_(id: string): string {
+    return this.boolToString_(this.isCollectionSelected_(id));
+  }
+
   private onClassicChromeClick_() {
     this.pageHandler_.setDefaultColor();
     this.pageHandler_.removeBackgroundImage();
diff --git a/chrome/browser/resources/side_panel/customize_chrome/chrome_colors.html b/chrome/browser/resources/side_panel/customize_chrome/chrome_colors.html
index 33e8cbea..cf7aca0 100644
--- a/chrome/browser/resources/side_panel/customize_chrome/chrome_colors.html
+++ b/chrome/browser/resources/side_panel/customize_chrome/chrome_colors.html
@@ -90,7 +90,8 @@
       class="tile"
       title="$i18n{colorPickerLabel}"
       aria-label$="$i18n{colorPickerLabel}"
-      aria-checked$="[[isCustomColorSelected_]]"
+      role="button"
+      aria-current$="[[boolToString_(isCustomColorSelected_)]]"
       tabindex="0"
       on-click="onCustomColorClick_">
     <customize-chrome-color
@@ -108,7 +109,8 @@
       class="tile"
       title="$i18n{defaultColorName}"
       aria-label$="$i18n{defaultColorName}"
-      aria-checked$="[[isDefaultColorSelected_]]"
+      role="button"
+      aria-current$="[[boolToString_(isDefaultColorSelected_)]]"
       tabindex="0"
       on-click="onDefaultColorClick_"
       background-color="[[defaultColor_.background]]"
@@ -120,7 +122,9 @@
         class="chrome-color tile"
         title="[[item.name]]"
         aria-label$="[[item.name]]"
-        aria-checked$="[[isChromeColorSelected_(item.seed, selectedColor_)]]"
+        role="button"
+        aria-current$=
+            "[[getChromeColorCheckedStatus_(item.seed, selectedColor_)]]"
         tabindex="0"
         on-click="onChromeColorClick_"
         background-color="[[item.background]]"
diff --git a/chrome/browser/resources/side_panel/customize_chrome/chrome_colors.ts b/chrome/browser/resources/side_panel/customize_chrome/chrome_colors.ts
index 52dfd54..310761e 100644
--- a/chrome/browser/resources/side_panel/customize_chrome/chrome_colors.ts
+++ b/chrome/browser/resources/side_panel/customize_chrome/chrome_colors.ts
@@ -150,6 +150,14 @@
         this.selectedColor_.chromeColor!.value === color.value;
   }
 
+  private boolToString_(value: boolean): string {
+    return value ? 'true' : 'false';
+  }
+
+  private getChromeColorCheckedStatus_(color: SkColor): string {
+    return this.boolToString_(this.isChromeColorSelected_(color));
+  }
+
   private onBackClick_() {
     this.dispatchEvent(new Event('back-click'));
   }
diff --git a/chrome/browser/resources/side_panel/customize_chrome/themes.html b/chrome/browser/resources/side_panel/customize_chrome/themes.html
index c043273..9bf37c4 100644
--- a/chrome/browser/resources/side_panel/customize_chrome/themes.html
+++ b/chrome/browser/resources/side_panel/customize_chrome/themes.html
@@ -113,7 +113,7 @@
       on-rendered-item-count-changed="onThemesRendered_">
     <div class="tile theme" tabindex="0" role="button"
         on-click="onSelectTheme_" title$="[[item.attribution1]]"
-        aria-checked$="[[isThemeSelected_(item.attribution1, theme_)]]">
+        aria-current$="[[getThemeCheckedStatus_(item.imageUrl.url, theme_)]]">
       <customize-chrome-check-mark-wrapper
           checked="[[isThemeSelected_(item.imageUrl.url, theme_)]]">
         <div class="image-container">
diff --git a/chrome/browser/resources/side_panel/customize_chrome/themes.ts b/chrome/browser/resources/side_panel/customize_chrome/themes.ts
index a903fc04..6c48d2c5 100644
--- a/chrome/browser/resources/side_panel/customize_chrome/themes.ts
+++ b/chrome/browser/resources/side_panel/customize_chrome/themes.ts
@@ -172,6 +172,10 @@
         this.theme_.backgroundImage.url.url === url &&
         !this.isRefreshToggleChecked_;
   }
+
+  private getThemeCheckedStatus_(url: string): string {
+    return this.isThemeSelected_(url) ? 'true' : 'false';
+  }
 }
 
 declare global {
diff --git a/chrome/browser/search/background/ntp_custom_background_service.cc b/chrome/browser/search/background/ntp_custom_background_service.cc
index a398e78..8771406 100644
--- a/chrome/browser/search/background/ntp_custom_background_service.cc
+++ b/chrome/browser/search/background/ntp_custom_background_service.cc
@@ -229,6 +229,10 @@
     const GURL& image_url,
     const gfx::Image& fetched_image,
     const image_fetcher::RequestMetadata& metadata) {
+  if (metadata.http_response_code ==
+      image_fetcher::RequestMetadata::ResponseCode::RESPONSE_CODE_INVALID) {
+    return;
+  }
   // Calculate the bitmap color asynchronously as it is slow (1-2 seconds for
   // the thumbnail). However, prefs should be updated on the main thread.
   base::ThreadPool::PostTaskAndReplyWithResult(
@@ -242,8 +246,6 @@
 void NtpCustomBackgroundService::FetchCustomBackgroundAndExtractBackgroundColor(
     const GURL& image_url,
     const GURL& fetch_url) {
-  DCHECK(!fetch_url.is_empty());
-
   net::NetworkTrafficAnnotationTag traffic_annotation =
       net::DefineNetworkTrafficAnnotation("ntp_custom_background",
                                           R"(
diff --git a/chrome/browser/search/background/ntp_custom_background_service_unittest.cc b/chrome/browser/search/background/ntp_custom_background_service_unittest.cc
index 23bc95d..f0b0859 100644
--- a/chrome/browser/search/background/ntp_custom_background_service_unittest.cc
+++ b/chrome/browser/search/background/ntp_custom_background_service_unittest.cc
@@ -543,7 +543,7 @@
   scoped_feature_list.InitAndEnableFeature(
       ntp_features::kCustomizeChromeColorExtraction);
 
-  EXPECT_CALL(observer_, OnCustomBackgroundImageUpdated).Times(4);
+  EXPECT_CALL(observer_, OnCustomBackgroundImageUpdated).Times(2);
   SkBitmap bitmap;
   bitmap.allocN32Pixels(32, 32);
   bitmap.eraseColor(SK_ColorRED);
@@ -573,9 +573,23 @@
       kUrl, kThumbnailUrl, kAttributionLine1, kAttributionLine2, kActionUrl,
       "");
 
+  image_fetcher::RequestMetadata metadata = image_fetcher::RequestMetadata();
+
+  // Background color will not update if metadata http code invalid.
+  metadata.http_response_code =
+      image_fetcher::RequestMetadata::ResponseCode::RESPONSE_CODE_INVALID;
+  custom_background_service_->UpdateCustomBackgroundColorAsync(kUrl, image,
+                                                               metadata);
+  task_environment_.RunUntilIdle();
+  custom_background = custom_background_service_->GetCustomBackground();
+  EXPECT_NE(
+      SK_ColorRED,
+      custom_background->custom_background_main_color.value_or(SK_ColorWHITE));
+
   // Background color will not update if current background url changed.
+  metadata.http_response_code = 200;
   custom_background_service_->UpdateCustomBackgroundColorAsync(
-      GURL("different_url"), image, image_fetcher::RequestMetadata());
+      GURL("different_url"), image, metadata);
   task_environment_.RunUntilIdle();
   custom_background = custom_background_service_->GetCustomBackground();
   EXPECT_NE(
@@ -583,8 +597,8 @@
       custom_background->custom_background_main_color.value_or(SK_ColorWHITE));
 
   // Background color should update.
-  custom_background_service_->UpdateCustomBackgroundColorAsync(
-      kUrl, image, image_fetcher::RequestMetadata());
+  custom_background_service_->UpdateCustomBackgroundColorAsync(kUrl, image,
+                                                               metadata);
   task_environment_.RunUntilIdle();
   custom_background = custom_background_service_->GetCustomBackground();
   EXPECT_EQ(
diff --git a/chrome/browser/share/android/java/src/org/chromium/chrome/browser/share/ChromeProvidedSharingOptionsProviderBase.java b/chrome/browser/share/android/java/src/org/chromium/chrome/browser/share/ChromeProvidedSharingOptionsProviderBase.java
new file mode 100644
index 0000000..4dda47e7
--- /dev/null
+++ b/chrome/browser/share/android/java/src/org/chromium/chrome/browser/share/ChromeProvidedSharingOptionsProviderBase.java
@@ -0,0 +1,464 @@
+// 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.share;
+
+import android.app.Activity;
+import android.content.ClipData;
+import android.content.ClipboardManager;
+import android.content.Context;
+import android.view.View;
+
+import androidx.annotation.Nullable;
+
+import org.chromium.base.BuildInfo;
+import org.chromium.base.Callback;
+import org.chromium.base.supplier.Supplier;
+import org.chromium.chrome.R;
+import org.chromium.chrome.browser.content_creation.notes.NoteCreationCoordinator;
+import org.chromium.chrome.browser.content_creation.notes.NoteCreationCoordinatorFactory;
+import org.chromium.chrome.browser.flags.ChromeFeatureList;
+import org.chromium.chrome.browser.preferences.Pref;
+import org.chromium.chrome.browser.profiles.Profile;
+import org.chromium.chrome.browser.share.ChromeShareExtras.DetailedContentType;
+import org.chromium.chrome.browser.share.qrcode.QrCodeCoordinator;
+import org.chromium.chrome.browser.share.send_tab_to_self.SendTabToSelfAndroidBridge;
+import org.chromium.chrome.browser.share.send_tab_to_self.SendTabToSelfCoordinator;
+import org.chromium.chrome.browser.share.share_sheet.ChromeOptionShareCallback;
+import org.chromium.chrome.browser.share.share_sheet.ShareSheetPropertyModelBuilder.ContentType;
+import org.chromium.chrome.browser.tab.Tab;
+import org.chromium.components.browser_ui.bottomsheet.BottomSheetController;
+import org.chromium.components.browser_ui.share.ShareImageFileUtils;
+import org.chromium.components.browser_ui.share.ShareParams;
+import org.chromium.components.feature_engagement.EventConstants;
+import org.chromium.components.feature_engagement.Tracker;
+import org.chromium.components.user_prefs.UserPrefs;
+import org.chromium.ui.base.Clipboard;
+import org.chromium.ui.base.WindowAndroid;
+import org.chromium.ui.widget.Toast;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Optional;
+import java.util.Set;
+
+/**
+ * Provides a list of Chrome-provided sharing options.
+ */
+public abstract class ChromeProvidedSharingOptionsProviderBase {
+    private static final String USER_ACTION_COPY_URL_SELECTED = "SharingHubAndroid.CopyURLSelected";
+    private static final String USER_ACTION_COPY_GIF_SELECTED = "SharingHubAndroid.CopyGifSelected";
+    private static final String USER_ACTION_COPY_IMAGE_SELECTED =
+            "SharingHubAndroid.CopyImageSelected";
+    private static final String USER_ACTION_COPY_SELECTED = "SharingHubAndroid.CopySelected";
+    private static final String USER_ACTION_COPY_TEXT_SELECTED =
+            "SharingHubAndroid.CopyTextSelected";
+    private static final String USER_ACTION_SEND_TAB_TO_SELF_SELECTED =
+            "SharingHubAndroid.SendTabToSelfSelected";
+    private static final String USER_ACTION_QR_CODE_SELECTED = "SharingHubAndroid.QRCodeSelected";
+    private static final String USER_ACTION_PRINT_SELECTED = "SharingHubAndroid.PrintSelected";
+    private static final String USER_ACTION_SAVE_IMAGE_SELECTED =
+            "SharingHubAndroid.SaveImageSelected";
+
+    protected static final String USER_ACTION_WEB_STYLE_NOTES_SELECTED =
+            "SharingHubAndroid.WebnotesStylize";
+
+    protected final Activity mActivity;
+    protected final WindowAndroid mWindowAndroid;
+    protected final Supplier<Tab> mTabProvider;
+    protected final BottomSheetController mBottomSheetController;
+    protected final ShareParams mShareParams;
+    protected final Callback<Tab> mPrintTabCallback;
+    protected final boolean mIsIncognito;
+    protected final List<FirstPartyOption> mOrderedFirstPartyOptions;
+    protected final ChromeOptionShareCallback mChromeOptionShareCallback;
+    protected final String mUrl;
+    protected final Tracker mFeatureEngagementTracker;
+    protected final Profile mProfile;
+
+    /**
+     * Constructs a new {@link ChromeProvidedSharingOptionsProviderBase}.
+     *
+     * @param activity The current {@link Activity}.
+     * @param windowAndroid The current window.
+     * @param tabProvider Supplier for the current activity tab.
+     * @param bottomSheetController The {@link BottomSheetController} for the current activity.
+     * @param shareParams The {@link ShareParams} for the current share.
+     * @param printTab A {@link Callback} that will print a given Tab.
+     * @param isIncognito Whether incognito mode is enabled.
+     * @param chromeOptionShareCallback A ChromeOptionShareCallback that can be used by
+     * Chrome-provided sharing options.
+     * @param featureEngagementTracker feature engagement tracker.
+     * @param url Url to share.
+     * @param profile The current profile of the User.
+     */
+    protected ChromeProvidedSharingOptionsProviderBase(Activity activity,
+            WindowAndroid windowAndroid, Supplier<Tab> tabProvider,
+            BottomSheetController bottomSheetController, ShareParams shareParams,
+            Callback<Tab> printTab, boolean isIncognito,
+            ChromeOptionShareCallback chromeOptionShareCallback, Tracker featureEngagementTracker,
+            String url, Profile profile) {
+        mActivity = activity;
+        mWindowAndroid = windowAndroid;
+        mTabProvider = tabProvider;
+        mBottomSheetController = bottomSheetController;
+        mShareParams = shareParams;
+        mPrintTabCallback = printTab;
+        mIsIncognito = isIncognito;
+        mFeatureEngagementTracker = featureEngagementTracker;
+        mChromeOptionShareCallback = chromeOptionShareCallback;
+        mUrl = url;
+        mProfile = profile;
+
+        mOrderedFirstPartyOptions = new ArrayList<>();
+        initializeFirstPartyOptionsInOrder();
+    }
+
+    /**
+     * Data structure carries details on how a first party option should be used.
+     */
+    protected static class FirstPartyOption {
+        public final int icon;
+        public final int iconLabel;
+        public final String iconContentDescription;
+        public final String featureNameForMetrics;
+        public final Callback<View> onClickCallback;
+        public final Collection<Integer> contentTypes;
+        public final Collection<Integer> contentTypesToDisableFor;
+        public final Collection<Integer> detailedContentTypesToDisableFor;
+        public final boolean disableForMultiWindow;
+
+        private FirstPartyOption(int icon, int iconLabel, String iconContentDescription,
+                String featureNameForMetrics, Callback<View> onClickCallback,
+                Collection<Integer> contentTypes, Collection<Integer> contentTypesToDisableFor,
+                Collection<Integer> detailedContentTypesToDisableFor,
+                boolean disableForMultiWindow) {
+            this.icon = icon;
+            this.iconLabel = iconLabel;
+            this.iconContentDescription = iconContentDescription;
+            this.featureNameForMetrics = featureNameForMetrics;
+            this.onClickCallback = onClickCallback;
+            this.contentTypes = contentTypes;
+            this.contentTypesToDisableFor = contentTypesToDisableFor;
+            this.detailedContentTypesToDisableFor = detailedContentTypesToDisableFor;
+            this.disableForMultiWindow = disableForMultiWindow;
+        }
+    }
+
+    protected static class FirstPartyOptionBuilder {
+        private int mIcon;
+        private int mIconLabel;
+        private String mIconContentDescription;
+        private String mFeatureNameForMetrics;
+        private Callback<View> mOnClickCallback;
+        private boolean mDisableForMultiWindow;
+        private Integer[] mContentTypesToDisableFor;
+        private Integer[] mDetailedContentTypesToDisableFor;
+        private final Integer[] mContentTypesInBuilder;
+
+        public FirstPartyOptionBuilder(Integer... contentTypes) {
+            mContentTypesInBuilder = contentTypes;
+            mContentTypesToDisableFor = new Integer[] {};
+            mDetailedContentTypesToDisableFor = new Integer[] {};
+        }
+
+        public FirstPartyOptionBuilder setIcon(int icon, int iconLabel) {
+            mIcon = icon;
+            mIconLabel = iconLabel;
+            return this;
+        }
+
+        public FirstPartyOptionBuilder setIconContentDescription(String iconContentDescription) {
+            mIconContentDescription = iconContentDescription;
+            return this;
+        }
+
+        public FirstPartyOptionBuilder setFeatureNameForMetrics(String featureName) {
+            mFeatureNameForMetrics = featureName;
+            return this;
+        }
+
+        public FirstPartyOptionBuilder setOnClickCallback(Callback<View> onClickCallback) {
+            mOnClickCallback = onClickCallback;
+            return this;
+        }
+
+        public FirstPartyOptionBuilder setContentTypesToDisableFor(
+                Integer... contentTypesToDisableFor) {
+            mContentTypesToDisableFor = contentTypesToDisableFor;
+            return this;
+        }
+
+        public FirstPartyOptionBuilder setDetailedContentTypesToDisableFor(
+                Integer... detailedContentTypesToDisableFor) {
+            mDetailedContentTypesToDisableFor = detailedContentTypesToDisableFor;
+            return this;
+        }
+
+        public FirstPartyOptionBuilder setDisableForMultiWindow(boolean disableForMultiWindow) {
+            mDisableForMultiWindow = disableForMultiWindow;
+            return this;
+        }
+
+        public FirstPartyOption build() {
+            assert mOnClickCallback != null;
+            return new FirstPartyOption(mIcon, mIconLabel, mIconContentDescription,
+                    mFeatureNameForMetrics, mOnClickCallback, Arrays.asList(mContentTypesInBuilder),
+                    Arrays.asList(mContentTypesToDisableFor),
+                    Arrays.asList(mDetailedContentTypesToDisableFor), mDisableForMultiWindow);
+        }
+    }
+
+    /**
+     * Returns an ordered list of {@link FirstPartyOption}s that should be shown given the {@code
+     * contentTypes} being shared.
+     *
+     * @param contentTypes a {@link Set} of {@link ContentType}.
+     * @param detailedContentType the {@link DetailedContentType} being shared.
+     * @param isMultiWindow if in multi-window mode.
+     * @return a list of {@link FirstPartyOption}s.
+     */
+    protected List<FirstPartyOption> getFirstPartyOptions(Set<Integer> contentTypes,
+            @DetailedContentType int detailedContentType, boolean isMultiWindow) {
+        List<FirstPartyOption> availableOptions = new ArrayList<>();
+        for (FirstPartyOption firstPartyOption : mOrderedFirstPartyOptions) {
+            if (!Collections.disjoint(contentTypes, firstPartyOption.contentTypes)
+                    && Collections.disjoint(contentTypes, firstPartyOption.contentTypesToDisableFor)
+                    && !firstPartyOption.detailedContentTypesToDisableFor.contains(
+                            detailedContentType)
+                    && !(isMultiWindow && firstPartyOption.disableForMultiWindow)) {
+                availableOptions.add(firstPartyOption);
+            }
+        }
+        return availableOptions;
+    }
+
+    /**
+     * Creates all enabled {@link FirstPartyOption}s and adds them to {@code
+     * mOrderedFirstPartyOptions} in the order they should appear.
+     */
+    private void initializeFirstPartyOptionsInOrder() {
+        // Only show a limited first party share selection for automotive
+        if (BuildInfo.getInstance().isAutomotive) {
+            mOrderedFirstPartyOptions.add(createCopyLinkFirstPartyOption());
+            maybeAddSendTabToSelfFirstPartyOption();
+            maybeAddQrCodeFirstPartyOption();
+            return;
+        }
+        if (ChromeFeatureList.isEnabled(ChromeFeatureList.WEBNOTES_STYLIZE)) {
+            mOrderedFirstPartyOptions.add(createWebNotesStylizeFirstPartyOption());
+        }
+        maybeAddScreenshotFirstPartyOption();
+        maybeAddLongScreenshotFirstPartyOption();
+
+        mOrderedFirstPartyOptions.add(createCopyLinkFirstPartyOption());
+        mOrderedFirstPartyOptions.add(createCopyGifFirstPartyOption());
+        mOrderedFirstPartyOptions.add(createCopyImageFirstPartyOption());
+        mOrderedFirstPartyOptions.add(createCopyFirstPartyOption());
+        mOrderedFirstPartyOptions.add(createCopyTextFirstPartyOption());
+        maybeAddSendTabToSelfFirstPartyOption();
+        maybeAddQrCodeFirstPartyOption();
+        if (mTabProvider.hasValue() && UserPrefs.get(mProfile).getBoolean(Pref.PRINTING_ENABLED)) {
+            mOrderedFirstPartyOptions.add(createPrintingFirstPartyOption());
+        }
+        mOrderedFirstPartyOptions.add(createSaveImageFirstPartyOption());
+    }
+
+    private void maybeAddSendTabToSelfFirstPartyOption() {
+        Optional<Integer> sendTabToSelfDisplayReason =
+                SendTabToSelfAndroidBridge.getEntryPointDisplayReason(mProfile, mUrl);
+        if (sendTabToSelfDisplayReason.isPresent()
+                || !ChromeFeatureList.isEnabled(ChromeFeatureList.SEND_TAB_TO_SELF_SIGNIN_PROMO)) {
+            mOrderedFirstPartyOptions.add(createSendTabToSelfFirstPartyOption());
+        }
+    }
+
+    private void maybeAddQrCodeFirstPartyOption() {
+        if (!mIsIncognito) {
+            mOrderedFirstPartyOptions.add(createQrCodeFirstPartyOption());
+        }
+    }
+
+    private void maybeAddScreenshotFirstPartyOption() {
+        FirstPartyOption option = createScreenshotFirstPartyOption();
+        if (option != null) {
+            mOrderedFirstPartyOptions.add(option);
+        }
+    }
+
+    private void maybeAddLongScreenshotFirstPartyOption() {
+        // TODO(crbug.com/1250871): Long Screenshots on by default; supported on Android 7.0+.
+        if (!(ChromeFeatureList.isEnabled(ChromeFeatureList.CHROME_SHARE_LONG_SCREENSHOT)
+                    && mTabProvider.hasValue())) {
+            return;
+        }
+        FirstPartyOption option = createLongScreenshotsFirstPartyOption();
+        if (option != null) {
+            mOrderedFirstPartyOptions.add(option);
+        }
+    }
+
+    private FirstPartyOption createCopyLinkFirstPartyOption() {
+        return new FirstPartyOptionBuilder(
+                ContentType.LINK_PAGE_VISIBLE, ContentType.LINK_PAGE_NOT_VISIBLE)
+                .setContentTypesToDisableFor(ContentType.LINK_AND_TEXT)
+                .setIcon(R.drawable.ic_content_copy_black, R.string.sharing_copy_url)
+                .setFeatureNameForMetrics(USER_ACTION_COPY_URL_SELECTED)
+                .setOnClickCallback((view) -> {
+                    ClipboardManager clipboard = (ClipboardManager) mActivity.getSystemService(
+                            Context.CLIPBOARD_SERVICE);
+                    clipboard.setPrimaryClip(
+                            ClipData.newPlainText(mShareParams.getTitle(), mShareParams.getUrl()));
+                    Toast.makeText(mActivity, R.string.link_copied, Toast.LENGTH_SHORT).show();
+                })
+                .build();
+    }
+
+    private FirstPartyOption createCopyGifFirstPartyOption() {
+        return new FirstPartyOptionBuilder(ContentType.IMAGE, ContentType.IMAGE_AND_LINK)
+                .setIcon(R.drawable.ic_content_copy_black, R.string.sharing_copy_gif)
+                // Enables only for GIF.
+                .setDetailedContentTypesToDisableFor(DetailedContentType.IMAGE,
+                        DetailedContentType.WEB_NOTES, DetailedContentType.NOT_SPECIFIED)
+                .setFeatureNameForMetrics(USER_ACTION_COPY_GIF_SELECTED)
+                .setOnClickCallback((view) -> {
+                    if (!mShareParams.getFileUris().isEmpty()) {
+                        Clipboard.getInstance().setImageUri(mShareParams.getFileUris().get(0));
+                        Toast.makeText(mActivity, R.string.gif_copied, Toast.LENGTH_SHORT).show();
+                    }
+                })
+                .build();
+    }
+
+    private FirstPartyOption createCopyImageFirstPartyOption() {
+        return new FirstPartyOptionBuilder(ContentType.IMAGE, ContentType.IMAGE_AND_LINK)
+                .setIcon(R.drawable.ic_content_copy_black, R.string.sharing_copy_image)
+                .setFeatureNameForMetrics(USER_ACTION_COPY_IMAGE_SELECTED)
+                .setDetailedContentTypesToDisableFor(DetailedContentType.GIF)
+                .setOnClickCallback((view) -> {
+                    if (!mShareParams.getFileUris().isEmpty()) {
+                        Clipboard.getInstance().setImageUri(mShareParams.getFileUris().get(0));
+                        Toast.makeText(mActivity, R.string.image_copied, Toast.LENGTH_SHORT).show();
+                    }
+                })
+                .build();
+    }
+
+    private FirstPartyOption createCopyFirstPartyOption() {
+        return new FirstPartyOptionBuilder(ContentType.LINK_AND_TEXT)
+                .setIcon(R.drawable.ic_content_copy_black, R.string.sharing_copy)
+                .setFeatureNameForMetrics(USER_ACTION_COPY_SELECTED)
+                .setOnClickCallback((view) -> {
+                    ClipboardManager clipboard = (ClipboardManager) mActivity.getSystemService(
+                            Context.CLIPBOARD_SERVICE);
+                    clipboard.setPrimaryClip(ClipData.newPlainText(
+                            mShareParams.getTitle(), mShareParams.getTextAndUrl()));
+                    Toast.makeText(mActivity, R.string.copied, Toast.LENGTH_SHORT).show();
+                })
+                .build();
+    }
+
+    private FirstPartyOption createCopyTextFirstPartyOption() {
+        return new FirstPartyOptionBuilder(ContentType.TEXT, ContentType.HIGHLIGHTED_TEXT)
+                .setContentTypesToDisableFor(ContentType.LINK_AND_TEXT)
+                .setIcon(R.drawable.ic_content_copy_black, R.string.sharing_copy_text)
+                .setFeatureNameForMetrics(USER_ACTION_COPY_TEXT_SELECTED)
+                .setOnClickCallback((view) -> {
+                    ClipboardManager clipboard = (ClipboardManager) mActivity.getSystemService(
+                            Context.CLIPBOARD_SERVICE);
+                    clipboard.setPrimaryClip(
+                            ClipData.newPlainText(mShareParams.getTitle(), mShareParams.getText()));
+                    Toast.makeText(mActivity, R.string.text_copied, Toast.LENGTH_SHORT).show();
+                })
+                .build();
+    }
+
+    private FirstPartyOption createSendTabToSelfFirstPartyOption() {
+        return new FirstPartyOptionBuilder(
+                ContentType.LINK_PAGE_VISIBLE, ContentType.LINK_PAGE_NOT_VISIBLE, ContentType.IMAGE)
+                .setDetailedContentTypesToDisableFor(DetailedContentType.WEB_NOTES)
+                .setIcon(R.drawable.send_tab, R.string.send_tab_to_self_share_activity_title)
+                .setFeatureNameForMetrics(USER_ACTION_SEND_TAB_TO_SELF_SELECTED)
+                .setOnClickCallback((view) -> {
+                    SendTabToSelfCoordinator sttsCoordinator =
+                            new SendTabToSelfCoordinator(mActivity, mWindowAndroid, mUrl,
+                                    mShareParams.getTitle(), mBottomSheetController, mProfile);
+                    sttsCoordinator.show();
+                })
+                .build();
+    }
+
+    private FirstPartyOption createQrCodeFirstPartyOption() {
+        return new FirstPartyOptionBuilder(
+                ContentType.LINK_PAGE_VISIBLE, ContentType.LINK_PAGE_NOT_VISIBLE, ContentType.IMAGE)
+                .setDetailedContentTypesToDisableFor(DetailedContentType.WEB_NOTES)
+                .setIcon(R.drawable.qr_code, R.string.qr_code_share_icon_label)
+                .setFeatureNameForMetrics(USER_ACTION_QR_CODE_SELECTED)
+                .setOnClickCallback((view) -> {
+                    QrCodeCoordinator qrCodeCoordinator =
+                            new QrCodeCoordinator(mActivity, mUrl, mShareParams.getWindow());
+                    qrCodeCoordinator.show();
+                })
+                .build();
+    }
+
+    private FirstPartyOption createPrintingFirstPartyOption() {
+        return new FirstPartyOptionBuilder(ContentType.LINK_PAGE_VISIBLE)
+                .setIcon(R.drawable.sharing_print, R.string.print_share_activity_title)
+                .setFeatureNameForMetrics(USER_ACTION_PRINT_SELECTED)
+                .setOnClickCallback((view) -> { mPrintTabCallback.onResult(mTabProvider.get()); })
+                .build();
+    }
+
+    private FirstPartyOption createSaveImageFirstPartyOption() {
+        return new FirstPartyOptionBuilder(ContentType.IMAGE, ContentType.IMAGE_AND_LINK)
+                .setIcon(R.drawable.save_to_device, R.string.sharing_save_image)
+                .setFeatureNameForMetrics(USER_ACTION_SAVE_IMAGE_SELECTED)
+                .setOnClickCallback((view) -> {
+                    if (mShareParams.getFileUris().isEmpty()) return;
+
+                    ShareImageFileUtils.getBitmapFromUriAsync(
+                            mActivity, mShareParams.getFileUris().get(0), (bitmap) -> {
+                                SaveBitmapDelegate saveBitmapDelegate = new SaveBitmapDelegate(
+                                        mActivity, bitmap, R.string.save_image_filename_prefix,
+                                        null, mShareParams.getWindow());
+                                saveBitmapDelegate.save();
+                            });
+                })
+                .build();
+    }
+
+    protected FirstPartyOption createWebNotesStylizeFirstPartyOption() {
+        String title = mShareParams.getTitle();
+        return new FirstPartyOptionBuilder(ContentType.HIGHLIGHTED_TEXT)
+                .setIcon(R.drawable.webnote, R.string.sharing_webnotes_create_card)
+                .setIconContentDescription(
+                        mActivity.getString(R.string.sharing_webnotes_accessibility_description))
+                .setFeatureNameForMetrics(USER_ACTION_WEB_STYLE_NOTES_SELECTED)
+                .setOnClickCallback((view) -> {
+                    mFeatureEngagementTracker.notifyEvent(
+                            EventConstants.SHARING_HUB_WEBNOTES_STYLIZE_USED);
+                    NoteCreationCoordinator coordinator = NoteCreationCoordinatorFactory.create(
+                            mActivity, mShareParams.getWindow(), mUrl, title,
+                            mShareParams.getRawText().trim(), mChromeOptionShareCallback);
+                    coordinator.showDialog();
+                })
+                .build();
+    }
+
+    /**
+     * Create a {@link FirstPartyOption} used to do screenshot. Return null if not supported.
+     */
+    @Nullable
+    protected abstract FirstPartyOption createScreenshotFirstPartyOption();
+
+    /**
+     * Create a {@link FirstPartyOption} used to do long screenshot. Return null if not supported.
+     */
+    @Nullable
+    protected abstract FirstPartyOption createLongScreenshotsFirstPartyOption();
+}
diff --git a/chrome/browser/share/android/java/src/org/chromium/chrome/browser/share/share_sheet/ChromeProvidedSharingOptionsProvider.java b/chrome/browser/share/android/java/src/org/chromium/chrome/browser/share/share_sheet/ChromeProvidedSharingOptionsProvider.java
index 4e9bc7f..1153ef2 100644
--- a/chrome/browser/share/android/java/src/org/chromium/chrome/browser/share/share_sheet/ChromeProvidedSharingOptionsProvider.java
+++ b/chrome/browser/share/android/java/src/org/chromium/chrome/browser/share/share_sheet/ChromeProvidedSharingOptionsProvider.java
@@ -5,80 +5,53 @@
 package org.chromium.chrome.browser.share.share_sheet;
 
 import android.app.Activity;
-import android.content.ClipData;
-import android.content.ClipboardManager;
 import android.content.ComponentName;
-import android.content.Context;
-import android.view.View;
 
 import androidx.appcompat.content.res.AppCompatResources;
 
-import org.chromium.base.BuildInfo;
 import org.chromium.base.Callback;
 import org.chromium.base.supplier.Supplier;
 import org.chromium.chrome.R;
-import org.chromium.chrome.browser.content_creation.notes.NoteCreationCoordinator;
-import org.chromium.chrome.browser.content_creation.notes.NoteCreationCoordinatorFactory;
-import org.chromium.chrome.browser.flags.ChromeFeatureList;
-import org.chromium.chrome.browser.preferences.Pref;
 import org.chromium.chrome.browser.profiles.Profile;
+import org.chromium.chrome.browser.share.ChromeProvidedSharingOptionsProviderBase;
 import org.chromium.chrome.browser.share.ChromeShareExtras.DetailedContentType;
-import org.chromium.chrome.browser.share.SaveBitmapDelegate;
 import org.chromium.chrome.browser.share.link_to_text.LinkToTextCoordinator.LinkGeneration;
 import org.chromium.chrome.browser.share.long_screenshots.LongScreenshotsCoordinator;
-import org.chromium.chrome.browser.share.qrcode.QrCodeCoordinator;
 import org.chromium.chrome.browser.share.screenshot.ScreenshotCoordinator;
-import org.chromium.chrome.browser.share.send_tab_to_self.SendTabToSelfAndroidBridge;
-import org.chromium.chrome.browser.share.send_tab_to_self.SendTabToSelfCoordinator;
 import org.chromium.chrome.browser.share.share_sheet.ShareSheetLinkToggleMetricsHelper.LinkToggleMetricsDetails;
 import org.chromium.chrome.browser.share.share_sheet.ShareSheetPropertyModelBuilder.ContentType;
 import org.chromium.chrome.browser.tab.Tab;
 import org.chromium.chrome.modules.image_editor.ImageEditorModuleProvider;
 import org.chromium.components.browser_ui.bottomsheet.BottomSheetController;
-import org.chromium.components.browser_ui.share.ShareImageFileUtils;
 import org.chromium.components.browser_ui.share.ShareParams;
 import org.chromium.components.feature_engagement.EventConstants;
 import org.chromium.components.feature_engagement.FeatureConstants;
 import org.chromium.components.feature_engagement.Tracker;
-import org.chromium.components.user_prefs.UserPrefs;
-import org.chromium.ui.base.Clipboard;
 import org.chromium.ui.base.WindowAndroid;
 import org.chromium.ui.modelutil.PropertyModel;
-import org.chromium.ui.widget.Toast;
 
 import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.Collections;
 import java.util.List;
-import java.util.Optional;
 import java.util.Set;
 
 /**
  * Provides {@code PropertyModel}s of Chrome-provided sharing options.
  */
-public class ChromeProvidedSharingOptionsProvider {
+public class ChromeProvidedSharingOptionsProvider extends ChromeProvidedSharingOptionsProviderBase {
     // ComponentName used for Chrome share options in ShareParams.TargetChosenCallback
     public static final ComponentName CHROME_PROVIDED_FEATURE_COMPONENT_NAME =
             new ComponentName("CHROME", "CHROME_FEATURE");
 
-    private final Activity mActivity;
-    private final WindowAndroid mWindowAndroid;
-    private final Supplier<Tab> mTabProvider;
-    private final BottomSheetController mBottomSheetController;
+    private static final String USER_ACTION_SCREENSHOT_SELECTED =
+            "SharingHubAndroid.ScreenshotSelected";
+    private static final String USER_ACTION_LONG_SCREENSHOT_SELECTED =
+            "SharingHubAndroid.LongScreenshotSelected";
+
     private final ShareSheetBottomSheetContent mBottomSheetContent;
-    private final ShareParams mShareParams;
-    private final Callback<Tab> mPrintTabCallback;
-    private final boolean mIsIncognito;
     private final long mShareStartTime;
-    private final List<FirstPartyOption> mOrderedFirstPartyOptions;
-    private final ChromeOptionShareCallback mChromeOptionShareCallback;
-    private final String mUrl;
     private final ImageEditorModuleProvider mImageEditorModuleProvider;
-    private final Tracker mFeatureEngagementTracker;
     private final @LinkGeneration int mLinkGenerationStatusForMetrics;
     private final LinkToggleMetricsDetails mLinkToggleMetricsDetails;
-    private final Profile mProfile;
 
     /**
      * Constructs a new {@link ChromeProvidedSharingOptionsProvider}.
@@ -112,143 +85,13 @@
             ImageEditorModuleProvider imageEditorModuleProvider, Tracker featureEngagementTracker,
             String url, @LinkGeneration int linkGenerationStatusForMetrics,
             LinkToggleMetricsDetails linkToggleMetricsDetails, Profile profile) {
-        mActivity = activity;
-        mWindowAndroid = windowAndroid;
-        mTabProvider = tabProvider;
-        mBottomSheetController = bottomSheetController;
+        super(activity, windowAndroid, tabProvider, bottomSheetController, shareParams, printTab,
+                isIncognito, chromeOptionShareCallback, featureEngagementTracker, url, profile);
         mBottomSheetContent = bottomSheetContent;
-        mShareParams = shareParams;
-        mPrintTabCallback = printTab;
-        mIsIncognito = isIncognito;
         mShareStartTime = shareStartTime;
         mImageEditorModuleProvider = imageEditorModuleProvider;
-        mFeatureEngagementTracker = featureEngagementTracker;
-        mChromeOptionShareCallback = chromeOptionShareCallback;
-        mUrl = url;
         mLinkGenerationStatusForMetrics = linkGenerationStatusForMetrics;
         mLinkToggleMetricsDetails = linkToggleMetricsDetails;
-        mProfile = profile;
-        mOrderedFirstPartyOptions = new ArrayList<>();
-        initializeFirstPartyOptionsInOrder();
-    }
-
-    /**
-     * Encapsulates a {@link PropertyModel} and the {@link ContentType}s it should be shown for.
-     */
-    private static class FirstPartyOption {
-        final Collection<Integer> mContentTypes;
-        final Collection<Integer> mContentTypesToDisableFor;
-        final Collection<Integer> mDetailedContentTypesToDisableFor;
-        final PropertyModel mPropertyModel;
-        final boolean mDisableForMultiWindow;
-
-        /**
-         * Should only be used when the default property model constructed in the builder does not
-         * fit the feature's needs. This should be rare.
-         *
-         * @param model Property model for the first party option.
-         * @param contentTypes Content types to trigger for.
-         * @param contentTypesToDisableFor Content types to disable for.
-         * @param detailedContentTypesToDisableFor {@link DetailedContentType}s to disable for.
-         * @param disableForMultiWindow If the feature should be disabled if in multi-window mode.
-         */
-        FirstPartyOption(PropertyModel model, Collection<Integer> contentTypes,
-                Collection<Integer> contentTypesToDisableFor,
-                Collection<Integer> detailedContentTypesToDisableFor,
-                boolean disableForMultiWindow) {
-            mPropertyModel = model;
-            mContentTypes = contentTypes;
-            mContentTypesToDisableFor = contentTypesToDisableFor;
-            mDetailedContentTypesToDisableFor = detailedContentTypesToDisableFor;
-            mDisableForMultiWindow = disableForMultiWindow;
-        }
-    }
-
-    private class FirstPartyOptionBuilder {
-        private int mIcon;
-        private int mIconLabel;
-        private String mIconContentDescription;
-        private String mFeatureNameForMetrics;
-        private Callback<View> mOnClickCallback;
-        private boolean mDisableForMultiWindow;
-        private Integer[] mContentTypesToDisableFor;
-        private Integer[] mDetailedContentTypesToDisableFor;
-        private final Integer[] mContentTypesInBuilder;
-        private boolean mShowNewBadge;
-        private boolean mHideBottomSheetContentOnTap = true;
-
-        FirstPartyOptionBuilder(Integer... contentTypes) {
-            mContentTypesInBuilder = contentTypes;
-            mContentTypesToDisableFor = new Integer[] {};
-            mDetailedContentTypesToDisableFor = new Integer[] {};
-        }
-
-        FirstPartyOptionBuilder setIcon(int icon, int iconLabel) {
-            mIcon = icon;
-            mIconLabel = iconLabel;
-            return this;
-        }
-
-        FirstPartyOptionBuilder setIconContentDescription(int iconContentDescription) {
-            mIconContentDescription = mActivity.getResources().getString(iconContentDescription);
-            return this;
-        }
-
-        FirstPartyOptionBuilder setFeatureNameForMetrics(String featureName) {
-            mFeatureNameForMetrics = featureName;
-            return this;
-        }
-
-        FirstPartyOptionBuilder setOnClickCallback(Callback<View> onClickCallback) {
-            mOnClickCallback = onClickCallback;
-            return this;
-        }
-
-        FirstPartyOptionBuilder setContentTypesToDisableFor(Integer... contentTypesToDisableFor) {
-            mContentTypesToDisableFor = contentTypesToDisableFor;
-            return this;
-        }
-
-        FirstPartyOptionBuilder setDetailedContentTypesToDisableFor(
-                Integer... detailedContentTypesToDisableFor) {
-            mDetailedContentTypesToDisableFor = detailedContentTypesToDisableFor;
-            return this;
-        }
-
-        FirstPartyOptionBuilder setDisableForMultiWindow(boolean disableForMultiWindow) {
-            mDisableForMultiWindow = disableForMultiWindow;
-            return this;
-        }
-
-        FirstPartyOptionBuilder setShowNewBadge(boolean showNewBadge) {
-            mShowNewBadge = showNewBadge;
-            return this;
-        }
-
-        FirstPartyOptionBuilder setHideBottomSheetContentOnTap(
-                boolean hideBottomSheetContentOnTap) {
-            mHideBottomSheetContentOnTap = hideBottomSheetContentOnTap;
-            return this;
-        }
-
-        FirstPartyOption build() {
-            PropertyModel model = ShareSheetPropertyModelBuilder.createPropertyModel(
-                    AppCompatResources.getDrawable(mActivity, mIcon),
-                    mActivity.getResources().getString(mIconLabel),
-                    mIconContentDescription, (view) -> {
-                        ShareSheetCoordinator.recordShareMetrics(mFeatureNameForMetrics,
-                                mLinkGenerationStatusForMetrics, mLinkToggleMetricsDetails,
-                                mShareStartTime, mProfile);
-                        if (mHideBottomSheetContentOnTap) {
-                            mBottomSheetController.hideContent(mBottomSheetContent, true);
-                        }
-                        mOnClickCallback.onResult(view);
-                        callTargetChosenCallback();
-                    }, mShowNewBadge);
-            return new FirstPartyOption(model, Arrays.asList(mContentTypesInBuilder),
-                    Arrays.asList(mContentTypesToDisableFor),
-                    Arrays.asList(mDetailedContentTypesToDisableFor), mDisableForMultiWindow);
-        }
     }
 
     /**
@@ -263,84 +106,63 @@
     List<PropertyModel> getPropertyModels(Set<Integer> contentTypes,
             @DetailedContentType int detailedContentType, boolean isMultiWindow) {
         List<PropertyModel> propertyModels = new ArrayList<>();
-        for (FirstPartyOption firstPartyOption : mOrderedFirstPartyOptions) {
-            if (!Collections.disjoint(contentTypes, firstPartyOption.mContentTypes)
-                    && Collections.disjoint(
-                            contentTypes, firstPartyOption.mContentTypesToDisableFor)
-                    && !firstPartyOption.mDetailedContentTypesToDisableFor.contains(
-                            detailedContentType)
-                    && !(isMultiWindow && firstPartyOption.mDisableForMultiWindow)) {
-                propertyModels.add(firstPartyOption.mPropertyModel);
-            }
+        for (FirstPartyOption firstPartyOption :
+                getFirstPartyOptions(contentTypes, detailedContentType, isMultiWindow)) {
+            propertyModels.add(getShareSheetModel(firstPartyOption));
         }
         return propertyModels;
     }
 
-    /**
-     * Creates all enabled {@link FirstPartyOption}s and adds them to {@code
-     * mOrderedFirstPartyOptions} in the order they should appear.
-     */
-    private void initializeFirstPartyOptionsInOrder() {
-        boolean enableAllUpcomingSharingFeatures =
-                ChromeFeatureList.isEnabled(ChromeFeatureList.UPCOMING_SHARING_FEATURES);
+    private PropertyModel getShareSheetModel(FirstPartyOption option) {
+        boolean setShowNewBadge = showNewBadge(option);
+        boolean hideBottomSheetContentOnTap = hideBottomSheetContentOnTap(option);
 
-        // Only show a limited first party share selection for automotive
-        if (BuildInfo.getInstance().isAutomotive) {
-            mOrderedFirstPartyOptions.add(createCopyLinkFirstPartyOption());
-            maybeAddSendTabToSelfFirstPartyOption();
-            maybeAddQrCodeFirstPartyOption();
-            return;
-        }
-        if (ChromeFeatureList.isEnabled(ChromeFeatureList.WEBNOTES_STYLIZE)) {
-            mOrderedFirstPartyOptions.add(createWebNotesStylizeFirstPartyOption());
-        }
-        mOrderedFirstPartyOptions.add(createScreenshotFirstPartyOption());
-
-        // TODO(crbug.com/1250871): Long Screenshots on by default; supported on Android 7.0+.
-        if (ChromeFeatureList.isEnabled(ChromeFeatureList.CHROME_SHARE_LONG_SCREENSHOT)
-                && mTabProvider.hasValue()) {
-            mOrderedFirstPartyOptions.add(createLongScreenshotsFirstPartyOption());
-        }
-        mOrderedFirstPartyOptions.add(createCopyLinkFirstPartyOption());
-        mOrderedFirstPartyOptions.add(createCopyGifFirstPartyOption());
-        mOrderedFirstPartyOptions.add(createCopyImageFirstPartyOption());
-        mOrderedFirstPartyOptions.add(createCopyFirstPartyOption());
-        mOrderedFirstPartyOptions.add(createCopyTextFirstPartyOption());
-        maybeAddSendTabToSelfFirstPartyOption();
-        maybeAddQrCodeFirstPartyOption();
-        if (mTabProvider.hasValue() && UserPrefs.get(mProfile).getBoolean(Pref.PRINTING_ENABLED)) {
-            mOrderedFirstPartyOptions.add(createPrintingFirstPartyOption());
-        }
-        mOrderedFirstPartyOptions.add(createSaveImageFirstPartyOption());
+        return ShareSheetPropertyModelBuilder.createPropertyModel(
+                AppCompatResources.getDrawable(mActivity, option.icon),
+                mActivity.getResources().getString(option.iconLabel),
+                option.iconContentDescription, (view) -> {
+                    ShareSheetCoordinator.recordShareMetrics(option.featureNameForMetrics,
+                            mLinkGenerationStatusForMetrics, mLinkToggleMetricsDetails,
+                            mShareStartTime, mProfile);
+                    if (hideBottomSheetContentOnTap) {
+                        mBottomSheetController.hideContent(mBottomSheetContent, true);
+                    }
+                    option.onClickCallback.onResult(view);
+                    callTargetChosenCallback();
+                }, setShowNewBadge);
     }
 
-    private void maybeAddSendTabToSelfFirstPartyOption() {
-        Optional<Integer> sendTabToSelfDisplayReason =
-                SendTabToSelfAndroidBridge.getEntryPointDisplayReason(mProfile, mUrl);
-        if (sendTabToSelfDisplayReason.isPresent()
-                || !ChromeFeatureList.isEnabled(ChromeFeatureList.SEND_TAB_TO_SELF_SIGNIN_PROMO)) {
-            mOrderedFirstPartyOptions.add(createSendTabToSelfFirstPartyOption());
+    private boolean showNewBadge(FirstPartyOption firstPartyOption) {
+        if (!mFeatureEngagementTracker.isInitialized()) return false;
+
+        if (USER_ACTION_SCREENSHOT_SELECTED.equals(firstPartyOption.featureNameForMetrics)) {
+            return mFeatureEngagementTracker.shouldTriggerHelpUI(
+                    FeatureConstants.IPH_SHARE_SCREENSHOT_FEATURE);
         }
+        if (USER_ACTION_WEB_STYLE_NOTES_SELECTED.equals(firstPartyOption.featureNameForMetrics)) {
+            return mFeatureEngagementTracker.shouldTriggerHelpUI(
+                    FeatureConstants.SHARING_HUB_WEBNOTES_STYLIZE_FEATURE);
+        }
+        return false;
     }
 
-    private void maybeAddQrCodeFirstPartyOption() {
-        if (!mIsIncognito) {
-            mOrderedFirstPartyOptions.add(createQrCodeFirstPartyOption());
+    private boolean hideBottomSheetContentOnTap(FirstPartyOption firstPartyOption) {
+        if (USER_ACTION_SCREENSHOT_SELECTED.equals(firstPartyOption.featureNameForMetrics)
+                || USER_ACTION_WEB_STYLE_NOTES_SELECTED.equals(
+                        firstPartyOption.featureNameForMetrics)) {
+            return false;
         }
+        return true;
     }
 
-    private FirstPartyOption createScreenshotFirstPartyOption() {
-        boolean showNewBadge = mFeatureEngagementTracker.isInitialized()
-                && mFeatureEngagementTracker.shouldTriggerHelpUI(
-                        FeatureConstants.IPH_SHARE_SCREENSHOT_FEATURE);
+    @Override
+    protected FirstPartyOption createScreenshotFirstPartyOption() {
         return new FirstPartyOptionBuilder(ContentType.LINK_PAGE_VISIBLE, ContentType.TEXT,
                 ContentType.HIGHLIGHTED_TEXT, ContentType.IMAGE)
                 .setDetailedContentTypesToDisableFor(DetailedContentType.WEB_NOTES)
                 .setIcon(R.drawable.screenshot, R.string.sharing_screenshot)
-                .setFeatureNameForMetrics("SharingHubAndroid.ScreenshotSelected")
+                .setFeatureNameForMetrics(USER_ACTION_SCREENSHOT_SELECTED)
                 .setDisableForMultiWindow(true)
-                .setShowNewBadge(showNewBadge)
-                .setHideBottomSheetContentOnTap(false)
                 .setOnClickCallback((view) -> {
                     mFeatureEngagementTracker.notifyEvent(EventConstants.SHARE_SCREENSHOT_SELECTED);
                     ScreenshotCoordinator coordinator = new ScreenshotCoordinator(mActivity,
@@ -352,14 +174,14 @@
                 .build();
     }
 
-    private FirstPartyOption createLongScreenshotsFirstPartyOption() {
+    @Override
+    protected FirstPartyOption createLongScreenshotsFirstPartyOption() {
         return new FirstPartyOptionBuilder(ContentType.LINK_PAGE_VISIBLE, ContentType.TEXT,
                 ContentType.HIGHLIGHTED_TEXT, ContentType.IMAGE)
                 .setDetailedContentTypesToDisableFor(DetailedContentType.WEB_NOTES)
                 .setIcon(R.drawable.long_screenshot, R.string.sharing_long_screenshot)
-                .setFeatureNameForMetrics("SharingHubAndroid.LongScreenshotSelected")
+                .setFeatureNameForMetrics(USER_ACTION_LONG_SCREENSHOT_SELECTED)
                 .setDisableForMultiWindow(true)
-                .setHideBottomSheetContentOnTap(false)
                 .setOnClickCallback((view) -> {
                     mFeatureEngagementTracker.notifyEvent(EventConstants.SHARE_SCREENSHOT_SELECTED);
                     LongScreenshotsCoordinator coordinator = LongScreenshotsCoordinator.create(
@@ -371,157 +193,6 @@
                 .build();
     }
 
-    private FirstPartyOption createCopyLinkFirstPartyOption() {
-        return new FirstPartyOptionBuilder(
-                ContentType.LINK_PAGE_VISIBLE, ContentType.LINK_PAGE_NOT_VISIBLE)
-                .setContentTypesToDisableFor(ContentType.LINK_AND_TEXT)
-                .setIcon(R.drawable.ic_content_copy_black, R.string.sharing_copy_url)
-                .setFeatureNameForMetrics("SharingHubAndroid.CopyURLSelected")
-                .setOnClickCallback((view) -> {
-                    ClipboardManager clipboard = (ClipboardManager) mActivity.getSystemService(
-                            Context.CLIPBOARD_SERVICE);
-                    clipboard.setPrimaryClip(
-                            ClipData.newPlainText(mShareParams.getTitle(), mShareParams.getUrl()));
-                    Toast.makeText(mActivity, R.string.link_copied, Toast.LENGTH_SHORT).show();
-                })
-                .build();
-    }
-
-    private FirstPartyOption createCopyGifFirstPartyOption() {
-        return new FirstPartyOptionBuilder(ContentType.IMAGE, ContentType.IMAGE_AND_LINK)
-                .setIcon(R.drawable.ic_content_copy_black, R.string.sharing_copy_gif)
-                // Enables only for GIF.
-                .setDetailedContentTypesToDisableFor(DetailedContentType.IMAGE,
-                        DetailedContentType.WEB_NOTES, DetailedContentType.NOT_SPECIFIED)
-                .setFeatureNameForMetrics("SharingHubAndroid.CopyGifSelected")
-                .setOnClickCallback((view) -> {
-                    if (!mShareParams.getFileUris().isEmpty()) {
-                        Clipboard.getInstance().setImageUri(mShareParams.getFileUris().get(0));
-                        Toast.makeText(mActivity, R.string.gif_copied, Toast.LENGTH_SHORT).show();
-                    }
-                })
-                .build();
-    }
-
-    private FirstPartyOption createCopyImageFirstPartyOption() {
-        return new FirstPartyOptionBuilder(ContentType.IMAGE, ContentType.IMAGE_AND_LINK)
-                .setIcon(R.drawable.ic_content_copy_black, R.string.sharing_copy_image)
-                .setFeatureNameForMetrics("SharingHubAndroid.CopyImageSelected")
-                .setDetailedContentTypesToDisableFor(DetailedContentType.GIF)
-                .setOnClickCallback((view) -> {
-                    if (!mShareParams.getFileUris().isEmpty()) {
-                        Clipboard.getInstance().setImageUri(mShareParams.getFileUris().get(0));
-                        Toast.makeText(mActivity, R.string.image_copied, Toast.LENGTH_SHORT).show();
-                    }
-                })
-                .build();
-    }
-
-    private FirstPartyOption createCopyFirstPartyOption() {
-        return new FirstPartyOptionBuilder(ContentType.LINK_AND_TEXT)
-                .setIcon(R.drawable.ic_content_copy_black, R.string.sharing_copy)
-                .setFeatureNameForMetrics("SharingHubAndroid.CopySelected")
-                .setOnClickCallback((view) -> {
-                    ClipboardManager clipboard = (ClipboardManager) mActivity.getSystemService(
-                            Context.CLIPBOARD_SERVICE);
-                    clipboard.setPrimaryClip(ClipData.newPlainText(
-                            mShareParams.getTitle(), mShareParams.getTextAndUrl()));
-                    Toast.makeText(mActivity, R.string.copied, Toast.LENGTH_SHORT).show();
-                })
-                .build();
-    }
-
-    private FirstPartyOption createCopyTextFirstPartyOption() {
-        return new FirstPartyOptionBuilder(ContentType.TEXT, ContentType.HIGHLIGHTED_TEXT)
-                .setContentTypesToDisableFor(ContentType.LINK_AND_TEXT)
-                .setIcon(R.drawable.ic_content_copy_black, R.string.sharing_copy_text)
-                .setFeatureNameForMetrics("SharingHubAndroid.CopyTextSelected")
-                .setOnClickCallback((view) -> {
-                    ClipboardManager clipboard = (ClipboardManager) mActivity.getSystemService(
-                            Context.CLIPBOARD_SERVICE);
-                    clipboard.setPrimaryClip(
-                            ClipData.newPlainText(mShareParams.getTitle(), mShareParams.getText()));
-                    Toast.makeText(mActivity, R.string.text_copied, Toast.LENGTH_SHORT).show();
-                })
-                .build();
-    }
-
-    private FirstPartyOption createSendTabToSelfFirstPartyOption() {
-        return new FirstPartyOptionBuilder(
-                ContentType.LINK_PAGE_VISIBLE, ContentType.LINK_PAGE_NOT_VISIBLE, ContentType.IMAGE)
-                .setDetailedContentTypesToDisableFor(DetailedContentType.WEB_NOTES)
-                .setIcon(R.drawable.send_tab, R.string.send_tab_to_self_share_activity_title)
-                .setFeatureNameForMetrics("SharingHubAndroid.SendTabToSelfSelected")
-                .setOnClickCallback((view) -> {
-                    SendTabToSelfCoordinator sttsCoordinator =
-                            new SendTabToSelfCoordinator(mActivity, mWindowAndroid, mUrl,
-                                    mShareParams.getTitle(), mBottomSheetController, mProfile);
-                    sttsCoordinator.show();
-                })
-                .build();
-    }
-
-    private FirstPartyOption createQrCodeFirstPartyOption() {
-        return new FirstPartyOptionBuilder(
-                ContentType.LINK_PAGE_VISIBLE, ContentType.LINK_PAGE_NOT_VISIBLE, ContentType.IMAGE)
-                .setDetailedContentTypesToDisableFor(DetailedContentType.WEB_NOTES)
-                .setIcon(R.drawable.qr_code, R.string.qr_code_share_icon_label)
-                .setFeatureNameForMetrics("SharingHubAndroid.QRCodeSelected")
-                .setOnClickCallback((view) -> {
-                    QrCodeCoordinator qrCodeCoordinator =
-                            new QrCodeCoordinator(mActivity, mUrl, mShareParams.getWindow());
-                    qrCodeCoordinator.show();
-                })
-                .build();
-    }
-
-    private FirstPartyOption createPrintingFirstPartyOption() {
-        return new FirstPartyOptionBuilder(ContentType.LINK_PAGE_VISIBLE)
-                .setIcon(R.drawable.sharing_print, R.string.print_share_activity_title)
-                .setFeatureNameForMetrics("SharingHubAndroid.PrintSelected")
-                .setOnClickCallback((view) -> { mPrintTabCallback.onResult(mTabProvider.get()); })
-                .build();
-    }
-
-    private FirstPartyOption createWebNotesStylizeFirstPartyOption() {
-        boolean showNewBadge = mFeatureEngagementTracker.isInitialized()
-                && mFeatureEngagementTracker.shouldTriggerHelpUI(
-                        FeatureConstants.SHARING_HUB_WEBNOTES_STYLIZE_FEATURE);
-        String title = mShareParams.getTitle();
-        return new FirstPartyOptionBuilder(ContentType.HIGHLIGHTED_TEXT)
-                .setIcon(R.drawable.webnote, R.string.sharing_webnotes_create_card)
-                .setIconContentDescription(R.string.sharing_webnotes_accessibility_description)
-                .setFeatureNameForMetrics("SharingHubAndroid.WebnotesStylize")
-                .setOnClickCallback((view) -> {
-                    mFeatureEngagementTracker.notifyEvent(
-                            EventConstants.SHARING_HUB_WEBNOTES_STYLIZE_USED);
-                    NoteCreationCoordinator coordinator = NoteCreationCoordinatorFactory.create(
-                            mActivity, mShareParams.getWindow(), mUrl, title,
-                            mShareParams.getRawText().trim(), mChromeOptionShareCallback);
-                    coordinator.showDialog();
-                })
-                .setShowNewBadge(showNewBadge)
-                .build();
-    }
-
-    private FirstPartyOption createSaveImageFirstPartyOption() {
-        return new FirstPartyOptionBuilder(ContentType.IMAGE, ContentType.IMAGE_AND_LINK)
-                .setIcon(R.drawable.save_to_device, R.string.sharing_save_image)
-                .setFeatureNameForMetrics("SharingHubAndroid.SaveImageSelected")
-                .setOnClickCallback((view) -> {
-                    if (mShareParams.getFileUris().isEmpty()) return;
-
-                    ShareImageFileUtils.getBitmapFromUriAsync(
-                            mActivity, mShareParams.getFileUris().get(0), (bitmap) -> {
-                                SaveBitmapDelegate saveBitmapDelegate = new SaveBitmapDelegate(
-                                        mActivity, bitmap, R.string.save_image_filename_prefix,
-                                        null, mShareParams.getWindow());
-                                saveBitmapDelegate.save();
-                            });
-                })
-                .build();
-    }
-
     private void callTargetChosenCallback() {
         ShareParams.TargetChosenCallback callback = mShareParams.getCallback();
         if (callback != null) {
@@ -531,5 +202,4 @@
             mShareParams.setCallback(null);
         }
     }
-
 }
diff --git a/chrome/browser/share/android/java/src/org/chromium/chrome/browser/share/share_sheet/ShareSheetPropertyModelBuilder.java b/chrome/browser/share/android/java/src/org/chromium/chrome/browser/share/share_sheet/ShareSheetPropertyModelBuilder.java
index c940fc529..0253de4 100644
--- a/chrome/browser/share/android/java/src/org/chromium/chrome/browser/share/share_sheet/ShareSheetPropertyModelBuilder.java
+++ b/chrome/browser/share/android/java/src/org/chromium/chrome/browser/share/share_sheet/ShareSheetPropertyModelBuilder.java
@@ -52,7 +52,7 @@
             ContentType.HIGHLIGHTED_TEXT, ContentType.LINK_AND_TEXT, ContentType.IMAGE,
             ContentType.OTHER_FILE_TYPE, ContentType.IMAGE_AND_LINK})
     @Retention(RetentionPolicy.SOURCE)
-    @interface ContentType {
+    public @interface ContentType {
         int LINK_PAGE_VISIBLE = 0;
         int LINK_PAGE_NOT_VISIBLE = 1;
         int TEXT = 2;
diff --git a/chrome/browser/share/android/java_sources.gni b/chrome/browser/share/android/java_sources.gni
index a561bb3f..954efa3 100644
--- a/chrome/browser/share/android/java_sources.gni
+++ b/chrome/browser/share/android/java_sources.gni
@@ -7,6 +7,7 @@
 share_java_sources = [
   "//chrome/browser/share/android/java/src/org/chromium/chrome/browser/share/BaseScreenshotCoordinator.java",
   "//chrome/browser/share/android/java/src/org/chromium/chrome/browser/share/BitmapDownloadRequest.java",
+  "//chrome/browser/share/android/java/src/org/chromium/chrome/browser/share/ChromeProvidedSharingOptionsProviderBase.java",
   "//chrome/browser/share/android/java/src/org/chromium/chrome/browser/share/SaveBitmapDelegate.java",
   "//chrome/browser/share/android/java/src/org/chromium/chrome/browser/share/crow/CrowBridge.java",
   "//chrome/browser/share/android/java/src/org/chromium/chrome/browser/share/crow/CrowIphController.java",
diff --git a/chrome/browser/speech/crosapi_tts_engine_delegate_ash.cc b/chrome/browser/speech/crosapi_tts_engine_delegate_ash.cc
index e78c1af..139bd1a 100644
--- a/chrome/browser/speech/crosapi_tts_engine_delegate_ash.cc
+++ b/chrome/browser/speech/crosapi_tts_engine_delegate_ash.cc
@@ -51,3 +51,13 @@
   crosapi::CrosapiManager::Get()->crosapi_ash()->tts_ash()->StopRemoteEngine(
       utterance);
 }
+
+void CrosapiTtsEngineDelegateAsh::Pause(content::TtsUtterance* utterance) {
+  crosapi::CrosapiManager::Get()->crosapi_ash()->tts_ash()->PauseRemoteEngine(
+      utterance);
+}
+
+void CrosapiTtsEngineDelegateAsh::Resume(content::TtsUtterance* utterance) {
+  crosapi::CrosapiManager::Get()->crosapi_ash()->tts_ash()->ResumeRemoteEngine(
+      utterance);
+}
diff --git a/chrome/browser/speech/crosapi_tts_engine_delegate_ash.h b/chrome/browser/speech/crosapi_tts_engine_delegate_ash.h
index 7435e90..9222a92 100644
--- a/chrome/browser/speech/crosapi_tts_engine_delegate_ash.h
+++ b/chrome/browser/speech/crosapi_tts_engine_delegate_ash.h
@@ -24,6 +24,8 @@
   void Speak(content::TtsUtterance* utterance,
              const content::VoiceData& voice) override;
   void Stop(content::TtsUtterance* utterance) override;
+  void Pause(content::TtsUtterance* utterance) override;
+  void Resume(content::TtsUtterance* utterance) override;
 };
 
 #endif  // CHROME_BROWSER_SPEECH_CROSAPI_TTS_ENGINE_DELEGATE_ASH_H_
diff --git a/chrome/browser/speech/extension_api/tts_engine_extension_api.cc b/chrome/browser/speech/extension_api/tts_engine_extension_api.cc
index f45aa44..de071675 100644
--- a/chrome/browser/speech/extension_api/tts_engine_extension_api.cc
+++ b/chrome/browser/speech/extension_api/tts_engine_extension_api.cc
@@ -339,27 +339,33 @@
 }
 
 void TtsExtensionEngine::Pause(content::TtsUtterance* utterance) {
-  Profile* profile =
-      Profile::FromBrowserContext(utterance->GetBrowserContext());
+  Pause(utterance->GetBrowserContext(), utterance->GetEngineId());
+}
+
+void TtsExtensionEngine::Pause(content::BrowserContext* browser_context,
+                               const std::string& engine_id) {
+  Profile* profile = Profile::FromBrowserContext(browser_context);
   auto event = std::make_unique<extensions::Event>(
       extensions::events::TTS_ENGINE_ON_PAUSE, tts_engine_events::kOnPause,
       base::Value::List(), profile);
   EventRouter* event_router = EventRouter::Get(profile);
-  std::string id = utterance->GetEngineId();
-  event_router->DispatchEventToExtension(id, std::move(event));
-  WarnIfMissingPauseOrResumeListener(profile, event_router, id);
+  event_router->DispatchEventToExtension(engine_id, std::move(event));
+  WarnIfMissingPauseOrResumeListener(profile, event_router, engine_id);
 }
 
 void TtsExtensionEngine::Resume(content::TtsUtterance* utterance) {
-  Profile* profile =
-      Profile::FromBrowserContext(utterance->GetBrowserContext());
+  Resume(utterance->GetBrowserContext(), utterance->GetEngineId());
+}
+
+void TtsExtensionEngine::Resume(content::BrowserContext* browser_context,
+                                const std::string& engine_id) {
+  Profile* profile = Profile::FromBrowserContext(browser_context);
   auto event = std::make_unique<extensions::Event>(
       extensions::events::TTS_ENGINE_ON_RESUME, tts_engine_events::kOnResume,
       base::Value::List(), profile);
   EventRouter* event_router = EventRouter::Get(profile);
-  std::string id = utterance->GetEngineId();
-  event_router->DispatchEventToExtension(id, std::move(event));
-  WarnIfMissingPauseOrResumeListener(profile, event_router, id);
+  event_router->DispatchEventToExtension(engine_id, std::move(event));
+  WarnIfMissingPauseOrResumeListener(profile, event_router, engine_id);
 }
 
 void TtsExtensionEngine::LoadBuiltInTtsEngine(
diff --git a/chrome/browser/speech/extension_api/tts_engine_extension_api.h b/chrome/browser/speech/extension_api/tts_engine_extension_api.h
index 819858f..7fb8057 100644
--- a/chrome/browser/speech/extension_api/tts_engine_extension_api.h
+++ b/chrome/browser/speech/extension_api/tts_engine_extension_api.h
@@ -45,14 +45,22 @@
   void Speak(content::TtsUtterance* utterance,
              const content::VoiceData& voice) override;
   void Stop(content::TtsUtterance* utterance) override;
-  void Stop(content::BrowserContext* browser_context,
-            const std::string& engine_id) override;
   void Pause(content::TtsUtterance* utterance) override;
   void Resume(content::TtsUtterance* utterance) override;
   void LoadBuiltInTtsEngine(content::BrowserContext* browser_context) override;
   bool IsBuiltInTtsEngineInitialized(
       content::BrowserContext* browser_context) override;
 
+  // Stops the given speech engine loaded in |browser_context|.
+  void Stop(content::BrowserContext* browser_context,
+            const std::string& engine_id);
+  // Pauses the given speech engine loaded in |browser_context|.
+  void Pause(content::BrowserContext* browser_context,
+             const std::string& engine_id);
+  // Resumes the given speech engine loaded in |browser_context|.
+  void Resume(content::BrowserContext* browser_context,
+              const std::string& engine_id);
+
   void DisableBuiltInTTSEngineForTesting() {
     disable_built_in_tts_engine_for_testing_ = true;
   }
diff --git a/chrome/browser/speech/extension_api/tts_extension_api_lacros_browsertest.cc b/chrome/browser/speech/extension_api/tts_extension_api_lacros_browsertest.cc
index dffe3228..cc76aced 100644
--- a/chrome/browser/speech/extension_api/tts_extension_api_lacros_browsertest.cc
+++ b/chrome/browser/speech/extension_api/tts_extension_api_lacros_browsertest.cc
@@ -333,4 +333,36 @@
   ASSERT_TRUE(IsUtteranceQueueEmpty());
 }
 
+IN_PROC_BROWSER_TEST_F(LacrosTtsApiTest, PauseBeforeSpeakWithLacrosTtsEngine) {
+  if (chromeos::LacrosService::Get()->GetInterfaceVersion(
+          crosapi::mojom::Tts::Uuid_) <
+      static_cast<int>(crosapi::mojom::Tts::kPauseMinVersion)) {
+    GTEST_SKIP() << "Unsupported ash version.";
+  }
+
+  // Load Lacros tts engine extension, register the tts engine events, and
+  // call tts.pause, then tts.speak, tts.resume from the testing extension.
+  ASSERT_TRUE(
+      RunExtensionTest("tts_engine/lacros_tts_support/"
+                       "tts_pause_before_speak_lacros_engine",
+                       {}, {.ignore_manifest_warnings = true}))
+      << message_;
+}
+
+IN_PROC_BROWSER_TEST_F(LacrosTtsApiTest, PauseDuringSpeakWithLacrosTtsEngine) {
+  if (chromeos::LacrosService::Get()->GetInterfaceVersion(
+          crosapi::mojom::Tts::Uuid_) <
+      static_cast<int>(crosapi::mojom::Tts::kPauseMinVersion)) {
+    GTEST_SKIP() << "Unsupported ash version.";
+  }
+
+  // Load Lacros tts engine extension, register the tts engine events, and
+  // call tts.speak, then tts.pause, tts.resume from the testing extension.
+  ASSERT_TRUE(
+      RunExtensionTest("tts_engine/lacros_tts_support/"
+                       "tts_pause_during_speak_lacros_engine",
+                       {}, {.ignore_manifest_warnings = true}))
+      << message_;
+}
+
 }  // namespace extensions
diff --git a/chrome/browser/speech/tts_ash.cc b/chrome/browser/speech/tts_ash.cc
index 5d2a377..566934c2 100644
--- a/chrome/browser/speech/tts_ash.cc
+++ b/chrome/browser/speech/tts_ash.cc
@@ -236,6 +236,14 @@
   content::TtsController::GetInstance()->Stop(source_url);
 }
 
+void TtsAsh::Pause() {
+  content::TtsController::GetInstance()->Pause();
+}
+
+void TtsAsh::Resume() {
+  content::TtsController::GetInstance()->Resume();
+}
+
 void TtsAsh::SpeakWithLacrosVoice(content::TtsUtterance* utterance,
                                   const content::VoiceData& voice) {
   if (!HasTtsClient())
@@ -295,6 +303,18 @@
   item->second->Stop(utterance->GetEngineId());
 }
 
+void TtsAsh::PauseRemoteEngine(content::TtsUtterance* utterance) {
+  auto item = tts_clients_.find(GetPrimaryProfileBrowserContextId());
+  DCHECK(item != tts_clients_.end());
+  item->second->Pause(utterance->GetEngineId());
+}
+
+void TtsAsh::ResumeRemoteEngine(content::TtsUtterance* utterance) {
+  auto item = tts_clients_.find(GetPrimaryProfileBrowserContextId());
+  DCHECK(item != tts_clients_.end());
+  item->second->Resume(utterance->GetEngineId());
+}
+
 void TtsAsh::GetCrosapiVoices(base::UnguessableToken browser_context_id,
                               std::vector<content::VoiceData>* out_voices) {
   // Returns the cached Lacros voices.
diff --git a/chrome/browser/speech/tts_ash.h b/chrome/browser/speech/tts_ash.h
index 79ccc02f..029eaec1 100644
--- a/chrome/browser/speech/tts_ash.h
+++ b/chrome/browser/speech/tts_ash.h
@@ -55,6 +55,14 @@
   // |utterance|.
   void StopRemoteEngine(content::TtsUtterance* utterance);
 
+  // Requests the associated Lacros speech engine to pause speaking the
+  // |utterance|.
+  void PauseRemoteEngine(content::TtsUtterance* utterance);
+
+  // Requests the associated Lacros speech engine to resume speaking the
+  // |utterance|.
+  void ResumeRemoteEngine(content::TtsUtterance* utterance);
+
   void DeletePendingAshUtteranceClient(int utterance_id);
 
   // crosapi::mojom::Tts:
@@ -67,6 +75,8 @@
       mojom::TtsUtterancePtr utterance,
       mojo::PendingRemote<mojom::TtsUtteranceClient> utterance_client) override;
   void Stop(const GURL& source_url) override;
+  void Pause() override;
+  void Resume() override;
 
  private:
   class TtsUtteranceClient;
diff --git a/chrome/browser/speech/tts_client_lacros.cc b/chrome/browser/speech/tts_client_lacros.cc
index c9dc174..434f8c84 100644
--- a/chrome/browser/speech/tts_client_lacros.cc
+++ b/chrome/browser/speech/tts_client_lacros.cc
@@ -265,8 +265,15 @@
 }
 
 void TtsClientLacros::Stop(const std::string& engine_id) {
-  content::TtsController::GetInstance()->GetTtsEngineDelegate()->Stop(
-      browser_context_, engine_id);
+  TtsExtensionEngine::GetInstance()->Stop(browser_context_, engine_id);
+}
+
+void TtsClientLacros::Pause(const std::string& engine_id) {
+  TtsExtensionEngine::GetInstance()->Pause(browser_context_, engine_id);
+}
+
+void TtsClientLacros::Resume(const std::string& engine_id) {
+  TtsExtensionEngine::GetInstance()->Resume(browser_context_, engine_id);
 }
 
 void TtsClientLacros::GetAllVoices(
@@ -401,6 +408,30 @@
   lacros_service->GetRemote<crosapi::mojom::Tts>()->Stop(source_url);
 }
 
+void TtsClientLacros::RequestPause() {
+  auto* lacros_service = chromeos::LacrosService::Get();
+  if (!lacros_service->IsAvailable<crosapi::mojom::Tts>() ||
+      static_cast<uint32_t>(
+          lacros_service->GetInterfaceVersion(crosapi::mojom::Tts::Uuid_)) <
+          crosapi::mojom::Tts::kPauseMinVersion) {
+    LOG(WARNING) << kErrorUnsupportedVersion;
+    return;
+  }
+  lacros_service->GetRemote<crosapi::mojom::Tts>()->Pause();
+}
+
+void TtsClientLacros::RequestResume() {
+  auto* lacros_service = chromeos::LacrosService::Get();
+  if (!lacros_service->IsAvailable<crosapi::mojom::Tts>() ||
+      static_cast<uint32_t>(
+          lacros_service->GetInterfaceVersion(crosapi::mojom::Tts::Uuid_)) <
+          crosapi::mojom::Tts::kResumeMinVersion) {
+    LOG(WARNING) << kErrorUnsupportedVersion;
+    return;
+  }
+  lacros_service->GetRemote<crosapi::mojom::Tts>()->Resume();
+}
+
 void TtsClientLacros::OnLacrosSpeechEngineTtsEvent(
     int utterance_id,
     content::TtsEventType event_type,
diff --git a/chrome/browser/speech/tts_client_lacros.h b/chrome/browser/speech/tts_client_lacros.h
index b94bbd64..fd9e4ed 100644
--- a/chrome/browser/speech/tts_client_lacros.h
+++ b/chrome/browser/speech/tts_client_lacros.h
@@ -45,6 +45,8 @@
       mojo::PendingRemote<crosapi::mojom::TtsUtteranceClient>
           ash_utterance_client) override;
   void Stop(const std::string& engine_id) override;
+  void Pause(const std::string& engine_id) override;
+  void Resume(const std::string& engine_id) override;
 
   const base::UnguessableToken& browser_context_id() const {
     return browser_context_id_;
@@ -61,6 +63,16 @@
   // the given |source_url|) to Ash.
   void RequestStop(const GURL& source_url);
 
+  // Forwards the Pause request from Lacros Tts client (Tts extension api or
+  // speechSynthesis web api) to Ash, so that the request will be processed by
+  // Ash's TtsController.
+  void RequestPause();
+
+  // Forwards the Resume request from Lacros Tts client (Tts extension api or
+  // speechSynthesis web api) to Ash, so that the request will be processed by
+  // Ash's TtsController.
+  void RequestResume();
+
   // Handle events received from the Lacros speech engine.
   void OnLacrosSpeechEngineTtsEvent(int utterance_id,
                                     content::TtsEventType event_type,
diff --git a/chrome/browser/speech/tts_external_platform_delegate_impl_lacros.cc b/chrome/browser/speech/tts_external_platform_delegate_impl_lacros.cc
index 7b3cc7ab..e4a1b9dc 100644
--- a/chrome/browser/speech/tts_external_platform_delegate_impl_lacros.cc
+++ b/chrome/browser/speech/tts_external_platform_delegate_impl_lacros.cc
@@ -61,3 +61,15 @@
   TtsClientLacros::GetForBrowserContext(browser_context)
       ->RequestStop(source_url);
 }
+
+void ExternalPlatformDelegateImplLacros::Pause() {
+  content::BrowserContext* browser_context =
+      ProfileManager::GetPrimaryUserProfile();
+  TtsClientLacros::GetForBrowserContext(browser_context)->RequestPause();
+}
+
+void ExternalPlatformDelegateImplLacros::Resume() {
+  content::BrowserContext* browser_context =
+      ProfileManager::GetPrimaryUserProfile();
+  TtsClientLacros::GetForBrowserContext(browser_context)->RequestResume();
+}
diff --git a/chrome/browser/speech/tts_external_platform_delegate_impl_lacros.h b/chrome/browser/speech/tts_external_platform_delegate_impl_lacros.h
index ca6027db..22e921b 100644
--- a/chrome/browser/speech/tts_external_platform_delegate_impl_lacros.h
+++ b/chrome/browser/speech/tts_external_platform_delegate_impl_lacros.h
@@ -32,6 +32,8 @@
                   int length,
                   const std::string& error_message) override;
   void Stop(const GURL& source_url) override;
+  void Pause() override;
+  void Resume() override;
 
  private:
   friend class base::NoDestructor<ExternalPlatformDelegateImplLacros>;
diff --git a/chrome/browser/storage/shared_storage_browsertest.cc b/chrome/browser/storage/shared_storage_browsertest.cc
index addd9667..74d81f2 100644
--- a/chrome/browser/storage/shared_storage_browsertest.cc
+++ b/chrome/browser/storage/shared_storage_browsertest.cc
@@ -16,6 +16,7 @@
 #include "base/test/scoped_feature_list.h"
 #include "base/test/scoped_run_loop_timeout.h"
 #include "base/test/task_environment.h"
+#include "base/test/with_feature_override.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/test/base/chrome_test_utils.h"
 #include "components/content_settings/core/browser/cookie_settings.h"
@@ -31,6 +32,7 @@
 #include "content/public/test/browser_test_utils.h"
 #include "content/public/test/shared_storage_test_utils.h"
 #include "content/public/test/test_navigation_observer.h"
+#include "content/public/test/test_select_url_fenced_frame_config_observer.h"
 #include "net/dns/mock_host_resolver.h"
 #include "net/test/embedded_test_server/request_handler_util.h"
 #include "testing/gmock/include/gmock/gmock.h"
@@ -104,6 +106,14 @@
 
 const double kBudgetAllowed = 5.0;
 
+auto describe_param = [](const auto& info) {
+  if (info.param) {
+    return "ResolveSelectURLToConfig";
+  } else {
+    return "ResolveSelectURLToURN";
+  }
+};
+
 #if BUILDFLAG(IS_ANDROID)
 base::FilePath GetChromeTestDataDir() {
   return base::FilePath(FILE_PATH_LITERAL("chrome/test/data"));
@@ -199,9 +209,9 @@
 
 }  // namespace
 
-class SharedStorageChromeBrowserTest : public PlatformBrowserTest {
+class SharedStorageChromeBrowserTestBase : public PlatformBrowserTest {
  public:
-  SharedStorageChromeBrowserTest() {
+  SharedStorageChromeBrowserTestBase() {
     base::test::TaskEnvironment task_environment;
 
     // TODO(crbug.com/1378703): Update the tests to support Privacy Sandbox 4.
@@ -223,7 +233,7 @@
     InitPrefs();
   }
 
-  ~SharedStorageChromeBrowserTest() override = default;
+  ~SharedStorageChromeBrowserTestBase() override = default;
 
   net::EmbeddedTestServer* https_server() { return &https_server_; }
 
@@ -300,8 +310,7 @@
         MakeFilter({"Finish executing customizable_module.js"}));
 
     base::StringPairs run_function_body_replacement;
-    run_function_body_replacement.push_back(
-        std::make_pair("{{RUN_FUNCTION_BODY}}", script));
+    run_function_body_replacement.emplace_back("{{RUN_FUNCTION_BODY}}", script);
 
     std::string host =
         execution_target.render_frame_host()->GetLastCommittedOrigin().host();
@@ -383,6 +392,8 @@
     return result;
   }
 
+  virtual bool ResolveSelectURLToConfig() { return false; }
+
  protected:
   base::HistogramTester histogram_tester_;
 
@@ -391,31 +402,44 @@
   net::EmbeddedTestServer https_server_{net::EmbeddedTestServer::TYPE_HTTPS};
 };
 
-struct SharedStorageChromeBrowserParams {
-  bool enable_privacy_sandbox;
-  bool allow_third_party_cookies;
+class SharedStorageChromeBrowserTest
+    : public base::test::WithFeatureOverride,
+      public SharedStorageChromeBrowserTestBase {
+ public:
+  SharedStorageChromeBrowserTest()
+      : base::test::WithFeatureOverride(
+            blink::features::kFencedFramesAPIChanges) {
+    scoped_feature_list_.InitAndEnableFeature(blink::features::kFencedFrames);
+  }
+  ~SharedStorageChromeBrowserTest() override = default;
+
+  bool ResolveSelectURLToConfig() override { return IsParamFeatureEnabled(); }
+
+ private:
+  base::test::ScopedFeatureList scoped_feature_list_;
 };
 
-// Used by `testing::PrintToStringParamName()`.
-std::string PrintToString(const SharedStorageChromeBrowserParams& p) {
-  return base::StrCat(
-      {"PrivacySandbox", p.enable_privacy_sandbox ? "Enabled" : "Disabled",
-       "_3PCookies", p.allow_third_party_cookies ? "Allowed" : "Blocked"});
-}
-
-std::vector<SharedStorageChromeBrowserParams>
-GetSharedStorageChromeBrowserParams() {
-  return std::vector<SharedStorageChromeBrowserParams>(
-      {{true, true}, {true, false}, {false, true}, {false, false}});
-}
+using SharedStorageChromeBrowserParams =
+    std::tuple</*resolve_to_config=*/bool,
+               /*enable_privacy_sandbox=*/bool,
+               /*allow_third_party_cookies=*/bool>;
 
 class SharedStoragePrefBrowserTest
-    : public SharedStorageChromeBrowserTest,
+    : public SharedStorageChromeBrowserTestBase,
       public testing::WithParamInterface<SharedStorageChromeBrowserParams> {
  public:
+  SharedStoragePrefBrowserTest() {
+    fenced_frame_api_change_feature_.InitWithFeatureState(
+        blink::features::kFencedFramesAPIChanges, ResolveSelectURLToConfig());
+    fenced_frame_feature_.InitAndEnableFeature(blink::features::kFencedFrames);
+  }
+
+  bool ResolveSelectURLToConfig() override { return std::get<0>(GetParam()); }
+  bool EnablePrivacySandbox() const { return std::get<1>(GetParam()); }
+  bool AllowThirdPartyCookies() const { return std::get<2>(GetParam()); }
+
   bool SuccessExpected() {
-    return GetParam().enable_privacy_sandbox &&
-           GetParam().allow_third_party_cookies;
+    return EnablePrivacySandbox() && AllowThirdPartyCookies();
   }
 
   // Sets prefs as parametrized.
@@ -423,8 +447,7 @@
   // TODO(crbug.com/1396748): We may need to update how preferences are set once
   // the Privacy Sandbox settings release 4 is launched (crbug.com/1378703).
   void InitPrefs() override {
-    SetPrefs(GetParam().enable_privacy_sandbox,
-             GetParam().allow_third_party_cookies);
+    SetPrefs(EnablePrivacySandbox(), AllowThirdPartyCookies());
   }
 
   void AddSimpleModuleWithPermissionBypassed(
@@ -462,8 +485,7 @@
         MakeFilter({"Finish executing customizable_module.js"}));
 
     base::StringPairs run_function_body_replacement;
-    run_function_body_replacement.push_back(
-        std::make_pair("{{RUN_FUNCTION_BODY}}", script));
+    run_function_body_replacement.emplace_back("{{RUN_FUNCTION_BODY}}", script);
 
     std::string host =
         execution_target.render_frame_host()->GetLastCommittedOrigin().host();
@@ -530,13 +552,23 @@
 
     return result.error.empty();
   }
+
+ private:
+  base::test::ScopedFeatureList fenced_frame_api_change_feature_;
+  base::test::ScopedFeatureList fenced_frame_feature_;
 };
 
 INSTANTIATE_TEST_SUITE_P(
     All,
     SharedStoragePrefBrowserTest,
-    testing::ValuesIn(GetSharedStorageChromeBrowserParams()),
-    testing::PrintToStringParamName());
+    testing::Combine(testing::Bool(), testing::Bool(), testing::Bool()),
+    [](const testing::TestParamInfo<SharedStoragePrefBrowserTest::ParamType>&
+           info) {
+      return base::StrCat(
+          {"ResolveSelectURLTo", std::get<0>(info.param) ? "Config" : "URN",
+           "_PrivacySandbox", std::get<1>(info.param) ? "Enabled" : "Disabled",
+           "_3PCookies", std::get<2>(info.param) ? "Allowed" : "Blocked"});
+    });
 
 IN_PROC_BROWSER_TEST_P(SharedStoragePrefBrowserTest, AddModule) {
   EXPECT_TRUE(content::NavigateToURL(
@@ -644,16 +676,48 @@
   run_url_op_console_observer.SetFilter(
       MakeFilter({"Finish executing \'test-url-selection-operation\'"}));
 
-  content::EvalJsResult run_url_op_result =
-      content::EvalJs(GetActiveWebContents(), R"(
-      sharedStorage.selectURL(
-          'test-url-selection-operation',
-          [{url: "fenced_frames/title0.html"},
-           {url: "fenced_frames/title1.html",
-            reportingMetadata: {"click": "fenced_frames/report1.html"}},
-           {url: "fenced_frames/title2.html"}],
-          {data: {'mockResult': 1}});
-    )");
+  EXPECT_TRUE(ExecJs(GetActiveWebContents(),
+                     content::JsReplace("window.resolveSelectURLToConfig = $1;",
+                                        ResolveSelectURLToConfig())));
+
+  // Construct and add the `TestSelectURLFencedFrameConfigObserver` to shared
+  // storage worklet host manager.
+  content::StoragePartition* storage_partition =
+      content::ToRenderFrameHost(GetActiveWebContents())
+          .render_frame_host()
+          ->GetStoragePartition();
+  content::TestSelectURLFencedFrameConfigObserver config_observer(
+      storage_partition);
+  content::EvalJsResult run_url_op_result = EvalJs(GetActiveWebContents(), R"(
+        (async function() {
+          window.select_url_result = await sharedStorage.selectURL(
+            'test-url-selection-operation',
+            [
+              {
+                url: "fenced_frames/title0.html"
+              },
+              {
+                url: "fenced_frames/title1.html",
+                reportingMetadata: {
+                  "click": "fenced_frames/report1.html"
+                }
+              },
+              {
+                url: "fenced_frames/title2.html"
+              }
+            ],
+            {
+              data: {'mockResult': 1},
+              resolveToConfig: resolveSelectURLToConfig
+            }
+          );
+          if (resolveSelectURLToConfig &&
+              !(select_url_result instanceof FencedFrameConfig)) {
+            throw new Error('selectURL() did not return a FencedFrameConfig.');
+          }
+          return window.select_url_result;
+        })()
+      )");
 
   WaitForHistograms({kTimingDocumentAddModuleHistogram});
   histogram_tester_.ExpectTotalCount(kTimingDocumentAddModuleHistogram, 1);
@@ -679,8 +743,15 @@
   // Privacy Sandbox is enabled and 3P cookies are allowed, so Shared Storage
   // should be allowed.
   EXPECT_TRUE(run_url_op_result.error.empty());
-  EXPECT_TRUE(
-      blink::IsValidUrnUuidURL(GURL(run_url_op_result.ExtractString())));
+  absl::optional<GURL> observed_urn_uuid = config_observer.GetUrnUuid();
+  EXPECT_TRUE(observed_urn_uuid.has_value());
+  EXPECT_TRUE(blink::IsValidUrnUuidURL(observed_urn_uuid.value()));
+  GURL urn_uuid = observed_urn_uuid.value();
+
+  if (!ResolveSelectURLToConfig()) {
+    EXPECT_EQ(run_url_op_result.ExtractString(), observed_urn_uuid->spec());
+  }
+
   EXPECT_EQ(1u, run_url_op_console_observer.messages().size());
   EXPECT_EQ(
       "Finish executing \'test-url-selection-operation\'",
@@ -1059,7 +1130,7 @@
   }
 }
 
-IN_PROC_BROWSER_TEST_F(SharedStorageChromeBrowserTest,
+IN_PROC_BROWSER_TEST_P(SharedStorageChromeBrowserTest,
                        WorkletKeysEntries_AllIterated) {
   EXPECT_TRUE(content::NavigateToURL(
       GetActiveWebContents(),
@@ -1142,7 +1213,7 @@
                                       2);
 }
 
-IN_PROC_BROWSER_TEST_F(SharedStorageChromeBrowserTest,
+IN_PROC_BROWSER_TEST_P(SharedStorageChromeBrowserTest,
                        WorkletKeysEntries_PartiallyIterated) {
   EXPECT_TRUE(content::NavigateToURL(
       GetActiveWebContents(),
@@ -1239,7 +1310,7 @@
                                       0);
 }
 
-IN_PROC_BROWSER_TEST_F(SharedStorageChromeBrowserTest,
+IN_PROC_BROWSER_TEST_P(SharedStorageChromeBrowserTest,
                        WorkletKeysEntries_AllIteratedLessThanTenKeys) {
   EXPECT_TRUE(content::NavigateToURL(
       GetActiveWebContents(),
@@ -1322,7 +1393,7 @@
                                       2);
 }
 
-IN_PROC_BROWSER_TEST_F(SharedStorageChromeBrowserTest,
+IN_PROC_BROWSER_TEST_P(SharedStorageChromeBrowserTest,
                        WorkletKeysEntries_PartiallyIteratedLessThanTenKeys) {
   EXPECT_TRUE(content::NavigateToURL(
       GetActiveWebContents(),
@@ -1417,7 +1488,7 @@
                                       0);
 }
 
-IN_PROC_BROWSER_TEST_F(SharedStorageChromeBrowserTest,
+IN_PROC_BROWSER_TEST_P(SharedStorageChromeBrowserTest,
                        WorkletKeysEntries_AllIteratedNoKeys) {
   EXPECT_TRUE(content::NavigateToURL(
       GetActiveWebContents(),
@@ -1480,7 +1551,7 @@
                                       0);
 }
 
-IN_PROC_BROWSER_TEST_F(SharedStorageChromeBrowserTest,
+IN_PROC_BROWSER_TEST_P(SharedStorageChromeBrowserTest,
                        AddModule_InvalidScriptUrlError) {
   EXPECT_TRUE(content::NavigateToURL(
       GetActiveWebContents(),
@@ -1505,7 +1576,7 @@
       blink::SharedStorageWorkletErrorType::kAddModuleWebVisible, 1);
 }
 
-IN_PROC_BROWSER_TEST_F(SharedStorageChromeBrowserTest,
+IN_PROC_BROWSER_TEST_P(SharedStorageChromeBrowserTest,
                        AddModule_CrossOriginScriptError) {
   EXPECT_TRUE(content::NavigateToURL(
       GetActiveWebContents(),
@@ -1532,7 +1603,7 @@
       blink::SharedStorageWorkletErrorType::kAddModuleWebVisible, 1);
 }
 
-IN_PROC_BROWSER_TEST_F(SharedStorageChromeBrowserTest,
+IN_PROC_BROWSER_TEST_P(SharedStorageChromeBrowserTest,
                        AddModule_LoadFailureError) {
   EXPECT_TRUE(content::NavigateToURL(
       GetActiveWebContents(),
@@ -1555,7 +1626,7 @@
       blink::SharedStorageWorkletErrorType::kAddModuleWebVisible, 1);
 }
 
-IN_PROC_BROWSER_TEST_F(SharedStorageChromeBrowserTest,
+IN_PROC_BROWSER_TEST_P(SharedStorageChromeBrowserTest,
                        AddModule_UnexpectedRedirectError) {
   EXPECT_TRUE(content::NavigateToURL(
       GetActiveWebContents(),
@@ -1578,7 +1649,7 @@
       blink::SharedStorageWorkletErrorType::kAddModuleWebVisible, 1);
 }
 
-IN_PROC_BROWSER_TEST_F(SharedStorageChromeBrowserTest,
+IN_PROC_BROWSER_TEST_P(SharedStorageChromeBrowserTest,
                        AddModule_EmptyResultError) {
   EXPECT_TRUE(content::NavigateToURL(
       GetActiveWebContents(),
@@ -1601,7 +1672,7 @@
       blink::SharedStorageWorkletErrorType::kAddModuleWebVisible, 1);
 }
 
-IN_PROC_BROWSER_TEST_F(SharedStorageChromeBrowserTest,
+IN_PROC_BROWSER_TEST_P(SharedStorageChromeBrowserTest,
                        AddModule_MultipleAddModuleError) {
   EXPECT_TRUE(content::NavigateToURL(
       GetActiveWebContents(),
@@ -1634,7 +1705,7 @@
   histogram_tester_.ExpectUniqueSample(kWorkletNumPerPageHistogram, 1, 1);
 }
 
-IN_PROC_BROWSER_TEST_F(SharedStorageChromeBrowserTest, Run_NotLoadedError) {
+IN_PROC_BROWSER_TEST_P(SharedStorageChromeBrowserTest, Run_NotLoadedError) {
   EXPECT_TRUE(content::NavigateToURL(
       GetActiveWebContents(),
       https_server()->GetURL(kSimpleTestHost, kSimplePagePath)));
@@ -1651,7 +1722,7 @@
       blink::SharedStorageWorkletErrorType::kRunNonWebVisible, 1);
 }
 
-IN_PROC_BROWSER_TEST_F(SharedStorageChromeBrowserTest, Run_NotRegisteredError) {
+IN_PROC_BROWSER_TEST_P(SharedStorageChromeBrowserTest, Run_NotRegisteredError) {
   EXPECT_TRUE(content::NavigateToURL(
       GetActiveWebContents(),
       https_server()->GetURL(kSimpleTestHost, kSimplePagePath)));
@@ -1680,7 +1751,7 @@
   histogram_tester_.ExpectUniqueSample(kWorkletNumPerPageHistogram, 1, 1);
 }
 
-IN_PROC_BROWSER_TEST_F(SharedStorageChromeBrowserTest, Run_FunctionError) {
+IN_PROC_BROWSER_TEST_P(SharedStorageChromeBrowserTest, Run_FunctionError) {
   EXPECT_TRUE(content::NavigateToURL(
       GetActiveWebContents(),
       https_server()->GetURL(kSimpleTestHost, kSimplePagePath)));
@@ -1709,7 +1780,7 @@
   histogram_tester_.ExpectUniqueSample(kWorkletNumPerPageHistogram, 1, 1);
 }
 
-IN_PROC_BROWSER_TEST_F(SharedStorageChromeBrowserTest, Run_NotAPromiseError) {
+IN_PROC_BROWSER_TEST_P(SharedStorageChromeBrowserTest, Run_NotAPromiseError) {
   EXPECT_TRUE(content::NavigateToURL(
       GetActiveWebContents(),
       https_server()->GetURL(kSimpleTestHost, kSimplePagePath)));
@@ -1738,7 +1809,7 @@
   histogram_tester_.ExpectUniqueSample(kWorkletNumPerPageHistogram, 1, 1);
 }
 
-IN_PROC_BROWSER_TEST_F(SharedStorageChromeBrowserTest, Run_ScriptError) {
+IN_PROC_BROWSER_TEST_P(SharedStorageChromeBrowserTest, Run_ScriptError) {
   EXPECT_TRUE(content::NavigateToURL(
       GetActiveWebContents(),
       https_server()->GetURL(kSimpleTestHost, kSimplePagePath)));
@@ -1767,7 +1838,7 @@
   histogram_tester_.ExpectUniqueSample(kWorkletNumPerPageHistogram, 1, 1);
 }
 
-IN_PROC_BROWSER_TEST_F(SharedStorageChromeBrowserTest,
+IN_PROC_BROWSER_TEST_P(SharedStorageChromeBrowserTest,
                        Run_UnexpectedCustomDataError) {
   EXPECT_TRUE(content::NavigateToURL(
       GetActiveWebContents(),
@@ -1797,18 +1868,36 @@
   histogram_tester_.ExpectUniqueSample(kWorkletNumPerPageHistogram, 1, 1);
 }
 
-IN_PROC_BROWSER_TEST_F(SharedStorageChromeBrowserTest,
+IN_PROC_BROWSER_TEST_P(SharedStorageChromeBrowserTest,
                        SelectUrl_NotLoadedError) {
   EXPECT_TRUE(content::NavigateToURL(
       GetActiveWebContents(),
       https_server()->GetURL(kSimpleTestHost, kSimplePagePath)));
 
-  content::EvalJsResult result = content::EvalJs(GetActiveWebContents(),
-                                                 R"(
-      sharedStorage.selectURL(
-          'test-url-selection-operation-1',
-          [{url: "fenced_frames/title0.html"}], {data: {}});
-    )");
+  EXPECT_TRUE(ExecJs(GetActiveWebContents(),
+                     content::JsReplace("window.resolveSelectURLToConfig = $1;",
+                                        ResolveSelectURLToConfig())));
+  content::EvalJsResult result = EvalJs(GetActiveWebContents(), R"(
+        (async function() {
+          window.select_url_result = await sharedStorage.selectURL(
+            'test-url-selection-operation-1',
+            [
+              {
+                url: "fenced_frames/title0.html"
+              }
+            ],
+            {
+              data: {},
+              resolveToConfig: resolveSelectURLToConfig
+            }
+          );
+          if (resolveSelectURLToConfig &&
+              !(select_url_result instanceof FencedFrameConfig)) {
+            throw new Error('selectURL() did not return a FencedFrameConfig.');
+          }
+          return window.select_url_result;
+        })()
+      )");
 
   EXPECT_EQ(base::StrCat({"a JavaScript error: \"Error: ",
                           "sharedStorage.worklet.addModule() has to be ",
@@ -1822,7 +1911,7 @@
       blink::SharedStorageWorkletErrorType::kSelectURLWebVisible, 1);
 }
 
-IN_PROC_BROWSER_TEST_F(SharedStorageChromeBrowserTest,
+IN_PROC_BROWSER_TEST_P(SharedStorageChromeBrowserTest,
                        SelectUrl_NotRegisteredError) {
   EXPECT_TRUE(content::NavigateToURL(
       GetActiveWebContents(),
@@ -1834,12 +1923,30 @@
       GetActiveWebContents(),
       content::JsReplace("sharedStorage.worklet.addModule($1)", script_url)));
 
-  EXPECT_TRUE(content::ExecJs(GetActiveWebContents(),
-                              R"(
-      sharedStorage.selectURL(
-          'test-url-selection-operation-1',
-          [{url: "fenced_frames/title0.html"}], {data: {}});
-    )"));
+  EXPECT_TRUE(ExecJs(GetActiveWebContents(),
+                     content::JsReplace("window.resolveSelectURLToConfig = $1;",
+                                        ResolveSelectURLToConfig())));
+  EXPECT_TRUE(content::ExecJs(GetActiveWebContents(), R"(
+        (async function() {
+          window.select_url_result = await sharedStorage.selectURL(
+            'test-url-selection-operation-1',
+            [
+              {
+                url: "fenced_frames/title0.html"
+              }
+            ],
+            {
+              data: {},
+              resolveToConfig: resolveSelectURLToConfig
+            }
+          );
+          if (resolveSelectURLToConfig &&
+              !(select_url_result instanceof FencedFrameConfig)) {
+            throw new Error('selectURL() did not return a FencedFrameConfig.');
+          }
+          return window.select_url_result;
+        })()
+      )"));
 
   // Navigate away to record `kWorkletNumPerPageHistogram` histogram.
   EXPECT_TRUE(content::NavigateToURL(GetActiveWebContents(),
@@ -1853,7 +1960,7 @@
   histogram_tester_.ExpectUniqueSample(kWorkletNumPerPageHistogram, 1, 1);
 }
 
-IN_PROC_BROWSER_TEST_F(SharedStorageChromeBrowserTest,
+IN_PROC_BROWSER_TEST_P(SharedStorageChromeBrowserTest,
                        SelectUrl_FunctionError) {
   EXPECT_TRUE(content::NavigateToURL(
       GetActiveWebContents(),
@@ -1865,12 +1972,30 @@
       GetActiveWebContents(),
       content::JsReplace("sharedStorage.worklet.addModule($1)", script_url)));
 
-  EXPECT_TRUE(content::ExecJs(GetActiveWebContents(),
-                              R"(
-      sharedStorage.selectURL(
-          'test-url-selection-operation',
-          [{url: "fenced_frames/title0.html"}], {data: {}});
-    )"));
+  EXPECT_TRUE(ExecJs(GetActiveWebContents(),
+                     content::JsReplace("window.resolveSelectURLToConfig = $1;",
+                                        ResolveSelectURLToConfig())));
+  EXPECT_TRUE(content::ExecJs(GetActiveWebContents(), R"(
+        (async function() {
+          window.select_url_result = await sharedStorage.selectURL(
+            'test-url-selection-operation',
+            [
+              {
+                url: "fenced_frames/title0.html"
+              }
+            ],
+            {
+              data: {},
+              resolveToConfig: resolveSelectURLToConfig
+            }
+          );
+          if (resolveSelectURLToConfig &&
+              !(select_url_result instanceof FencedFrameConfig)) {
+            throw new Error('selectURL() did not return a FencedFrameConfig.');
+          }
+          return window.select_url_result;
+        })()
+      )"));
 
   // Navigate away to record `kWorkletNumPerPageHistogram` histogram.
   EXPECT_TRUE(content::NavigateToURL(GetActiveWebContents(),
@@ -1884,7 +2009,7 @@
   histogram_tester_.ExpectUniqueSample(kWorkletNumPerPageHistogram, 1, 1);
 }
 
-IN_PROC_BROWSER_TEST_F(SharedStorageChromeBrowserTest,
+IN_PROC_BROWSER_TEST_P(SharedStorageChromeBrowserTest,
                        SelectUrl_NotAPromiseError) {
   EXPECT_TRUE(content::NavigateToURL(
       GetActiveWebContents(),
@@ -1896,12 +2021,30 @@
       GetActiveWebContents(),
       content::JsReplace("sharedStorage.worklet.addModule($1)", script_url)));
 
-  EXPECT_TRUE(content::ExecJs(GetActiveWebContents(),
-                              R"(
-      sharedStorage.selectURL(
-          'test-url-selection-operation',
-          [{url: "fenced_frames/title0.html"}], {data: {}});
-    )"));
+  EXPECT_TRUE(ExecJs(GetActiveWebContents(),
+                     content::JsReplace("window.resolveSelectURLToConfig = $1;",
+                                        ResolveSelectURLToConfig())));
+  EXPECT_TRUE(content::ExecJs(GetActiveWebContents(), R"(
+        (async function() {
+          window.select_url_result = await sharedStorage.selectURL(
+            'test-url-selection-operation',
+            [
+              {
+                url: "fenced_frames/title0.html"
+              }
+            ],
+            {
+              data: {},
+              resolveToConfig: resolveSelectURLToConfig
+            }
+          );
+          if (resolveSelectURLToConfig &&
+              !(select_url_result instanceof FencedFrameConfig)) {
+            throw new Error('selectURL() did not return a FencedFrameConfig.');
+          }
+          return window.select_url_result;
+        })()
+      )"));
 
   // Navigate away to record `kWorkletNumPerPageHistogram` histogram.
   EXPECT_TRUE(content::NavigateToURL(GetActiveWebContents(),
@@ -1915,7 +2058,7 @@
   histogram_tester_.ExpectUniqueSample(kWorkletNumPerPageHistogram, 1, 1);
 }
 
-IN_PROC_BROWSER_TEST_F(SharedStorageChromeBrowserTest, SelectUrl_ScriptError) {
+IN_PROC_BROWSER_TEST_P(SharedStorageChromeBrowserTest, SelectUrl_ScriptError) {
   EXPECT_TRUE(content::NavigateToURL(
       GetActiveWebContents(),
       https_server()->GetURL(kSimpleTestHost, kSimplePagePath)));
@@ -1926,12 +2069,30 @@
       GetActiveWebContents(),
       content::JsReplace("sharedStorage.worklet.addModule($1)", script_url)));
 
-  EXPECT_TRUE(content::ExecJs(GetActiveWebContents(),
-                              R"(
-      sharedStorage.selectURL(
-          'test-url-selection-operation',
-          [{url: "fenced_frames/title0.html"}], {data: {}});
-    )"));
+  EXPECT_TRUE(ExecJs(GetActiveWebContents(),
+                     content::JsReplace("window.resolveSelectURLToConfig = $1;",
+                                        ResolveSelectURLToConfig())));
+  EXPECT_TRUE(content::ExecJs(GetActiveWebContents(), R"(
+        (async function() {
+          window.select_url_result = await sharedStorage.selectURL(
+            'test-url-selection-operation',
+            [
+              {
+                url: "fenced_frames/title0.html"
+              }
+            ],
+            {
+              data: {},
+              resolveToConfig: resolveSelectURLToConfig
+            }
+          );
+          if (resolveSelectURLToConfig &&
+              !(select_url_result instanceof FencedFrameConfig)) {
+            throw new Error('selectURL() did not return a FencedFrameConfig.');
+          }
+          return window.select_url_result;
+        })()
+      )"));
 
   // Navigate away to record `kWorkletNumPerPageHistogram` histogram.
   EXPECT_TRUE(content::NavigateToURL(GetActiveWebContents(),
@@ -1945,7 +2106,7 @@
   histogram_tester_.ExpectUniqueSample(kWorkletNumPerPageHistogram, 1, 1);
 }
 
-IN_PROC_BROWSER_TEST_F(SharedStorageChromeBrowserTest,
+IN_PROC_BROWSER_TEST_P(SharedStorageChromeBrowserTest,
                        SelectUrl_UnexpectedCustomDataError) {
   EXPECT_TRUE(content::NavigateToURL(
       GetActiveWebContents(),
@@ -1957,13 +2118,30 @@
       GetActiveWebContents(),
       content::JsReplace("sharedStorage.worklet.addModule($1)", script_url)));
 
-  EXPECT_TRUE(content::ExecJs(GetActiveWebContents(),
-                              R"(
-      sharedStorage.selectURL(
-          'test-url-selection-operation',
-          [{url: "fenced_frames/title0.html"}],
-          {data: {'customField': 'customValue123'}});
-    )"));
+  EXPECT_TRUE(ExecJs(GetActiveWebContents(),
+                     content::JsReplace("window.resolveSelectURLToConfig = $1;",
+                                        ResolveSelectURLToConfig())));
+  EXPECT_TRUE(content::ExecJs(GetActiveWebContents(), R"(
+        (async function() {
+          window.select_url_result = await sharedStorage.selectURL(
+            'test-url-selection-operation',
+            [
+              {
+                url: "fenced_frames/title0.html"
+              }
+            ],
+            {
+              data: {'customField': 'customValue123'},
+              resolveToConfig: resolveSelectURLToConfig
+            }
+          );
+          if (resolveSelectURLToConfig &&
+              !(select_url_result instanceof FencedFrameConfig)) {
+            throw new Error('selectURL() did not return a FencedFrameConfig.');
+          }
+          return window.select_url_result;
+        })()
+      )"));
 
   // Navigate away to record `kWorkletNumPerPageHistogram` histogram.
   EXPECT_TRUE(content::NavigateToURL(GetActiveWebContents(),
@@ -1977,7 +2155,7 @@
   histogram_tester_.ExpectUniqueSample(kWorkletNumPerPageHistogram, 1, 1);
 }
 
-IN_PROC_BROWSER_TEST_F(SharedStorageChromeBrowserTest,
+IN_PROC_BROWSER_TEST_P(SharedStorageChromeBrowserTest,
                        SelectUrl_OutOfRangeError) {
   EXPECT_TRUE(content::NavigateToURL(
       GetActiveWebContents(),
@@ -1989,12 +2167,30 @@
       GetActiveWebContents(),
       content::JsReplace("sharedStorage.worklet.addModule($1)", script_url)));
 
-  EXPECT_TRUE(content::ExecJs(GetActiveWebContents(),
-                              R"(
-      sharedStorage.selectURL(
-          'test-url-selection-operation-1',
-          [{url: "fenced_frames/title0.html"}], {data: {}});
-    )"));
+  EXPECT_TRUE(ExecJs(GetActiveWebContents(),
+                     content::JsReplace("window.resolveSelectURLToConfig = $1;",
+                                        ResolveSelectURLToConfig())));
+  EXPECT_TRUE(content::ExecJs(GetActiveWebContents(), R"(
+        (async function() {
+          window.select_url_result = await sharedStorage.selectURL(
+            'test-url-selection-operation-1',
+            [
+              {
+                url: "fenced_frames/title0.html"
+              }
+            ],
+            {
+              data: {},
+              resolveToConfig: resolveSelectURLToConfig
+            }
+          );
+          if (resolveSelectURLToConfig &&
+              !(select_url_result instanceof FencedFrameConfig)) {
+            throw new Error('selectURL() did not return a FencedFrameConfig.');
+          }
+          return window.select_url_result;
+        })()
+      )"));
 
   // Navigate away to record `kWorkletNumPerPageHistogram` histogram.
   EXPECT_TRUE(content::NavigateToURL(GetActiveWebContents(),
@@ -2008,7 +2204,7 @@
   histogram_tester_.ExpectUniqueSample(kWorkletNumPerPageHistogram, 1, 1);
 }
 
-IN_PROC_BROWSER_TEST_F(SharedStorageChromeBrowserTest,
+IN_PROC_BROWSER_TEST_P(SharedStorageChromeBrowserTest,
                        SelectUrl_ReturnValueToIntError) {
   EXPECT_TRUE(content::NavigateToURL(
       GetActiveWebContents(),
@@ -2020,12 +2216,30 @@
       GetActiveWebContents(),
       content::JsReplace("sharedStorage.worklet.addModule($1)", script_url)));
 
-  EXPECT_TRUE(content::ExecJs(GetActiveWebContents(),
-                              R"(
-      sharedStorage.selectURL(
-          'test-url-selection-operation-2',
-          [{url: "fenced_frames/title0.html"}], {data: {}});
-    )"));
+  EXPECT_TRUE(ExecJs(GetActiveWebContents(),
+                     content::JsReplace("window.resolveSelectURLToConfig = $1;",
+                                        ResolveSelectURLToConfig())));
+  EXPECT_TRUE(content::ExecJs(GetActiveWebContents(), R"(
+        (async function() {
+          window.select_url_result = await sharedStorage.selectURL(
+            'test-url-selection-operation-2',
+            [
+              {
+                url: "fenced_frames/title0.html"
+              }
+            ],
+            {
+              data: {},
+              resolveToConfig: resolveSelectURLToConfig
+            }
+          );
+          if (resolveSelectURLToConfig &&
+              !(select_url_result instanceof FencedFrameConfig)) {
+            throw new Error('selectURL() did not return a FencedFrameConfig.');
+          }
+          return window.select_url_result;
+        })()
+      )"));
 
   // Navigate away to record `kWorkletNumPerPageHistogram` histogram.
   EXPECT_TRUE(content::NavigateToURL(GetActiveWebContents(),
@@ -2039,7 +2253,7 @@
   histogram_tester_.ExpectUniqueSample(kWorkletNumPerPageHistogram, 1, 1);
 }
 
-IN_PROC_BROWSER_TEST_F(SharedStorageChromeBrowserTest, DocumentTiming) {
+IN_PROC_BROWSER_TEST_P(SharedStorageChromeBrowserTest, DocumentTiming) {
   base::test::ScopedRunLoopTimeout timeout(FROM_HERE, base::Seconds(60));
 
   EXPECT_TRUE(content::NavigateToURL(
@@ -2076,7 +2290,7 @@
   histogram_tester_.ExpectTotalCount(kTimingDocumentClearHistogram, 1);
 }
 
-IN_PROC_BROWSER_TEST_F(SharedStorageChromeBrowserTest, WorkletTiming) {
+IN_PROC_BROWSER_TEST_P(SharedStorageChromeBrowserTest, WorkletTiming) {
   base::test::ScopedRunLoopTimeout timeout(FROM_HERE, base::Seconds(60));
 
   EXPECT_TRUE(content::NavigateToURL(
@@ -2136,7 +2350,7 @@
 }
 
 // Flaky: https://crbug.com/1406845
-IN_PROC_BROWSER_TEST_F(SharedStorageChromeBrowserTest,
+IN_PROC_BROWSER_TEST_P(SharedStorageChromeBrowserTest,
                        DISABLED_WorkletNumPerPage_Two) {
   EXPECT_TRUE(content::NavigateToURL(
       GetActiveWebContents(),
@@ -2176,7 +2390,7 @@
 }
 
 // Flaky: https://crbug.com/1406845
-IN_PROC_BROWSER_TEST_F(SharedStorageChromeBrowserTest,
+IN_PROC_BROWSER_TEST_P(SharedStorageChromeBrowserTest,
                        DISABLED_WorkletNumPerPage_Three) {
   EXPECT_TRUE(content::NavigateToURL(
       GetActiveWebContents(),
@@ -2225,24 +2439,32 @@
             histogram_tester_.GetAllSamples(kTimingWorkletSetHistogram).size());
 }
 
+INSTANTIATE_TEST_SUITE_P(All,
+                         SharedStorageChromeBrowserTest,
+                         testing::Bool(),
+                         describe_param);
+
 class SharedStorageFencedFrameChromeBrowserTest
-    : public SharedStorageChromeBrowserTest {
+    : public base::test::WithFeatureOverride,
+      public SharedStorageChromeBrowserTestBase {
  public:
-  SharedStorageFencedFrameChromeBrowserTest() {
+  SharedStorageFencedFrameChromeBrowserTest()
+      : base::test::WithFeatureOverride(
+            blink::features::kFencedFramesAPIChanges) {
     base::test::TaskEnvironment task_environment;
 
     scoped_feature_list_.InitWithFeaturesAndParameters(
         /*enabled_features=*/
         {{blink::features::kSharedStorageAPI,
           {{"SharedStorageBitBudget", base::NumberToString(kBudgetAllowed)}}},
-         {blink::features::kFencedFrames, {}},
-         {privacy_sandbox::kPrivacySandboxSettings3, {}},
-         {features::kPrivacySandboxAdsAPIsOverride, {}}},
+         {blink::features::kFencedFrames, {}}},
         /*disabled_features=*/{});
   }
 
   ~SharedStorageFencedFrameChromeBrowserTest() override = default;
 
+  bool ResolveSelectURLToConfig() override { return IsParamFeatureEnabled(); }
+
   content::RenderFrameHost* SelectURLAndCreateFencedFrame(
       content::RenderFrameHost* render_frame_host,
       bool should_add_module = true) {
@@ -2254,36 +2476,80 @@
     run_url_op_console_observer.SetFilter(
         MakeFilter({"Finish executing \'test-url-selection-operation\'"}));
 
-    content::EvalJsResult run_url_op_result =
-        content::EvalJs(render_frame_host, R"(
-      sharedStorage.selectURL(
-          'test-url-selection-operation',
-          [{url: "fenced_frames/title0.html"},
-           {url: "fenced_frames/title1.html",
-            reportingMetadata: {"click": "fenced_frames/report1.html"}},
-           {url: "fenced_frames/title2.html"}],
-          {data: {'mockResult': 1}});
-    )");
+    EXPECT_TRUE(
+        ExecJs(render_frame_host,
+               content::JsReplace("window.resolveSelectURLToConfig = $1;",
+                                  ResolveSelectURLToConfig())));
+
+    // Construct and add the `TestSelectURLFencedFrameConfigObserver` to shared
+    // storage worklet host manager.
+    content::StoragePartition* storage_partition =
+        content::ToRenderFrameHost(GetActiveWebContents())
+            .render_frame_host()
+            ->GetStoragePartition();
+    content::TestSelectURLFencedFrameConfigObserver config_observer(
+        storage_partition);
+    content::EvalJsResult run_url_op_result = EvalJs(render_frame_host, R"(
+        (async function() {
+          window.select_url_result = await sharedStorage.selectURL(
+            'test-url-selection-operation',
+            [
+              {
+                url: "fenced_frames/title0.html"
+              },
+              {
+                url: "fenced_frames/title1.html",
+                reportingMetadata:
+                {
+                  "click": "fenced_frames/report1.html"
+                }
+              },
+              {
+                url: "fenced_frames/title2.html"
+              }
+            ],
+            {
+              data: {'mockResult': 1},
+              resolveToConfig: resolveSelectURLToConfig
+            }
+          );
+          if (resolveSelectURLToConfig &&
+              !(select_url_result instanceof FencedFrameConfig)) {
+            throw new Error('selectURL() did not return a FencedFrameConfig.');
+          }
+          return window.select_url_result;
+        })()
+      )");
 
     EXPECT_TRUE(run_url_op_console_observer.Wait());
-
     EXPECT_TRUE(run_url_op_result.error.empty());
-    EXPECT_TRUE(
-        blink::IsValidUrnUuidURL(GURL(run_url_op_result.ExtractString())));
+    const absl::optional<GURL>& observed_urn_uuid =
+        config_observer.GetUrnUuid();
+    EXPECT_TRUE(observed_urn_uuid.has_value());
+    EXPECT_TRUE(blink::IsValidUrnUuidURL(observed_urn_uuid.value()));
+
+    if (!ResolveSelectURLToConfig()) {
+      EXPECT_EQ(run_url_op_result.ExtractString(), observed_urn_uuid->spec());
+    }
+
+    EXPECT_TRUE(blink::IsValidUrnUuidURL(observed_urn_uuid.value()));
     EXPECT_EQ(1u, run_url_op_console_observer.messages().size());
     EXPECT_EQ(
         "Finish executing \'test-url-selection-operation\'",
         base::UTF16ToUTF8(run_url_op_console_observer.messages()[0].message));
 
-    return content::CreateFencedFrame(render_frame_host,
-                                      GURL(run_url_op_result.ExtractString()));
+    return content::CreateFencedFrame(
+        render_frame_host,
+        ResolveSelectURLToConfig()
+            ? content::FencedFrameNavigationTarget("select_url_result")
+            : content::FencedFrameNavigationTarget(observed_urn_uuid.value()));
   }
 
  private:
   base::test::ScopedFeatureList scoped_feature_list_;
 };
 
-IN_PROC_BROWSER_TEST_F(SharedStorageFencedFrameChromeBrowserTest,
+IN_PROC_BROWSER_TEST_P(SharedStorageFencedFrameChromeBrowserTest,
                        FencedFrameNavigateTop_BudgetWithdrawal) {
   GURL main_url = https_server()->GetURL(kSimpleTestHost, kSimplePagePath);
   EXPECT_TRUE(NavigateToURL(GetActiveWebContents(), main_url));
@@ -2300,9 +2566,9 @@
 
   content::TestNavigationObserver top_navigation_observer(
       GetActiveWebContents());
-  EXPECT_TRUE(ExecJs(fenced_frame_root_node,
-                     content::JsReplace("window.open($1, '_unfencedTop')",
-                                        new_page_url.spec())));
+  EXPECT_TRUE(ExecJs(
+      fenced_frame_root_node,
+      content::JsReplace("window.open($1, '_unfencedTop')", new_page_url)));
   top_navigation_observer.Wait();
 
   content::RenderFrameHost* new_iframe =
@@ -2331,7 +2597,7 @@
   EXPECT_EQ(2, histogram_tester_.GetTotalSum(kWorkletNumPerPageHistogram));
 }
 
-IN_PROC_BROWSER_TEST_F(
+IN_PROC_BROWSER_TEST_P(
     SharedStorageFencedFrameChromeBrowserTest,
     TwoFencedFrames_DifferentURNs_EachNavigateOnce_BudgetWithdrawalTwice) {
   GURL main_url = https_server()->GetURL(kSimpleTestHost, kSimplePagePath);
@@ -2350,9 +2616,9 @@
 
   content::TestNavigationObserver top_navigation_observer1(
       GetActiveWebContents());
-  EXPECT_TRUE(ExecJs(fenced_frame_root_node1,
-                     content::JsReplace("window.open($1, '_unfencedTop')",
-                                        new_page_url1.spec())));
+  EXPECT_TRUE(ExecJs(
+      fenced_frame_root_node1,
+      content::JsReplace("window.open($1, '_unfencedTop')", new_page_url1)));
   top_navigation_observer1.Wait();
 
   content::RenderFrameHost* iframe2 =
@@ -2372,9 +2638,9 @@
 
   content::TestNavigationObserver top_navigation_observer2(
       GetActiveWebContents());
-  EXPECT_TRUE(ExecJs(fenced_frame_root_node2,
-                     content::JsReplace("window.open($1, '_unfencedTop')",
-                                        new_page_url2.spec())));
+  EXPECT_TRUE(ExecJs(
+      fenced_frame_root_node2,
+      content::JsReplace("window.open($1, '_unfencedTop')", new_page_url2)));
   top_navigation_observer2.Wait();
 
   content::RenderFrameHost* iframe3 =
@@ -2403,4 +2669,9 @@
   EXPECT_EQ(3, histogram_tester_.GetTotalSum(kWorkletNumPerPageHistogram));
 }
 
+INSTANTIATE_TEST_SUITE_P(All,
+                         SharedStorageFencedFrameChromeBrowserTest,
+                         testing::Bool(),
+                         describe_param);
+
 }  // namespace storage
diff --git a/chrome/browser/ui/android/infobars/translate_compact_infobar.cc b/chrome/browser/ui/android/infobars/translate_compact_infobar.cc
index c0828406..9e589e4 100644
--- a/chrome/browser/ui/android/infobars/translate_compact_infobar.cc
+++ b/chrome/browser/ui/android/infobars/translate_compact_infobar.cc
@@ -126,15 +126,13 @@
   if (option == translate::TranslateUtils::OPTION_SOURCE_CODE) {
     std::string source_code =
         base::android::ConvertJavaStringToUTF8(env, value);
-    if (delegate->source_language_code().compare(source_code) != 0)
-      delegate->UpdateSourceLanguage(source_code);
+    delegate->UpdateSourceLanguage(source_code);
     delegate->ReportUIInteraction(
         translate::UIInteraction::kChangeSourceLanguage);
   } else if (option == translate::TranslateUtils::OPTION_TARGET_CODE) {
     std::string target_code =
         base::android::ConvertJavaStringToUTF8(env, value);
-    if (delegate->target_language_code().compare(target_code) != 0)
-      delegate->UpdateTargetLanguage(target_code);
+    delegate->UpdateTargetLanguage(target_code);
     delegate->ReportUIInteraction(
         translate::UIInteraction::kChangeTargetLanguage);
   } else {
diff --git a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/AutocompleteController.java b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/AutocompleteController.java
index 0852e0e10..e59c399 100644
--- a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/AutocompleteController.java
+++ b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/AutocompleteController.java
@@ -10,7 +10,6 @@
 import androidx.annotation.Nullable;
 import androidx.annotation.VisibleForTesting;
 
-import org.chromium.base.LifetimeAssert;
 import org.chromium.base.annotations.CalledByNative;
 import org.chromium.base.annotations.NativeMethods;
 import org.chromium.chrome.browser.omnibox.voice.VoiceRecognitionHandler.VoiceResult;
@@ -45,7 +44,6 @@
     // Maximum number of voice suggestions to show.
     private static final int MAX_VOICE_SUGGESTION_COUNT = 3;
 
-    private final @NonNull LifetimeAssert mLifetimeAssert = LifetimeAssert.create(this);
     private final @NonNull Profile mProfile;
     private final @NonNull Set<OnSuggestionsReceivedListener> mListeners = new HashSet<>();
     private long mNativeController;
@@ -231,7 +229,6 @@
         if (mNativeController == 0) return;
         AutocompleteControllerJni.get().destroy(mNativeController);
         mNativeController = 0;
-        LifetimeAssert.setSafeToGc(mLifetimeAssert, true);
     }
 
     /**
diff --git a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/AutocompleteControllerProvider.java b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/AutocompleteControllerProvider.java
index 60f7fd1..c6d971cc 100644
--- a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/AutocompleteControllerProvider.java
+++ b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/AutocompleteControllerProvider.java
@@ -10,7 +10,6 @@
 import androidx.annotation.Nullable;
 import androidx.annotation.VisibleForTesting;
 
-import org.chromium.base.LifetimeAssert;
 import org.chromium.base.ThreadUtils;
 import org.chromium.base.UnownedUserData;
 import org.chromium.base.UnownedUserDataHost;
@@ -27,7 +26,6 @@
     private static final @NonNull UnownedUserDataKey<AutocompleteControllerProvider> KEY =
             new UnownedUserDataKey<>(AutocompleteControllerProvider.class);
     private static @Nullable AutocompleteController sControllerForTesting;
-    private final @NonNull LifetimeAssert mLifetimeAssert = LifetimeAssert.create(this);
     private final @NonNull ArrayMap<Profile, AutocompleteController> mControllers =
             new ArrayMap<>();
 
@@ -84,7 +82,6 @@
         }
         ProfileManager.removeObserver(this);
         mControllers.clear();
-        LifetimeAssert.setSafeToGc(mLifetimeAssert, true);
     }
 
     /**
diff --git a/chrome/browser/ui/android/strings/android_chrome_strings.grd b/chrome/browser/ui/android/strings/android_chrome_strings.grd
index 422e7b7e..a4dd3e45 100644
--- a/chrome/browser/ui/android/strings/android_chrome_strings.grd
+++ b/chrome/browser/ui/android/strings/android_chrome_strings.grd
@@ -5578,7 +5578,7 @@
       <message name="IDS_ACCOUNT_SELECTION_SHEET_CLOSED" desc="Accessibility string read when the Account Selection bottom sheet showing a list of the user's accounts is closed." is_accessibility_with_no_ui="true">
         Sign in bottom sheet is closed.
       </message>
-      <message name="IDS_VERIFY_SHEET_TITLE_AUTO_REAUTHN" desc="Header for verify sheet for auto re-authentication." translateable="false">
+      <message name="IDS_VERIFY_SHEET_TITLE_AUTO_REAUTHN" desc="Header for verify sheet for auto re-authentication.">
         Signing you in…
       </message>
       <message name="IDS_VERIFY_SHEET_TITLE" desc="Header for verify sheet for explicit sign-in.">
diff --git a/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_VERIFY_SHEET_TITLE_AUTO_REAUTHN.png.sha1 b/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_VERIFY_SHEET_TITLE_AUTO_REAUTHN.png.sha1
new file mode 100644
index 0000000..847f4b1
--- /dev/null
+++ b/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_VERIFY_SHEET_TITLE_AUTO_REAUTHN.png.sha1
@@ -0,0 +1 @@
+68981af90fef53db2f42d49223f5f30929d21729
\ No newline at end of file
diff --git a/chrome/browser/ui/android/toolbar/BUILD.gn b/chrome/browser/ui/android/toolbar/BUILD.gn
index fe3fcbf1..fd69c88 100644
--- a/chrome/browser/ui/android/toolbar/BUILD.gn
+++ b/chrome/browser/ui/android/toolbar/BUILD.gn
@@ -84,6 +84,7 @@
     "java/src/org/chromium/chrome/browser/toolbar/top/IncognitoSwitchViewBinder.java",
     "java/src/org/chromium/chrome/browser/toolbar/top/NavigationPopup.java",
     "java/src/org/chromium/chrome/browser/toolbar/top/OptionalBrowsingModeButtonController.java",
+    "java/src/org/chromium/chrome/browser/toolbar/top/PhoneCaptureStateToken.java",
     "java/src/org/chromium/chrome/browser/toolbar/top/ResourceFactory.java",
     "java/src/org/chromium/chrome/browser/toolbar/top/StartSurfaceTabSwitcherActionMenuCoordinator.java",
     "java/src/org/chromium/chrome/browser/toolbar/top/StartSurfaceToolbarCoordinator.java",
@@ -102,7 +103,7 @@
     "java/src/org/chromium/chrome/browser/toolbar/top/ToolbarControlContainer.java",
     "java/src/org/chromium/chrome/browser/toolbar/top/ToolbarLayout.java",
     "java/src/org/chromium/chrome/browser/toolbar/top/ToolbarPhone.java",
-    "java/src/org/chromium/chrome/browser/toolbar/top/ToolbarSnapshotState.java",
+    "java/src/org/chromium/chrome/browser/toolbar/top/ToolbarSnapshotDifference.java",
     "java/src/org/chromium/chrome/browser/toolbar/top/ToolbarTablet.java",
     "java/src/org/chromium/chrome/browser/toolbar/top/TopToolbarCoordinator.java",
     "java/src/org/chromium/chrome/browser/toolbar/top/TopToolbarInteractabilityManager.java",
@@ -309,9 +310,9 @@
     "java/src/org/chromium/chrome/browser/toolbar/optional_button/OptionalButtonViewTest.java",
     "java/src/org/chromium/chrome/browser/toolbar/top/HomeButtonCoordinatorTest.java",
     "java/src/org/chromium/chrome/browser/toolbar/top/OptionalBrowsingModeButtonControllerTest.java",
+    "java/src/org/chromium/chrome/browser/toolbar/top/PhoneCaptureStateTokenTest.java",
     "java/src/org/chromium/chrome/browser/toolbar/top/ToggleTabStackButtonCoordinatorTest.java",
     "java/src/org/chromium/chrome/browser/toolbar/top/ToolbarControlContainerTest.java",
-    "java/src/org/chromium/chrome/browser/toolbar/top/ToolbarSnapshotStateTest.java",
     "java/src/org/chromium/chrome/browser/toolbar/top/ToolbarTabletUnitTest.java",
     "java/src/org/chromium/chrome/browser/toolbar/top/TopToolbarInteractabilityManagerTest.java",
     "java/src/org/chromium/chrome/browser/toolbar/top/TopToolbarOverlayMediatorTest.java",
diff --git a/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/top/CaptureReadinessResult.java b/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/top/CaptureReadinessResult.java
index 96b93237..c8507c8 100644
--- a/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/top/CaptureReadinessResult.java
+++ b/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/top/CaptureReadinessResult.java
@@ -9,7 +9,6 @@
 import org.chromium.base.TraceEvent;
 import org.chromium.base.metrics.RecordHistogram;
 import org.chromium.chrome.browser.toolbar.ToolbarFeatures;
-import org.chromium.chrome.browser.toolbar.top.ToolbarSnapshotState.ToolbarSnapshotDifference;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
diff --git a/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/top/ToolbarSnapshotState.java b/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/top/PhoneCaptureStateToken.java
similarity index 73%
rename from chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/top/ToolbarSnapshotState.java
rename to chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/top/PhoneCaptureStateToken.java
index 7ece667c..01f2e96 100644
--- a/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/top/ToolbarSnapshotState.java
+++ b/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/top/PhoneCaptureStateToken.java
@@ -9,14 +9,11 @@
 
 import androidx.annotation.ColorInt;
 import androidx.annotation.DrawableRes;
-import androidx.annotation.IntDef;
 import androidx.annotation.Nullable;
 
 import org.chromium.chrome.browser.toolbar.ButtonData;
 import org.chromium.chrome.browser.toolbar.top.ToolbarPhone.VisualState;
 
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
 import java.util.Objects;
 
 /**
@@ -24,40 +21,7 @@
  * against new states, to infer if anything important has changed. Especially useful when deciding
  * if a new bitmap capture is warranted.
  */
-public class ToolbarSnapshotState {
-    /**
-     * Reasons that two snapshots are different. Treat this list as append only and keep it in sync
-     * with ToolbarSnapshotDifference in enums.xml, as well as the proto in
-     * chrome_track_event.proto.
-     **/
-    @IntDef({ToolbarSnapshotDifference.NONE, ToolbarSnapshotDifference.NULL,
-            ToolbarSnapshotDifference.TINT, ToolbarSnapshotDifference.TAB_COUNT,
-            ToolbarSnapshotDifference.OPTIONAL_BUTTON_DATA, ToolbarSnapshotDifference.VISUAL_STATE,
-            ToolbarSnapshotDifference.SECURITY_ICON, ToolbarSnapshotDifference.SHOWING_UPDATE_BADGE,
-            ToolbarSnapshotDifference.PAINT_PREVIEW, ToolbarSnapshotDifference.PROGRESS,
-            ToolbarSnapshotDifference.LOCATION_BAR_WIDTH, ToolbarSnapshotDifference.URL_TEXT,
-            ToolbarSnapshotDifference.HOME_BUTTON_COLOR, ToolbarSnapshotDifference.TITLE_TEXT,
-            ToolbarSnapshotDifference.CCT_ANIMATION, ToolbarSnapshotDifference.NUM_ENTRIES})
-    @Retention(RetentionPolicy.SOURCE)
-    public @interface ToolbarSnapshotDifference {
-        int NONE = 0;
-        int NULL = 1;
-        int TINT = 2;
-        int TAB_COUNT = 3;
-        int OPTIONAL_BUTTON_DATA = 4;
-        int VISUAL_STATE = 5;
-        int SECURITY_ICON = 6;
-        int SHOWING_UPDATE_BADGE = 7;
-        int PAINT_PREVIEW = 8;
-        int PROGRESS = 9;
-        int LOCATION_BAR_WIDTH = 10;
-        int URL_TEXT = 11;
-        int HOME_BUTTON_COLOR = 12;
-        int TITLE_TEXT = 13;
-        int CCT_ANIMATION = 14;
-        int NUM_ENTRIES = 15;
-    }
-
+class PhoneCaptureStateToken {
     private final @ColorInt int mTint;
     private final int mTabCount;
     private final ButtonData mOptionalButtonData;
@@ -71,7 +35,7 @@
     private final boolean mIsPaintPreview;
     private final int mUnfocusedLocationBarLayoutWidth;
 
-    public ToolbarSnapshotState(@ColorInt int tint, int tabCount, ButtonData optionalButtonData,
+    public PhoneCaptureStateToken(@ColorInt int tint, int tabCount, ButtonData optionalButtonData,
             @VisualState int visualState, String urlText,
             @Nullable CharSequence visibleTextPrefixHint, @DrawableRes int securityIcon,
             ColorStateList colorStateList, boolean isShowingUpdateBadgeDuringLastCapture,
@@ -100,7 +64,7 @@
      * @param that The other snapshot to compare against.
      * @return The difference.
      */
-    public @ToolbarSnapshotDifference int getAnyDifference(ToolbarSnapshotState that) {
+    public @ToolbarSnapshotDifference int getAnyDifference(PhoneCaptureStateToken that) {
         if (that == null) {
             return ToolbarSnapshotDifference.NULL;
         } else if (mTint != that.mTint) {
@@ -131,7 +95,7 @@
         return ToolbarSnapshotDifference.NONE;
     }
 
-    private boolean isVisibleUrlTextSame(ToolbarSnapshotState that) {
+    private boolean isVisibleUrlTextSame(PhoneCaptureStateToken that) {
         if (mVisibleTextPrefixHint != null
                 && TextUtils.equals(mVisibleTextPrefixHint, that.mVisibleTextPrefixHint)) {
             return true;
diff --git a/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/top/PhoneCaptureStateTokenTest.java b/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/top/PhoneCaptureStateTokenTest.java
new file mode 100644
index 0000000..379d8ff
--- /dev/null
+++ b/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/top/PhoneCaptureStateTokenTest.java
@@ -0,0 +1,340 @@
+// 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.
+
+package org.chromium.chrome.browser.toolbar.top;
+
+import android.content.res.ColorStateList;
+import android.graphics.Color;
+
+import androidx.annotation.ColorInt;
+import androidx.annotation.DrawableRes;
+import androidx.annotation.Nullable;
+
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import org.chromium.base.test.BaseRobolectricTestRunner;
+import org.chromium.chrome.browser.toolbar.ButtonData;
+import org.chromium.chrome.browser.toolbar.ButtonDataImpl;
+import org.chromium.chrome.browser.toolbar.top.ToolbarPhone.VisualState;
+
+/** Unit tests for {@link PhoneCaptureStateToken}. */
+@RunWith(BaseRobolectricTestRunner.class)
+public class PhoneCaptureStateTokenTest {
+    private static final @ColorInt int DEFAULT_TINT = Color.TRANSPARENT;
+    private static final int DEFAULT_TAB_COUNT = 1;
+    private static final ButtonData DEFAULT_BUTTON_DATA = makeButtonDate();
+    private static final @VisualState int DEFAULT_VISUAL_STATE = VisualState.NORMAL;
+    private static final String DEFAULT_URL_TEXT = "https://www.example.com/";
+    private static final CharSequence DEFAULT_URL_HINT_TEXT = null;
+    private static final @DrawableRes int DEFAULT_SECURITY_ICON = 0;
+    private static final boolean DEFAULT_IS_SHOWING_UPDATE_BADGE_DURING_LAST_CAPTURE = false;
+    private static final boolean DEFAULT_IS_PAINT_PREVIEW = false;
+    private static final float DEFAULT_PROGRESS = 0.1f;
+    private static final int DEFAULT_UNFOCUSED_LOCATION_BAR_LAYOUT_WIDTH = 2;
+
+    // Not static/final because they're initialized in #before(). Apparently ColorStateList.valueOf
+    // calls into Android native code, and cannot be done too early.
+    private ColorStateList mDefaultColorStateList;
+    private PhoneCaptureStateToken mDefaultPhoneCaptureStateToken;
+
+    private static ButtonData makeButtonDate() {
+        // Uses default equals impl, reference quality, to compare. Values do not matter.
+        return new ButtonDataImpl(false, null, null, "", false, null, false, 0);
+    }
+
+    @Before
+    public void before() {
+        mDefaultColorStateList = ColorStateList.valueOf(DEFAULT_TINT);
+        mDefaultPhoneCaptureStateToken = new PhoneCustomTabCaptureStateTokenBuilder().build();
+    }
+
+    @Test
+    public void testSameSnapshots() {
+        PhoneCaptureStateToken otherPhoneCaptureStateToken =
+                new PhoneCustomTabCaptureStateTokenBuilder().build();
+        Assert.assertEquals(ToolbarSnapshotDifference.NONE,
+                otherPhoneCaptureStateToken.getAnyDifference(mDefaultPhoneCaptureStateToken));
+    }
+
+    @Test
+    public void testDifferentTint() {
+        PhoneCaptureStateToken otherPhoneCaptureStateToken =
+                new PhoneCustomTabCaptureStateTokenBuilder().setTint(Color.RED).build();
+        Assert.assertEquals(ToolbarSnapshotDifference.TINT,
+                otherPhoneCaptureStateToken.getAnyDifference(mDefaultPhoneCaptureStateToken));
+    }
+
+    @Test
+    public void testDifferentTabCount() {
+        PhoneCaptureStateToken otherPhoneCaptureStateToken =
+                new PhoneCustomTabCaptureStateTokenBuilder().setTabCount(2).build();
+        Assert.assertEquals(ToolbarSnapshotDifference.TAB_COUNT,
+                otherPhoneCaptureStateToken.getAnyDifference(mDefaultPhoneCaptureStateToken));
+    }
+
+    @Test
+    public void testDifferentOptionalButtonData() {
+        PhoneCaptureStateToken otherPhoneCaptureStateToken =
+                new PhoneCustomTabCaptureStateTokenBuilder()
+                        .setOptionalButtonData(makeButtonDate())
+                        .build();
+        Assert.assertEquals(ToolbarSnapshotDifference.OPTIONAL_BUTTON_DATA,
+                otherPhoneCaptureStateToken.getAnyDifference(mDefaultPhoneCaptureStateToken));
+    }
+
+    @Test
+    public void testDifferentVisualState() {
+        PhoneCaptureStateToken otherPhoneCaptureStateToken =
+                new PhoneCustomTabCaptureStateTokenBuilder()
+                        .setVisualState(VisualState.INCOGNITO)
+                        .build();
+        Assert.assertEquals(ToolbarSnapshotDifference.VISUAL_STATE,
+                otherPhoneCaptureStateToken.getAnyDifference(mDefaultPhoneCaptureStateToken));
+    }
+
+    @Test
+    public void testDifferentUrlText() {
+        PhoneCaptureStateToken otherPhoneCaptureStateToken =
+                new PhoneCustomTabCaptureStateTokenBuilder()
+                        .setUrlText("https://www.other.com/")
+                        .build();
+        Assert.assertEquals(ToolbarSnapshotDifference.URL_TEXT,
+                otherPhoneCaptureStateToken.getAnyDifference(mDefaultPhoneCaptureStateToken));
+    }
+
+    @Test
+    public void testDifferentUrlText_SameHintText() {
+        PhoneCaptureStateToken initialPhoneCaptureStateToken =
+                new PhoneCustomTabCaptureStateTokenBuilder()
+                        .setVisibleTextPrefixHint(DEFAULT_URL_TEXT)
+                        .build();
+        PhoneCaptureStateToken otherPhoneCaptureStateToken =
+                new PhoneCustomTabCaptureStateTokenBuilder()
+                        .setUrlText(DEFAULT_URL_TEXT + "additional/paths/")
+                        .setVisibleTextPrefixHint(DEFAULT_URL_TEXT)
+                        .build();
+        Assert.assertEquals(ToolbarSnapshotDifference.NONE,
+                initialPhoneCaptureStateToken.getAnyDifference(otherPhoneCaptureStateToken));
+    }
+
+    @Test
+    public void testSameUrlText_DifferentHintText() {
+        PhoneCaptureStateToken initialPhoneCaptureStateToken =
+                new PhoneCustomTabCaptureStateTokenBuilder()
+                        .setVisibleTextPrefixHint(DEFAULT_URL_TEXT.substring(0, 2))
+                        .build();
+        PhoneCaptureStateToken otherPhoneCaptureStateToken =
+                new PhoneCustomTabCaptureStateTokenBuilder()
+                        .setVisibleTextPrefixHint(DEFAULT_URL_TEXT.substring(0, 3))
+                        .build();
+        Assert.assertEquals(ToolbarSnapshotDifference.NONE,
+                initialPhoneCaptureStateToken.getAnyDifference(otherPhoneCaptureStateToken));
+    }
+
+    @Test
+    public void testSameUrlText_BothNullHintText() {
+        PhoneCaptureStateToken initialPhoneCaptureStateToken =
+                new PhoneCustomTabCaptureStateTokenBuilder().setVisibleTextPrefixHint(null).build();
+        PhoneCaptureStateToken otherPhoneCaptureStateToken =
+                new PhoneCustomTabCaptureStateTokenBuilder().setVisibleTextPrefixHint(null).build();
+        Assert.assertEquals(ToolbarSnapshotDifference.NONE,
+                initialPhoneCaptureStateToken.getAnyDifference(otherPhoneCaptureStateToken));
+    }
+
+    @Test
+    public void testSameUrlText_NullHintText() {
+        PhoneCaptureStateToken initialPhoneCaptureStateToken =
+                new PhoneCustomTabCaptureStateTokenBuilder().setVisibleTextPrefixHint(null).build();
+        PhoneCaptureStateToken otherPhoneCaptureStateToken =
+                new PhoneCustomTabCaptureStateTokenBuilder()
+                        .setVisibleTextPrefixHint(DEFAULT_URL_TEXT)
+                        .build();
+        Assert.assertEquals(ToolbarSnapshotDifference.NONE,
+                initialPhoneCaptureStateToken.getAnyDifference(otherPhoneCaptureStateToken));
+        Assert.assertEquals(ToolbarSnapshotDifference.NONE,
+                otherPhoneCaptureStateToken.getAnyDifference(initialPhoneCaptureStateToken));
+    }
+
+    @Test
+    public void testDifferentSecurityIcon() {
+        PhoneCaptureStateToken otherPhoneCaptureStateToken =
+                new PhoneCustomTabCaptureStateTokenBuilder().setSecurityIcon(-1).build();
+        Assert.assertEquals(ToolbarSnapshotDifference.SECURITY_ICON,
+                otherPhoneCaptureStateToken.getAnyDifference(mDefaultPhoneCaptureStateToken));
+    }
+
+    @Test
+    public void testDifferentColorStateList() {
+        PhoneCaptureStateToken otherPhoneCaptureStateToken =
+                new PhoneCustomTabCaptureStateTokenBuilder()
+                        .setColorStateList(ColorStateList.valueOf(Color.RED))
+                        .build();
+        Assert.assertEquals(ToolbarSnapshotDifference.HOME_BUTTON_COLOR,
+                otherPhoneCaptureStateToken.getAnyDifference(mDefaultPhoneCaptureStateToken));
+    }
+
+    @Test
+    public void testSameColorStateList() {
+        // Create the ColorStateList by hand. ColorStateList.valueOf will inconsistently reuse
+        // objects, but this ColorStateList should never have reference equality with the default.
+        ColorStateList colorStateList =
+                new ColorStateList(new int[][] {new int[] {}}, new int[] {DEFAULT_TINT});
+        Assert.assertNotEquals(mDefaultColorStateList, colorStateList);
+
+        PhoneCaptureStateToken otherPhoneCaptureStateToken =
+                new PhoneCustomTabCaptureStateTokenBuilder()
+                        .setColorStateList(colorStateList)
+                        .build();
+        Assert.assertEquals(ToolbarSnapshotDifference.NONE,
+                otherPhoneCaptureStateToken.getAnyDifference(mDefaultPhoneCaptureStateToken));
+    }
+
+    @Test
+    public void testDifferentIsShowingUpdateBadgeDuringLastCapture() {
+        PhoneCaptureStateToken otherPhoneCaptureStateToken =
+                new PhoneCustomTabCaptureStateTokenBuilder()
+                        .setIsShowingUpdateBadgeDuringLastCapture(true)
+                        .build();
+        Assert.assertEquals(ToolbarSnapshotDifference.SHOWING_UPDATE_BADGE,
+                otherPhoneCaptureStateToken.getAnyDifference(mDefaultPhoneCaptureStateToken));
+    }
+
+    @Test
+    public void testDifferentIsPaintPreview() {
+        PhoneCaptureStateToken otherPhoneCaptureStateToken =
+                new PhoneCustomTabCaptureStateTokenBuilder().setIsPaintPreview(true).build();
+        Assert.assertEquals(ToolbarSnapshotDifference.PAINT_PREVIEW,
+                otherPhoneCaptureStateToken.getAnyDifference(mDefaultPhoneCaptureStateToken));
+    }
+
+    @Test
+    public void testDifferentProgress() {
+        PhoneCaptureStateToken otherPhoneCaptureStateToken =
+                new PhoneCustomTabCaptureStateTokenBuilder().setProgress(0.2f).build();
+        Assert.assertEquals(ToolbarSnapshotDifference.NONE,
+                otherPhoneCaptureStateToken.getAnyDifference(mDefaultPhoneCaptureStateToken));
+    }
+
+    @Test
+    public void testDifferentUnfocusedLocationBarLayoutWidth() {
+        PhoneCaptureStateToken otherPhoneCaptureStateToken =
+                new PhoneCustomTabCaptureStateTokenBuilder()
+                        .setUnfocusedLocationBarLayoutWidth(100)
+                        .build();
+        Assert.assertEquals(ToolbarSnapshotDifference.LOCATION_BAR_WIDTH,
+                otherPhoneCaptureStateToken.getAnyDifference(mDefaultPhoneCaptureStateToken));
+    }
+
+    @Test
+    public void testIsValidVisibleTextPrefixHint() {
+        Assert.assertFalse(PhoneCaptureStateToken.isValidVisibleTextPrefixHint(null, null));
+        Assert.assertFalse(PhoneCaptureStateToken.isValidVisibleTextPrefixHint("foo", null));
+        Assert.assertFalse(PhoneCaptureStateToken.isValidVisibleTextPrefixHint(null, "foo"));
+
+        Assert.assertFalse(PhoneCaptureStateToken.isValidVisibleTextPrefixHint("", ""));
+        Assert.assertFalse(PhoneCaptureStateToken.isValidVisibleTextPrefixHint("foo", ""));
+
+        Assert.assertFalse(PhoneCaptureStateToken.isValidVisibleTextPrefixHint("foo", "fooo"));
+        Assert.assertFalse(PhoneCaptureStateToken.isValidVisibleTextPrefixHint("foo", "foo/"));
+        Assert.assertFalse(PhoneCaptureStateToken.isValidVisibleTextPrefixHint("foo", "o/"));
+        Assert.assertFalse(PhoneCaptureStateToken.isValidVisibleTextPrefixHint("foo", "oo"));
+
+        Assert.assertTrue(PhoneCaptureStateToken.isValidVisibleTextPrefixHint("foo.com", "foo"));
+        Assert.assertTrue(
+                PhoneCaptureStateToken.isValidVisibleTextPrefixHint("foo.com", "foo.com"));
+    }
+
+    private class PhoneCustomTabCaptureStateTokenBuilder {
+        private @ColorInt int mTint = DEFAULT_TINT;
+        private int mTabCount = DEFAULT_TAB_COUNT;
+        private ButtonData mOptionalButtonData = DEFAULT_BUTTON_DATA;
+        private @VisualState int mVisualState = DEFAULT_VISUAL_STATE;
+        private String mUrlText = DEFAULT_URL_TEXT;
+        @Nullable
+        private CharSequence mVisibleTextPrefixHint = DEFAULT_URL_HINT_TEXT;
+        private @DrawableRes int mSecurityIcon = DEFAULT_SECURITY_ICON;
+        private ColorStateList mColorStateList = mDefaultColorStateList;
+        private boolean mIsShowingUpdateBadgeDuringLastCapture =
+                DEFAULT_IS_SHOWING_UPDATE_BADGE_DURING_LAST_CAPTURE;
+        private boolean mIsPaintPreview = DEFAULT_IS_PAINT_PREVIEW;
+        private float mProgress = DEFAULT_PROGRESS;
+        private int mUnfocusedLocationBarLayoutWidth = DEFAULT_UNFOCUSED_LOCATION_BAR_LAYOUT_WIDTH;
+
+        public PhoneCustomTabCaptureStateTokenBuilder setTint(@ColorInt int tint) {
+            mTint = tint;
+            return this;
+        }
+
+        public PhoneCustomTabCaptureStateTokenBuilder setTabCount(int tabCount) {
+            mTabCount = tabCount;
+            return this;
+        }
+
+        public PhoneCustomTabCaptureStateTokenBuilder setOptionalButtonData(
+                ButtonData optionalButtonData) {
+            mOptionalButtonData = optionalButtonData;
+            return this;
+        }
+
+        public PhoneCustomTabCaptureStateTokenBuilder setVisualState(@VisualState int visualState) {
+            mVisualState = visualState;
+            return this;
+        }
+
+        public PhoneCustomTabCaptureStateTokenBuilder setUrlText(String urlText) {
+            mUrlText = urlText;
+            return this;
+        }
+
+        public PhoneCustomTabCaptureStateTokenBuilder setVisibleTextPrefixHint(
+                CharSequence visibleTextPrefixHint) {
+            mVisibleTextPrefixHint = visibleTextPrefixHint;
+            return this;
+        }
+
+        public PhoneCustomTabCaptureStateTokenBuilder setSecurityIcon(
+                @DrawableRes int securityIcon) {
+            mSecurityIcon = securityIcon;
+            return this;
+        }
+
+        public PhoneCustomTabCaptureStateTokenBuilder setColorStateList(
+                ColorStateList colorStateList) {
+            mColorStateList = colorStateList;
+            return this;
+        }
+
+        public PhoneCustomTabCaptureStateTokenBuilder setIsShowingUpdateBadgeDuringLastCapture(
+                boolean isShowingUpdateBadgeDuringLastCapture) {
+            mIsShowingUpdateBadgeDuringLastCapture = isShowingUpdateBadgeDuringLastCapture;
+            return this;
+        }
+
+        public PhoneCustomTabCaptureStateTokenBuilder setIsPaintPreview(boolean isPaintPreview) {
+            mIsPaintPreview = isPaintPreview;
+            return this;
+        }
+
+        public PhoneCustomTabCaptureStateTokenBuilder setProgress(float progress) {
+            mProgress = progress;
+            return this;
+        }
+
+        public PhoneCustomTabCaptureStateTokenBuilder setUnfocusedLocationBarLayoutWidth(
+                int unfocusedLocationBarLayoutWidth) {
+            mUnfocusedLocationBarLayoutWidth = unfocusedLocationBarLayoutWidth;
+            return this;
+        }
+
+        public PhoneCaptureStateToken build() {
+            return new PhoneCaptureStateToken(mTint, mTabCount, mOptionalButtonData, mVisualState,
+                    mUrlText, mVisibleTextPrefixHint, mSecurityIcon, mColorStateList,
+                    mIsShowingUpdateBadgeDuringLastCapture, mIsPaintPreview, mProgress,
+                    mUnfocusedLocationBarLayoutWidth);
+        }
+    }
+}
diff --git a/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/top/ToolbarControlContainerTest.java b/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/top/ToolbarControlContainerTest.java
index 29b0b83d..671fa847 100644
--- a/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/top/ToolbarControlContainerTest.java
+++ b/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/top/ToolbarControlContainerTest.java
@@ -35,7 +35,6 @@
 import org.chromium.chrome.browser.toolbar.top.CaptureReadinessResult.TopToolbarBlockCaptureReason;
 import org.chromium.chrome.browser.toolbar.top.ToolbarControlContainer.ToolbarViewResourceAdapter;
 import org.chromium.chrome.browser.toolbar.top.ToolbarControlContainer.ToolbarViewResourceAdapter.ToolbarInMotionStage;
-import org.chromium.chrome.browser.toolbar.top.ToolbarSnapshotState.ToolbarSnapshotDifference;
 import org.chromium.chrome.test.util.browser.Features.DisableFeatures;
 import org.chromium.chrome.test.util.browser.Features.EnableFeatures;
 import org.chromium.chrome.test.util.browser.Features.JUnitProcessor;
diff --git a/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/top/ToolbarPhone.java b/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/top/ToolbarPhone.java
index 94a1065..e92f9cc 100644
--- a/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/top/ToolbarPhone.java
+++ b/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/top/ToolbarPhone.java
@@ -75,7 +75,6 @@
 import org.chromium.chrome.browser.toolbar.optional_button.OptionalButtonCoordinator;
 import org.chromium.chrome.browser.toolbar.optional_button.OptionalButtonCoordinator.TransitionType;
 import org.chromium.chrome.browser.toolbar.top.CaptureReadinessResult.TopToolbarBlockCaptureReason;
-import org.chromium.chrome.browser.toolbar.top.ToolbarSnapshotState.ToolbarSnapshotDifference;
 import org.chromium.chrome.browser.toolbar.top.TopToolbarCoordinator.UrlExpansionObserver;
 import org.chromium.chrome.browser.ui.theme.BrandedColorScheme;
 import org.chromium.chrome.browser.user_education.UserEducationHelper;
@@ -238,7 +237,7 @@
 
     /** Whether the toolbar has a pending request to call {@link triggerUrlFocusAnimation()}. */
     private boolean mPendingTriggerUrlFocusRequest;
-    private ToolbarSnapshotState mToolbarSnapshotState;
+    private PhoneCaptureStateToken mPhoneCaptureStateToken;
     private ButtonData mButtonData;
     /**
      * Whether the tab switcher is currently showing and controlled by the start surface. For
@@ -1568,9 +1567,9 @@
         } else if (isInTabSwitcherMode() || mIsShowingStartSurfaceTabSwitcher) {
             return CaptureReadinessResult.notReady(TopToolbarBlockCaptureReason.TAB_SWITCHER_MODE);
         } else {
-            ToolbarSnapshotState newSnapshotState = generateToolbarSnapshotState();
+            PhoneCaptureStateToken newSnapshotState = generateToolbarSnapshotState();
             @ToolbarSnapshotDifference
-            int snapshotDifference = newSnapshotState.getAnyDifference(mToolbarSnapshotState);
+            int snapshotDifference = newSnapshotState.getAnyDifference(mPhoneCaptureStateToken);
             if (snapshotDifference == ToolbarSnapshotDifference.NONE) {
                 return CaptureReadinessResult.notReady(TopToolbarBlockCaptureReason.SNAPSHOT_SAME);
             } else {
@@ -1584,15 +1583,15 @@
         if (forceTextureCapture) {
             // Only force a texture capture if the tint for the toolbar drawables is changing or
             // if the tab count has changed since the last texture capture.
-            if (mToolbarSnapshotState == null) {
-                mToolbarSnapshotState = generateToolbarSnapshotState();
+            if (mPhoneCaptureStateToken == null) {
+                mPhoneCaptureStateToken = generateToolbarSnapshotState();
             }
 
-            mForceTextureCapture = mToolbarSnapshotState.getTint() != getTint().getDefaultColor();
+            mForceTextureCapture = mPhoneCaptureStateToken.getTint() != getTint().getDefaultColor();
 
             if (mTabSwitcherAnimationTabStackDrawable != null && mToggleTabStackButton != null) {
                 mForceTextureCapture = mForceTextureCapture
-                        || mToolbarSnapshotState.getTabCount()
+                        || mPhoneCaptureStateToken.getTabCount()
                                 != mTabSwitcherAnimationTabStackDrawable.getTabCount();
             }
 
@@ -1603,7 +1602,7 @@
         return false;
     }
 
-    private ToolbarSnapshotState generateToolbarSnapshotState() {
+    private PhoneCaptureStateToken generateToolbarSnapshotState() {
         UrlBarData urlBarData;
         int securityIconResource;
         if (ToolbarFeatures.shouldSuppressCaptures()) {
@@ -1621,8 +1620,8 @@
         String displayedUrlText = urlBarData.displayText.toString();
         CharSequence prefixHint = mLocationBar.getOmniboxVisibleTextPrefixHint();
         boolean isValidPrefixHint =
-                ToolbarSnapshotState.isValidVisibleTextPrefixHint(displayedUrlText, prefixHint);
-        return new ToolbarSnapshotState(getTint().getDefaultColor(),
+                PhoneCaptureStateToken.isValidVisibleTextPrefixHint(displayedUrlText, prefixHint);
+        return new PhoneCaptureStateToken(getTint().getDefaultColor(),
                 mTabCountProvider.getTabCount(), mButtonData, mVisualState, displayedUrlText,
                 isValidPrefixHint ? prefixHint : null, securityIconResource,
                 ImageViewCompat.getImageTintList(mHomeButton),
@@ -1737,7 +1736,7 @@
             // When texture mode is turned off, we know a capture has just been completed. Update
             // our snapshot so that we can suppress correctly on the next
             // #isReadyForTextureCapture() call.
-            mToolbarSnapshotState = generateToolbarSnapshotState();
+            mPhoneCaptureStateToken = generateToolbarSnapshotState();
         }
     }
 
diff --git a/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/top/ToolbarSnapshotDifference.java b/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/top/ToolbarSnapshotDifference.java
new file mode 100644
index 0000000..89e7db5
--- /dev/null
+++ b/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/top/ToolbarSnapshotDifference.java
@@ -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.
+
+package org.chromium.chrome.browser.toolbar.top;
+
+import androidx.annotation.IntDef;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Reasons that two toolbar snapshots are different. Contains a superset of differences and each
+ * toolbar instance will only be able to report a subset. Treat this list as append only and keep
+ * it in sync with ToolbarSnapshotDifference in enums.xml, as well as the proto in
+ * chrome_track_event.proto.
+ **/
+@IntDef({ToolbarSnapshotDifference.NONE, ToolbarSnapshotDifference.NULL,
+        ToolbarSnapshotDifference.TINT, ToolbarSnapshotDifference.TAB_COUNT,
+        ToolbarSnapshotDifference.OPTIONAL_BUTTON_DATA, ToolbarSnapshotDifference.VISUAL_STATE,
+        ToolbarSnapshotDifference.SECURITY_ICON, ToolbarSnapshotDifference.SHOWING_UPDATE_BADGE,
+        ToolbarSnapshotDifference.PAINT_PREVIEW, ToolbarSnapshotDifference.PROGRESS,
+        ToolbarSnapshotDifference.LOCATION_BAR_WIDTH, ToolbarSnapshotDifference.URL_TEXT,
+        ToolbarSnapshotDifference.HOME_BUTTON_COLOR, ToolbarSnapshotDifference.TITLE_TEXT,
+        ToolbarSnapshotDifference.CCT_ANIMATION, ToolbarSnapshotDifference.NUM_ENTRIES})
+@Retention(RetentionPolicy.SOURCE)
+public @interface ToolbarSnapshotDifference {
+    int NONE = 0;
+    int NULL = 1;
+    int TINT = 2;
+    int TAB_COUNT = 3;
+    int OPTIONAL_BUTTON_DATA = 4;
+    int VISUAL_STATE = 5;
+    int SECURITY_ICON = 6;
+    int SHOWING_UPDATE_BADGE = 7;
+    int PAINT_PREVIEW = 8;
+    int PROGRESS = 9;
+    int LOCATION_BAR_WIDTH = 10;
+    int URL_TEXT = 11;
+    int HOME_BUTTON_COLOR = 12;
+    int TITLE_TEXT = 13;
+    int CCT_ANIMATION = 14;
+    int NUM_ENTRIES = 15;
+}
\ No newline at end of file
diff --git a/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/top/ToolbarSnapshotStateTest.java b/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/top/ToolbarSnapshotStateTest.java
deleted file mode 100644
index 06f2bd1..0000000
--- a/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/top/ToolbarSnapshotStateTest.java
+++ /dev/null
@@ -1,326 +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.
-
-package org.chromium.chrome.browser.toolbar.top;
-
-import android.content.res.ColorStateList;
-import android.graphics.Color;
-
-import androidx.annotation.ColorInt;
-import androidx.annotation.DrawableRes;
-import androidx.annotation.Nullable;
-
-import org.junit.Assert;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import org.chromium.base.test.BaseRobolectricTestRunner;
-import org.chromium.chrome.browser.toolbar.ButtonData;
-import org.chromium.chrome.browser.toolbar.ButtonDataImpl;
-import org.chromium.chrome.browser.toolbar.top.ToolbarPhone.VisualState;
-import org.chromium.chrome.browser.toolbar.top.ToolbarSnapshotState.ToolbarSnapshotDifference;
-
-/** Unit tests for {@link ToolbarSnapshotState}. */
-@RunWith(BaseRobolectricTestRunner.class)
-public class ToolbarSnapshotStateTest {
-    private static final @ColorInt int DEFAULT_TINT = Color.TRANSPARENT;
-    private static final int DEFAULT_TAB_COUNT = 1;
-    private static final ButtonData DEFAULT_BUTTON_DATA = makeButtonDate();
-    private static final @VisualState int DEFAULT_VISUAL_STATE = VisualState.NORMAL;
-    private static final String DEFAULT_URL_TEXT = "https://www.example.com/";
-    private static final CharSequence DEFAULT_URL_HINT_TEXT = null;
-    private static final @DrawableRes int DEFAULT_SECURITY_ICON = 0;
-    private static final boolean DEFAULT_IS_SHOWING_UPDATE_BADGE_DURING_LAST_CAPTURE = false;
-    private static final boolean DEFAULT_IS_PAINT_PREVIEW = false;
-    private static final float DEFAULT_PROGRESS = 0.1f;
-    private static final int DEFAULT_UNFOCUSED_LOCATION_BAR_LAYOUT_WIDTH = 2;
-
-    // Not static/final because they're initialized in #before(). Apparently ColorStateList.valueOf
-    // calls into Android native code, and cannot be done too early.
-    private ColorStateList mDefaultColorStateList;
-    private ToolbarSnapshotState mDefaultToolbarSnapshotState;
-
-    private static ButtonData makeButtonDate() {
-        // Uses default equals impl, reference quality, to compare. Values do not matter.
-        return new ButtonDataImpl(false, null, null, "", false, null, false, 0);
-    }
-
-    @Before
-    public void before() {
-        mDefaultColorStateList = ColorStateList.valueOf(DEFAULT_TINT);
-        mDefaultToolbarSnapshotState = new ToolbarSnapshotStateBuilder().build();
-    }
-
-    @Test
-    public void testSameSnapshots() {
-        ToolbarSnapshotState otherToolbarSnapshotState = new ToolbarSnapshotStateBuilder().build();
-        Assert.assertEquals(ToolbarSnapshotDifference.NONE,
-                otherToolbarSnapshotState.getAnyDifference(mDefaultToolbarSnapshotState));
-    }
-
-    @Test
-    public void testDifferentTint() {
-        ToolbarSnapshotState otherToolbarSnapshotState =
-                new ToolbarSnapshotStateBuilder().setTint(Color.RED).build();
-        Assert.assertEquals(ToolbarSnapshotDifference.TINT,
-                otherToolbarSnapshotState.getAnyDifference(mDefaultToolbarSnapshotState));
-    }
-
-    @Test
-    public void testDifferentTabCount() {
-        ToolbarSnapshotState otherToolbarSnapshotState =
-                new ToolbarSnapshotStateBuilder().setTabCount(2).build();
-        Assert.assertEquals(ToolbarSnapshotDifference.TAB_COUNT,
-                otherToolbarSnapshotState.getAnyDifference(mDefaultToolbarSnapshotState));
-    }
-
-    @Test
-    public void testDifferentOptionalButtonData() {
-        ToolbarSnapshotState otherToolbarSnapshotState =
-                new ToolbarSnapshotStateBuilder().setOptionalButtonData(makeButtonDate()).build();
-        Assert.assertEquals(ToolbarSnapshotDifference.OPTIONAL_BUTTON_DATA,
-                otherToolbarSnapshotState.getAnyDifference(mDefaultToolbarSnapshotState));
-    }
-
-    @Test
-    public void testDifferentVisualState() {
-        ToolbarSnapshotState otherToolbarSnapshotState =
-                new ToolbarSnapshotStateBuilder().setVisualState(VisualState.INCOGNITO).build();
-        Assert.assertEquals(ToolbarSnapshotDifference.VISUAL_STATE,
-                otherToolbarSnapshotState.getAnyDifference(mDefaultToolbarSnapshotState));
-    }
-
-    @Test
-    public void testDifferentUrlText() {
-        ToolbarSnapshotState otherToolbarSnapshotState =
-                new ToolbarSnapshotStateBuilder().setUrlText("https://www.other.com/").build();
-        Assert.assertEquals(ToolbarSnapshotDifference.URL_TEXT,
-                otherToolbarSnapshotState.getAnyDifference(mDefaultToolbarSnapshotState));
-    }
-
-    @Test
-    public void testDifferentUrlText_SameHintText() {
-        ToolbarSnapshotState initialToolbarSnapshotState =
-                new ToolbarSnapshotStateBuilder()
-                        .setVisibleTextPrefixHint(DEFAULT_URL_TEXT)
-                        .build();
-        ToolbarSnapshotState otherToolbarSnapshotState =
-                new ToolbarSnapshotStateBuilder()
-                        .setUrlText(DEFAULT_URL_TEXT + "additional/paths/")
-                        .setVisibleTextPrefixHint(DEFAULT_URL_TEXT)
-                        .build();
-        Assert.assertEquals(ToolbarSnapshotDifference.NONE,
-                initialToolbarSnapshotState.getAnyDifference(otherToolbarSnapshotState));
-    }
-
-    @Test
-    public void testSameUrlText_DifferentHintText() {
-        ToolbarSnapshotState initialToolbarSnapshotState =
-                new ToolbarSnapshotStateBuilder()
-                        .setVisibleTextPrefixHint(DEFAULT_URL_TEXT.substring(0, 2))
-                        .build();
-        ToolbarSnapshotState otherToolbarSnapshotState =
-                new ToolbarSnapshotStateBuilder()
-                        .setVisibleTextPrefixHint(DEFAULT_URL_TEXT.substring(0, 3))
-                        .build();
-        Assert.assertEquals(ToolbarSnapshotDifference.NONE,
-                initialToolbarSnapshotState.getAnyDifference(otherToolbarSnapshotState));
-    }
-
-    @Test
-    public void testSameUrlText_BothNullHintText() {
-        ToolbarSnapshotState initialToolbarSnapshotState =
-                new ToolbarSnapshotStateBuilder().setVisibleTextPrefixHint(null).build();
-        ToolbarSnapshotState otherToolbarSnapshotState =
-                new ToolbarSnapshotStateBuilder().setVisibleTextPrefixHint(null).build();
-        Assert.assertEquals(ToolbarSnapshotDifference.NONE,
-                initialToolbarSnapshotState.getAnyDifference(otherToolbarSnapshotState));
-    }
-
-    @Test
-    public void testSameUrlText_NullHintText() {
-        ToolbarSnapshotState initialToolbarSnapshotState =
-                new ToolbarSnapshotStateBuilder().setVisibleTextPrefixHint(null).build();
-        ToolbarSnapshotState otherToolbarSnapshotState =
-                new ToolbarSnapshotStateBuilder()
-                        .setVisibleTextPrefixHint(DEFAULT_URL_TEXT)
-                        .build();
-        Assert.assertEquals(ToolbarSnapshotDifference.NONE,
-                initialToolbarSnapshotState.getAnyDifference(otherToolbarSnapshotState));
-        Assert.assertEquals(ToolbarSnapshotDifference.NONE,
-                otherToolbarSnapshotState.getAnyDifference(initialToolbarSnapshotState));
-    }
-
-    @Test
-    public void testDifferentSecurityIcon() {
-        ToolbarSnapshotState otherToolbarSnapshotState =
-                new ToolbarSnapshotStateBuilder().setSecurityIcon(-1).build();
-        Assert.assertEquals(ToolbarSnapshotDifference.SECURITY_ICON,
-                otherToolbarSnapshotState.getAnyDifference(mDefaultToolbarSnapshotState));
-    }
-
-    @Test
-    public void testDifferentColorStateList() {
-        ToolbarSnapshotState otherToolbarSnapshotState =
-                new ToolbarSnapshotStateBuilder()
-                        .setColorStateList(ColorStateList.valueOf(Color.RED))
-                        .build();
-        Assert.assertEquals(ToolbarSnapshotDifference.HOME_BUTTON_COLOR,
-                otherToolbarSnapshotState.getAnyDifference(mDefaultToolbarSnapshotState));
-    }
-
-    @Test
-    public void testSameColorStateList() {
-        // Create the ColorStateList by hand. ColorStateList.valueOf will inconsistently reuse
-        // objects, but this ColorStateList should never have reference equality with the default.
-        ColorStateList colorStateList =
-                new ColorStateList(new int[][] {new int[] {}}, new int[] {DEFAULT_TINT});
-        Assert.assertNotEquals(mDefaultColorStateList, colorStateList);
-
-        ToolbarSnapshotState otherToolbarSnapshotState =
-                new ToolbarSnapshotStateBuilder().setColorStateList(colorStateList).build();
-        Assert.assertEquals(ToolbarSnapshotDifference.NONE,
-                otherToolbarSnapshotState.getAnyDifference(mDefaultToolbarSnapshotState));
-    }
-
-    @Test
-    public void testDifferentIsShowingUpdateBadgeDuringLastCapture() {
-        ToolbarSnapshotState otherToolbarSnapshotState =
-                new ToolbarSnapshotStateBuilder()
-                        .setIsShowingUpdateBadgeDuringLastCapture(true)
-                        .build();
-        Assert.assertEquals(ToolbarSnapshotDifference.SHOWING_UPDATE_BADGE,
-                otherToolbarSnapshotState.getAnyDifference(mDefaultToolbarSnapshotState));
-    }
-
-    @Test
-    public void testDifferentIsPaintPreview() {
-        ToolbarSnapshotState otherToolbarSnapshotState =
-                new ToolbarSnapshotStateBuilder().setIsPaintPreview(true).build();
-        Assert.assertEquals(ToolbarSnapshotDifference.PAINT_PREVIEW,
-                otherToolbarSnapshotState.getAnyDifference(mDefaultToolbarSnapshotState));
-    }
-
-    @Test
-    public void testDifferentProgress() {
-        ToolbarSnapshotState otherToolbarSnapshotState =
-                new ToolbarSnapshotStateBuilder().setProgress(0.2f).build();
-        Assert.assertEquals(ToolbarSnapshotDifference.NONE,
-                otherToolbarSnapshotState.getAnyDifference(mDefaultToolbarSnapshotState));
-    }
-
-    @Test
-    public void testDifferentUnfocusedLocationBarLayoutWidth() {
-        ToolbarSnapshotState otherToolbarSnapshotState =
-                new ToolbarSnapshotStateBuilder().setUnfocusedLocationBarLayoutWidth(100).build();
-        Assert.assertEquals(ToolbarSnapshotDifference.LOCATION_BAR_WIDTH,
-                otherToolbarSnapshotState.getAnyDifference(mDefaultToolbarSnapshotState));
-    }
-
-    @Test
-    public void testIsValidVisibleTextPrefixHint() {
-        Assert.assertFalse(ToolbarSnapshotState.isValidVisibleTextPrefixHint(null, null));
-        Assert.assertFalse(ToolbarSnapshotState.isValidVisibleTextPrefixHint("foo", null));
-        Assert.assertFalse(ToolbarSnapshotState.isValidVisibleTextPrefixHint(null, "foo"));
-
-        Assert.assertFalse(ToolbarSnapshotState.isValidVisibleTextPrefixHint("", ""));
-        Assert.assertFalse(ToolbarSnapshotState.isValidVisibleTextPrefixHint("foo", ""));
-
-        Assert.assertFalse(ToolbarSnapshotState.isValidVisibleTextPrefixHint("foo", "fooo"));
-        Assert.assertFalse(ToolbarSnapshotState.isValidVisibleTextPrefixHint("foo", "foo/"));
-        Assert.assertFalse(ToolbarSnapshotState.isValidVisibleTextPrefixHint("foo", "o/"));
-        Assert.assertFalse(ToolbarSnapshotState.isValidVisibleTextPrefixHint("foo", "oo"));
-
-        Assert.assertTrue(ToolbarSnapshotState.isValidVisibleTextPrefixHint("foo.com", "foo"));
-        Assert.assertTrue(ToolbarSnapshotState.isValidVisibleTextPrefixHint("foo.com", "foo.com"));
-    }
-
-    private class ToolbarSnapshotStateBuilder {
-        private @ColorInt int mTint = DEFAULT_TINT;
-        private int mTabCount = DEFAULT_TAB_COUNT;
-        private ButtonData mOptionalButtonData = DEFAULT_BUTTON_DATA;
-        private @VisualState int mVisualState = DEFAULT_VISUAL_STATE;
-        private String mUrlText = DEFAULT_URL_TEXT;
-        @Nullable
-        private CharSequence mVisibleTextPrefixHint = DEFAULT_URL_HINT_TEXT;
-        private @DrawableRes int mSecurityIcon = DEFAULT_SECURITY_ICON;
-        private ColorStateList mColorStateList = mDefaultColorStateList;
-        private boolean mIsShowingUpdateBadgeDuringLastCapture =
-                DEFAULT_IS_SHOWING_UPDATE_BADGE_DURING_LAST_CAPTURE;
-        private boolean mIsPaintPreview = DEFAULT_IS_PAINT_PREVIEW;
-        private float mProgress = DEFAULT_PROGRESS;
-        private int mUnfocusedLocationBarLayoutWidth = DEFAULT_UNFOCUSED_LOCATION_BAR_LAYOUT_WIDTH;
-
-        public ToolbarSnapshotStateBuilder setTint(@ColorInt int tint) {
-            mTint = tint;
-            return this;
-        }
-
-        public ToolbarSnapshotStateBuilder setTabCount(int tabCount) {
-            mTabCount = tabCount;
-            return this;
-        }
-
-        public ToolbarSnapshotStateBuilder setOptionalButtonData(ButtonData optionalButtonData) {
-            mOptionalButtonData = optionalButtonData;
-            return this;
-        }
-
-        public ToolbarSnapshotStateBuilder setVisualState(@VisualState int visualState) {
-            mVisualState = visualState;
-            return this;
-        }
-
-        public ToolbarSnapshotStateBuilder setUrlText(String urlText) {
-            mUrlText = urlText;
-            return this;
-        }
-
-        public ToolbarSnapshotStateBuilder setVisibleTextPrefixHint(
-                CharSequence visibleTextPrefixHint) {
-            mVisibleTextPrefixHint = visibleTextPrefixHint;
-            return this;
-        }
-
-        public ToolbarSnapshotStateBuilder setSecurityIcon(@DrawableRes int securityIcon) {
-            mSecurityIcon = securityIcon;
-            return this;
-        }
-
-        public ToolbarSnapshotStateBuilder setColorStateList(ColorStateList colorStateList) {
-            mColorStateList = colorStateList;
-            return this;
-        }
-
-        public ToolbarSnapshotStateBuilder setIsShowingUpdateBadgeDuringLastCapture(
-                boolean isShowingUpdateBadgeDuringLastCapture) {
-            mIsShowingUpdateBadgeDuringLastCapture = isShowingUpdateBadgeDuringLastCapture;
-            return this;
-        }
-
-        public ToolbarSnapshotStateBuilder setIsPaintPreview(boolean isPaintPreview) {
-            mIsPaintPreview = isPaintPreview;
-            return this;
-        }
-
-        public ToolbarSnapshotStateBuilder setProgress(float progress) {
-            mProgress = progress;
-            return this;
-        }
-
-        public ToolbarSnapshotStateBuilder setUnfocusedLocationBarLayoutWidth(
-                int unfocusedLocationBarLayoutWidth) {
-            mUnfocusedLocationBarLayoutWidth = unfocusedLocationBarLayoutWidth;
-            return this;
-        }
-
-        public ToolbarSnapshotState build() {
-            return new ToolbarSnapshotState(mTint, mTabCount, mOptionalButtonData, mVisualState,
-                    mUrlText, mVisibleTextPrefixHint, mSecurityIcon, mColorStateList,
-                    mIsShowingUpdateBadgeDuringLastCapture, mIsPaintPreview, mProgress,
-                    mUnfocusedLocationBarLayoutWidth);
-        }
-    }
-}
diff --git a/chrome/browser/ui/autofill/autofill_popup_controller_impl.cc b/chrome/browser/ui/autofill/autofill_popup_controller_impl.cc
index d4627ae3..b642db71 100644
--- a/chrome/browser/ui/autofill/autofill_popup_controller_impl.cc
+++ b/chrome/browser/ui/autofill/autofill_popup_controller_impl.cc
@@ -294,7 +294,10 @@
     return;
   }
 
-  const Suggestion& suggestion = suggestions_[index];
+  // Use a copy instead of a reference here. Under certain circumstances,
+  // `DidAcceptSuggestion()` can call `SetSuggestions()` and invalidate the
+  // reference.
+  Suggestion suggestion = suggestions_[index];
 #if BUILDFLAG(IS_ANDROID)
   auto mf_controller = ManualFillingController::GetOrCreate(web_contents_);
   // Accepting a suggestion should hide all suggestions. To prevent them from
diff --git a/chrome/browser/ui/browser_command_controller.cc b/chrome/browser/ui/browser_command_controller.cc
index 7f1313e..f91c359d 100644
--- a/chrome/browser/ui/browser_command_controller.cc
+++ b/chrome/browser/ui/browser_command_controller.cc
@@ -1066,9 +1066,10 @@
     return;
 
   // Navigation commands
-  command_updater_.UpdateCommandEnabled(IDC_RELOAD, true);
-  command_updater_.UpdateCommandEnabled(IDC_RELOAD_BYPASSING_CACHE, true);
-  command_updater_.UpdateCommandEnabled(IDC_RELOAD_CLEARING_CACHE, true);
+  const bool can_reload = CanReload(browser_);
+  command_updater_.UpdateCommandEnabled(IDC_RELOAD, can_reload);
+  command_updater_.UpdateCommandEnabled(IDC_RELOAD_BYPASSING_CACHE, can_reload);
+  command_updater_.UpdateCommandEnabled(IDC_RELOAD_CLEARING_CACHE, can_reload);
 
   // Window management commands
   command_updater_.UpdateCommandEnabled(IDC_CLOSE_WINDOW, true);
@@ -1326,11 +1327,10 @@
   // Navigation commands
   command_updater_.UpdateCommandEnabled(IDC_BACK, CanGoBack(browser_));
   command_updater_.UpdateCommandEnabled(IDC_FORWARD, CanGoForward(browser_));
-  command_updater_.UpdateCommandEnabled(IDC_RELOAD, CanReload(browser_));
-  command_updater_.UpdateCommandEnabled(IDC_RELOAD_BYPASSING_CACHE,
-                                        CanReload(browser_));
-  command_updater_.UpdateCommandEnabled(IDC_RELOAD_CLEARING_CACHE,
-                                        CanReload(browser_));
+  const bool can_reload = CanReload(browser_);
+  command_updater_.UpdateCommandEnabled(IDC_RELOAD, can_reload);
+  command_updater_.UpdateCommandEnabled(IDC_RELOAD_BYPASSING_CACHE, can_reload);
+  command_updater_.UpdateCommandEnabled(IDC_RELOAD_CLEARING_CACHE, can_reload);
 
   // Window management commands
   bool is_app = browser_->is_type_app() || browser_->is_type_app_popup();
diff --git a/chrome/browser/ui/browser_commands.cc b/chrome/browser/ui/browser_commands.cc
index 0da5058..4176459 100644
--- a/chrome/browser/ui/browser_commands.cc
+++ b/chrome/browser/ui/browser_commands.cc
@@ -630,7 +630,8 @@
 }
 
 bool CanReload(const Browser* browser) {
-  return browser && !browser->is_type_devtools();
+  return browser && !browser->is_type_devtools() &&
+         !browser->is_type_picture_in_picture();
 }
 
 void Home(Browser* browser, WindowOpenDisposition disposition) {
diff --git a/chrome/browser/ui/chrome_pages.cc b/chrome/browser/ui/chrome_pages.cc
index c5f51396..c94b77e3f 100644
--- a/chrome/browser/ui/chrome_pages.cc
+++ b/chrome/browser/ui/chrome_pages.cc
@@ -38,6 +38,7 @@
 #include "chrome/common/url_constants.h"
 #include "components/bookmarks/browser/bookmark_model.h"
 #include "components/bookmarks/browser/bookmark_node.h"
+#include "components/password_manager/core/common/password_manager_features.h"
 #include "components/privacy_sandbox/privacy_sandbox_features.h"
 #include "components/safe_browsing/core/common/safe_browsing_settings_metrics.h"
 #include "components/signin/public/base/consent_level.h"
@@ -433,12 +434,24 @@
 
 void ShowPasswordManager(Browser* browser) {
   base::RecordAction(UserMetricsAction("Options_ShowPasswordManager"));
-  ShowSettingsSubPage(browser, kPasswordManagerSubPage);
+  if (base::FeatureList::IsEnabled(
+          password_manager::features::kPasswordManagerRedesign)) {
+    ShowSingletonTabIgnorePathOverwriteNTP(browser,
+                                           GURL(kChromeUIPasswordManagerURL));
+  } else {
+    ShowSettingsSubPage(browser, kPasswordManagerSubPage);
+  }
 }
 
 void ShowPasswordCheck(Browser* browser) {
   base::RecordAction(UserMetricsAction("Options_ShowPasswordCheck"));
-  ShowSettingsSubPage(browser, kPasswordCheckSubPage);
+  if (base::FeatureList::IsEnabled(
+          password_manager::features::kPasswordManagerRedesign)) {
+    ShowSingletonTabIgnorePathOverwriteNTP(
+        browser, GURL(kChromeUIPasswordManagerCheckupURL));
+  } else {
+    ShowSettingsSubPage(browser, kPasswordCheckSubPage);
+  }
 }
 
 void ShowSafeBrowsingEnhancedProtection(Browser* browser) {
diff --git a/chrome/browser/ui/passwords/password_manager_navigation_throttle.cc b/chrome/browser/ui/passwords/password_manager_navigation_throttle.cc
index d1212b4..fcf744a 100644
--- a/chrome/browser/ui/passwords/password_manager_navigation_throttle.cc
+++ b/chrome/browser/ui/passwords/password_manager_navigation_throttle.cc
@@ -20,6 +20,8 @@
 
 #if BUILDFLAG(IS_ANDROID)
 #include "chrome/browser/password_manager/android/password_manager_launcher_android.h"
+#else
+#include "chrome/browser/password_manager/chrome_password_manager_client.h"
 #endif
 
 namespace {
@@ -28,10 +30,6 @@
 using content::NavigationThrottle;
 using content::WebContents;
 
-#if !BUILDFLAG(IS_ANDROID)
-constexpr char kChromeUIPasswordsURL[] = "chrome:/settings/passwords";
-#endif
-
 bool IsTriggeredOnGoogleOwnedUI(NavigationHandle* handle) {
   // Only cover cases when the user clicked on a link.
   if (!ui::PageTransitionCoreTypeIs(handle->GetPageTransition(),
@@ -43,10 +41,9 @@
     return false;
 
   url::Origin origin = handle->GetInitiatorOrigin().value_or(url::Origin());
-  if (origin != url::Origin::Create(GURL(password_manager::kReferrerURL)) &&
-      origin !=
-          url::Origin::Create(GURL(password_manager::kTestingReferrerURL)))
+  if (origin != url::Origin::Create(GURL(password_manager::kReferrerURL))) {
     return false;
+  }
 
   return true;
 }
@@ -85,24 +82,16 @@
       web_contents,
       password_manager::ManagePasswordsReferrer::kPasswordsGoogleWebsite);
 #else
-  content::OpenURLParams params =
-      content::OpenURLParams::FromNavigationHandle(navigation_handle());
-  params.url = GURL(kChromeUIPasswordsURL);
-  params.transition = ui::PAGE_TRANSITION_CLIENT_REDIRECT;
-
-  base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
-      FROM_HERE, base::BindOnce(
-                     [](base::WeakPtr<content::WebContents> web_contents,
-                        const content::OpenURLParams& params) {
-                       if (!web_contents)
-                         return;
-                       web_contents->OpenURL(params);
-                     },
-                     web_contents->GetWeakPtr(), std::move(params)));
-  UMA_HISTOGRAM_ENUMERATION(
-      "PasswordManager.ManagePasswordsReferrer",
-      password_manager::ManagePasswordsReferrer::kPasswordsGoogleWebsite);
+  ChromePasswordManagerClient::FromWebContents(web_contents)
+      ->NavigateToManagePasswordsPage(
+          password_manager::ManagePasswordsReferrer::kPasswordsGoogleWebsite);
 #endif
+  // Schedule a task to close current tab since on Android Password Manager is
+  // shown in the Native UI, and on desktop NavigateToManagePasswordsPage()
+  // handles creation or redirection to Passwords Manager tab.
+  base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
+      FROM_HERE,
+      base::BindOnce(&content::WebContents::Close, web_contents->GetWeakPtr()));
   return NavigationThrottle::CANCEL_AND_IGNORE;
 }
 
diff --git a/chrome/browser/ui/passwords/password_manager_navigation_throttle_unittest.cc b/chrome/browser/ui/passwords/password_manager_navigation_throttle_unittest.cc
index 2d40fabb..d18e625 100644
--- a/chrome/browser/ui/passwords/password_manager_navigation_throttle_unittest.cc
+++ b/chrome/browser/ui/passwords/password_manager_navigation_throttle_unittest.cc
@@ -97,13 +97,3 @@
           url::Origin::Create(GURL(password_manager::kReferrerURL)),
   }));
 }
-
-TEST_F(PasswordManagerNavigationThrottleTest,
-       CreatesNavigationThrottleForTestingWebsite) {
-  EXPECT_TRUE(CreateNavigationThrottle({
-      .url = GURL(password_manager::kManageMyPasswordsURL),
-      .page_transition = ui::PAGE_TRANSITION_LINK,
-      .initiator_origin =
-          url::Origin::Create(GURL(password_manager::kTestingReferrerURL)),
-  }));
-}
diff --git a/chrome/browser/ui/views/autofill/autofill_accessibility_win_browsertest.cc b/chrome/browser/ui/views/autofill/autofill_accessibility_win_browsertest.cc
index 292d513..9068a9f 100644
--- a/chrome/browser/ui/views/autofill/autofill_accessibility_win_browsertest.cc
+++ b/chrome/browser/ui/views/autofill/autofill_accessibility_win_browsertest.cc
@@ -58,7 +58,7 @@
    private:
     TestAutofillManagerWaiter forms_seen_waiter_{
         *this,
-        {&AutofillManager::Observer::OnAfterFormsSeen}};
+        {AutofillManagerEvent::kFormsSeen}};
   };
 
   void SetUpOnMainThread() override {
diff --git a/chrome/browser/ui/views/autofill/payments/credit_card_access_manager_browsertest.cc b/chrome/browser/ui/views/autofill/payments/credit_card_access_manager_browsertest.cc
index 8f2b370..7590d79 100644
--- a/chrome/browser/ui/views/autofill/payments/credit_card_access_manager_browsertest.cc
+++ b/chrome/browser/ui/views/autofill/payments/credit_card_access_manager_browsertest.cc
@@ -39,7 +39,7 @@
    private:
     TestAutofillManagerWaiter forms_seen_waiter_{
         *this,
-        {&AutofillManager::Observer::OnAfterFormsSeen}};
+        {AutofillManagerEvent::kFormsSeen}};
   };
 
   void SetUpOnMainThread() override {
diff --git a/chrome/browser/ui/views/autofill/payments/local_card_migration_uitest.cc b/chrome/browser/ui/views/autofill/payments/local_card_migration_uitest.cc
index 251ece1..822f298 100644
--- a/chrome/browser/ui/views/autofill/payments/local_card_migration_uitest.cc
+++ b/chrome/browser/ui/views/autofill/payments/local_card_migration_uitest.cc
@@ -174,7 +174,7 @@
    private:
     TestAutofillManagerWaiter forms_seen_waiter_{
         *this,
-        {&AutofillManager::Observer::OnAfterFormsSeen}};
+        {AutofillManagerEvent::kFormsSeen}};
   };
 
   // Various events that can be waited on by the DialogEventWaiter.
diff --git a/chrome/browser/ui/views/autofill/payments/offer_notification_bubble_views_test_base.h b/chrome/browser/ui/views/autofill/payments/offer_notification_bubble_views_test_base.h
index f6bda06..661833f 100644
--- a/chrome/browser/ui/views/autofill/payments/offer_notification_bubble_views_test_base.h
+++ b/chrome/browser/ui/views/autofill/payments/offer_notification_bubble_views_test_base.h
@@ -51,7 +51,7 @@
    private:
     TestAutofillManagerWaiter forms_seen_waiter_{
         *this,
-        {&AutofillManager::Observer::OnAfterFormsSeen}};
+        {AutofillManagerEvent::kFormsSeen}};
   };
 
   // Various events that can be waited on by the DialogEventWaiter.
diff --git a/chrome/browser/ui/views/autofill/payments/save_card_bubble_views_browsertest.cc b/chrome/browser/ui/views/autofill/payments/save_card_bubble_views_browsertest.cc
index 2703ece..d2e1fc8 100644
--- a/chrome/browser/ui/views/autofill/payments/save_card_bubble_views_browsertest.cc
+++ b/chrome/browser/ui/views/autofill/payments/save_card_bubble_views_browsertest.cc
@@ -160,7 +160,7 @@
    private:
     TestAutofillManagerWaiter forms_seen_waiter_{
         *this,
-        {&AutofillManager::Observer::OnAfterFormsSeen}};
+        {AutofillManagerEvent::kFormsSeen}};
   };
 
   // Various events that can be waited on by the DialogEventWaiter.
diff --git a/chrome/browser/ui/views/autofill/payments/save_iban_bubble_view_uitest.cc b/chrome/browser/ui/views/autofill/payments/save_iban_bubble_view_uitest.cc
index 3ec1c41..d60da9e 100644
--- a/chrome/browser/ui/views/autofill/payments/save_iban_bubble_view_uitest.cc
+++ b/chrome/browser/ui/views/autofill/payments/save_iban_bubble_view_uitest.cc
@@ -76,7 +76,7 @@
    private:
     TestAutofillManagerWaiter forms_seen_waiter_{
         *this,
-        {&AutofillManager::Observer::OnAfterFormsSeen}};
+        {AutofillManagerEvent::kFormsSeen}};
   };
 
   // Various events that can be waited on by the DialogEventWaiter.
diff --git a/chrome/browser/ui/views/frame/browser_non_client_frame_view_browsertest.cc b/chrome/browser/ui/views/frame/browser_non_client_frame_view_browsertest.cc
index 67d553c..26d96dc 100644
--- a/chrome/browser/ui/views/frame/browser_non_client_frame_view_browsertest.cc
+++ b/chrome/browser/ui/views/frame/browser_non_client_frame_view_browsertest.cc
@@ -52,7 +52,7 @@
  private:
   autofill::TestAutofillManagerWaiter forms_seen_waiter_{
       *this,
-      {&AutofillManager::Observer::OnAfterFormsSeen}};
+      {autofill::AutofillManagerEvent::kFormsSeen}};
 };
 
 }  // namespace
diff --git a/chrome/browser/ui/views/permissions/chooser_bubble_ui.cc b/chrome/browser/ui/views/permissions/chooser_bubble_ui.cc
index 1aff5bd..77183cc 100644
--- a/chrome/browser/ui/views/permissions/chooser_bubble_ui.cc
+++ b/chrome/browser/ui/views/permissions/chooser_bubble_ui.cc
@@ -204,15 +204,22 @@
   if (browser->tab_strip_model()->GetActiveWebContents() != contents)
     return base::DoNothing();
 
+  // `GetExtensionsToolbarContainer` may return `nullptr`, for instance in
+  // extension popup windows.
+  auto* extensions_toolbar_container =
+      BrowserView::GetBrowserViewForBrowser(browser)
+          ->toolbar_button_provider()
+          ->GetExtensionsToolbarContainer();
+  if (!extensions_toolbar_container) {
+    return base::DoNothing();
+  }
+
   auto bubble = std::make_unique<ChooserBubbleUiViewDelegate>(
       browser, contents, std::move(controller));
   base::OnceClosure close_closure = bubble->MakeCloseClosure();
-  views::Widget* widget =
-      views::BubbleDialogDelegateView::CreateBubble(std::move(bubble));
-  BrowserView::GetBrowserViewForBrowser(browser)
-      ->toolbar_button_provider()
-      ->GetExtensionsToolbarContainer()
-      ->ShowWidgetForExtension(widget, extension->id());
+  extensions_toolbar_container->ShowWidgetForExtension(
+      views::BubbleDialogDelegateView::CreateBubble(std::move(bubble)),
+      extension->id());
   return close_closure;
 }
 #endif  // BUILDFLAG(ENABLE_EXTENSIONS)
diff --git a/chrome/browser/ui/views/tabs/compound_tab_container.cc b/chrome/browser/ui/views/tabs/compound_tab_container.cc
index 3fe533d..bc42043 100644
--- a/chrome/browser/ui/views/tabs/compound_tab_container.cc
+++ b/chrome/browser/ui/views/tabs/compound_tab_container.cc
@@ -249,7 +249,14 @@
     bounds_animator_.SetAnimationDuration(base::TimeDelta());
 }
 
-CompoundTabContainer::~CompoundTabContainer() = default;
+CompoundTabContainer::~CompoundTabContainer() {
+  // Tabs call back up to the TabStrip during animation end and destruction.
+  // Ensure that happens now so we aren't in a half-destructed state when they
+  // do so.
+  CancelAnimation();
+  RemoveChildViewT(base::to_address(pinned_tab_container_));
+  RemoveChildViewT(base::to_address(unpinned_tab_container_));
+}
 
 void CompoundTabContainer::SetAvailableWidthCallback(
     base::RepeatingCallback<int()> available_width_callback) {
@@ -547,12 +554,21 @@
   pinned_tab_container_->AnimateToIdealBounds();
   unpinned_tab_container_->AnimateToIdealBounds();
 
+  // Animate the pinning or unpinning tabs too.
   for (views::View* child : children()) {
     Tab* tab = views::AsViewClass<Tab>(child);
     if (!tab)
       continue;
 
-    AnimateTabTo(tab, GetIdealBounds(GetModelIndexOf(tab).value()));
+    const absl::optional<int> model_index = GetModelIndexOf(tab);
+    // The tab may have been closed during a pin/unpin animation, in which case
+    // it a) has no model index and b) is already animating to its correct
+    // bounds because that will have been updated in `UpdateAnimationTarget()`.
+    if (!model_index.has_value()) {
+      continue;
+    }
+
+    AnimateTabTo(tab, GetIdealBounds(model_index.value()));
   }
 }
 
@@ -874,8 +890,14 @@
                                                         int to_model_index) {
   // If the tab at `from_model_index` is already being transferred, complete
   // all pending transfers before we embark upon this one to avoid conflicts.
-  if (bounds_animator_.IsAnimating(GetTabAtModelIndex(from_model_index)))
-    CompleteAnimationAndLayout();
+  if (bounds_animator_.IsAnimating(GetTabAtModelIndex(from_model_index))) {
+    // We are out of sync with the model right now (because we're handling a
+    // model update), so we need to be careful here. We can complete our
+    // directly managed animations, but we can't ask the sub-containers to do
+    // the same, as their ideal bounds calculations assume the model and
+    // viewmodel are in sync.
+    bounds_animator_.Complete();
+  }
 
   const bool prev_pinned = from_model_index < NumPinnedTabs();
   const bool next_pinned = !prev_pinned;
diff --git a/chrome/browser/ui/views/tabs/tab_strip_unittest.cc b/chrome/browser/ui/views/tabs/tab_strip_unittest.cc
index 48772fed..c119117 100644
--- a/chrome/browser/ui/views/tabs/tab_strip_unittest.cc
+++ b/chrome/browser/ui/views/tabs/tab_strip_unittest.cc
@@ -10,6 +10,7 @@
 #include "base/feature_list.h"
 #include "base/functional/bind.h"
 #include "base/memory/raw_ptr.h"
+#include "base/test/bind.h"
 #include "base/test/scoped_feature_list.h"
 #include "base/test/task_environment.h"
 #include "base/timer/timer.h"
@@ -184,12 +185,21 @@
     tab_strip_->GetDragContext()->StoppedDragging(views);
   }
 
-  std::vector<views::View*> GetChildViews() {
-    // The first (and only) View child of TabStrip is its TabContainer, which
-    // contains the tabs and group views.
-    // TODO(1116121): Move tests that test view/focus order to test
-    // TabContainer directly, once that functionality has been moved there.
-    return tab_strip_->children()[0]->children();
+  size_t NumTabSlotViews() {
+    base::RepeatingCallback<size_t(views::View*)> num_tab_slot_views =
+        base::BindLambdaForTesting([&num_tab_slot_views](views::View* parent) {
+          if (views::IsViewClass<TabSlotView>(parent)) {
+            return size_t(1);
+          } else {
+            size_t sum = 0;
+            for (views::View* child : parent->children()) {
+              sum += num_tab_slot_views.Run(child);
+            }
+            return sum;
+          }
+        });
+
+    return num_tab_slot_views.Run(tab_strip_);
   }
 
   std::vector<TabGroupViews*> ListGroupViews() const {
@@ -294,18 +304,18 @@
   TestTabStripObserver observer(tab_strip_);
   controller_->AddTab(0, TabActive::kInactive);
   controller_->AddTab(1, TabActive::kInactive);
-  const size_t num_children = GetChildViews().size();
+  const size_t num_children = NumTabSlotViews();
   EXPECT_EQ(2, tab_strip_->GetTabCount());
   controller_->RemoveTab(0);
   EXPECT_EQ(0, observer.last_tab_removed());
   // When removing a tab the tabcount should immediately decrement.
   EXPECT_EQ(1, tab_strip_->GetTabCount());
   // But the number of views should remain the same (it's animatining closed).
-  EXPECT_EQ(num_children, GetChildViews().size());
+  EXPECT_EQ(num_children, NumTabSlotViews());
 
   CompleteAnimationAndLayout();
 
-  EXPECT_EQ(num_children - 1, GetChildViews().size());
+  EXPECT_EQ(num_children - 1, NumTabSlotViews());
 
   // Remove the last tab to make sure things are cleaned up correctly when
   // the TabStrip is destroyed and an animation is ongoing.
diff --git a/chrome/browser/ui/views/user_education/browser_user_education_service.cc b/chrome/browser/ui/views/user_education/browser_user_education_service.cc
index df7bbdc3..3eea8f49 100644
--- a/chrome/browser/ui/views/user_education/browser_user_education_service.cc
+++ b/chrome/browser/ui/views/user_education/browser_user_education_service.cc
@@ -329,7 +329,7 @@
           IDS_PASSWORD_MANAGER_IPH_BODY_SAVE_TO_ACCOUNT)
           .SetBubbleTitleText(IDS_PASSWORD_MANAGER_IPH_TITLE_SAVE_TO_ACCOUNT)
           .SetInAnyContext(true)
-          .SetBubbleArrow(HelpBubbleArrow::kBottomLeft)
+          .SetBubbleArrow(HelpBubbleArrow::kBottomRight)
           .SetBubbleIcon(&vector_icons::kCelebrationIcon)));
 
   // kIPHBatterySaverModeFeature:
diff --git a/chrome/browser/updater/scheduler_mac.cc b/chrome/browser/updater/scheduler_mac.cc
index 3fcba7b..a5e0d51 100644
--- a/chrome/browser/updater/scheduler_mac.cc
+++ b/chrome/browser/updater/scheduler_mac.cc
@@ -75,6 +75,7 @@
         }
       }
     }
+    std::move(callback).Run();
   }
 }
 
diff --git a/chrome/browser/webauthn/authenticator_request_dialog_model.cc b/chrome/browser/webauthn/authenticator_request_dialog_model.cc
index 88d02e23c..7b00a6a 100644
--- a/chrome/browser/webauthn/authenticator_request_dialog_model.cc
+++ b/chrome/browser/webauthn/authenticator_request_dialog_model.cc
@@ -31,6 +31,7 @@
 #include "device/fido/discoverable_credential_metadata.h"
 #include "device/fido/features.h"
 #include "device/fido/fido_authenticator.h"
+#include "device/fido/fido_transport_protocol.h"
 #include "device/fido/fido_types.h"
 #include "device/fido/pin.h"
 #include "device/fido/public_key_credential_user_entity.h"
@@ -1071,7 +1072,9 @@
   const bool is_get_assertion = transport_availability_.request_type ==
                                 device::FidoRequestType::kGetAssertion;
   const bool is_passkey_request =
-      ((is_get_assertion && transport_availability_.has_empty_allow_list) ||
+      ((is_get_assertion &&
+        (transport_availability_.has_empty_allow_list ||
+         transport_availability_.is_only_hybrid_or_internal)) ||
        (!is_get_assertion && resident_key_requirement() !=
                                  device::ResidentKeyRequirement::kDiscouraged));
   // priority_transport contains the transport that should be activated
diff --git a/chrome/browser/webauthn/authenticator_request_dialog_model_unittest.cc b/chrome/browser/webauthn/authenticator_request_dialog_model_unittest.cc
index 4be65b6..b5099ae 100644
--- a/chrome/browser/webauthn/authenticator_request_dialog_model_unittest.cc
+++ b/chrome/browser/webauthn/authenticator_request_dialog_model_unittest.cc
@@ -104,6 +104,7 @@
   kOneRecognizedCred,
   kTwoRecognizedCreds,
   kEmptyAllowList,
+  kOnlyHybridOrInternal,
   kHasWinNativeAuthenticator,
   kHasCableV1Extension,
   kHasCableV2Extension,
@@ -125,6 +126,8 @@
       return "kTwoRecognizedCreds";
     case TransportAvailabilityParam::kEmptyAllowList:
       return "kEmptyAllowList";
+    case TransportAvailabilityParam::kOnlyHybridOrInternal:
+      return "kOnlyHybridOrInternal";
     case TransportAvailabilityParam::kHasWinNativeAuthenticator:
       return "kHasWinNativeAuthenticator";
     case TransportAvailabilityParam::kHasCableV1Extension:
@@ -184,6 +187,8 @@
   const auto one_cred = TransportAvailabilityParam::kOneRecognizedCred;
   const auto two_cred = TransportAvailabilityParam::kTwoRecognizedCreds;
   const auto empty_al = TransportAvailabilityParam::kEmptyAllowList;
+  const auto only_hybrid_or_internal =
+      TransportAvailabilityParam::kOnlyHybridOrInternal;
   const auto rk = TransportAvailabilityParam::kRequireResidentKey;
   const auto c_ui = TransportAvailabilityParam::kIsConditionalUI;
   using t = AuthenticatorRequestDialogModel::Mechanism::Transport;
@@ -306,7 +311,8 @@
       // caBLE isn't an option.
       {ga, {cable}, {has_winapi, empty_al}, {}, {winapi, add}, mss},
       {ga, {}, {has_winapi, empty_al}, {}, {winapi}, plat_ui},
-      // But with a non-empty allow list, always jump to Windows UI.
+      // But with a non-empty allow list containing non phone credentials,
+      // always jump to Windows UI.
       {ga, {cable}, {has_winapi}, {}, {winapi, add}, plat_ui},
       {ga, {}, {has_winapi}, {}, {winapi}, plat_ui},
       // Except when the request is legacy cable.
@@ -358,6 +364,14 @@
        {t(internal), t(usb), add},
        qr,
        {qr1st}},
+      // And if the allow list only contains phones.
+      {ga,
+       {internal, cable},
+       {only_hybrid_or_internal},
+       {},
+       {t(internal), add},
+       qr,
+       {qr1st}},
       // Unless there is a phone paired already.
       {ga,
        {usb, internal, cable},
@@ -382,7 +396,8 @@
        {t(usb), add},
        qr,
        {qr1st}},
-      // If there is an allow-list, go to transport selection instead.
+      // If there is an allow-list containing USB, go to transport selection
+      // instead.
       {ga,
        {usb, internal, cable},
        {},
@@ -456,6 +471,8 @@
     }
     transports_info.has_empty_allow_list = base::Contains(
         test.params, TransportAvailabilityParam::kEmptyAllowList);
+    transports_info.is_only_hybrid_or_internal = base::Contains(
+        test.params, TransportAvailabilityParam::kOnlyHybridOrInternal);
 
     if (base::Contains(
             test.params,
diff --git a/chrome/build/linux.pgo.txt b/chrome/build/linux.pgo.txt
index a4f7302..53eff49d 100644
--- a/chrome/build/linux.pgo.txt
+++ b/chrome/build/linux.pgo.txt
@@ -1 +1 @@
-chrome-linux-main-1676635198-985656c4c8f8c864219be56acd9ad96e64158cd6.profdata
+chrome-linux-main-1676656701-f9b954d106c330345c1769e674295f6dc682efea.profdata
diff --git a/chrome/build/mac-arm.pgo.txt b/chrome/build/mac-arm.pgo.txt
index cc1df68..8c76b01 100644
--- a/chrome/build/mac-arm.pgo.txt
+++ b/chrome/build/mac-arm.pgo.txt
@@ -1 +1 @@
-chrome-mac-arm-main-1676635198-d9c070fc3ac187a6fc78506298f02bcc703436f4.profdata
+chrome-mac-arm-main-1676656701-c19bf79ccd8178da0f09f5700b478205029b9eef.profdata
diff --git a/chrome/build/mac.pgo.txt b/chrome/build/mac.pgo.txt
index d9c32369..fb34a6a 100644
--- a/chrome/build/mac.pgo.txt
+++ b/chrome/build/mac.pgo.txt
@@ -1 +1 @@
-chrome-mac-main-1676635198-68379fcc089eca33d09943f7c369329ef51cabe2.profdata
+chrome-mac-main-1676656701-de66fa95d4ff92418365c2f65fb24b5355e5cd53.profdata
diff --git a/chrome/build/win32.pgo.txt b/chrome/build/win32.pgo.txt
index 93af1ab9..85da5e9a 100644
--- a/chrome/build/win32.pgo.txt
+++ b/chrome/build/win32.pgo.txt
@@ -1 +1 @@
-chrome-win32-main-1676635198-4817542e58ede2d7868e5eebef98fbe04a877e39.profdata
+chrome-win32-main-1676656701-7f1c3354d4d278e7adf75844663fe940142f9023.profdata
diff --git a/chrome/build/win64.pgo.txt b/chrome/build/win64.pgo.txt
index 15e0702..c4237a0 100644
--- a/chrome/build/win64.pgo.txt
+++ b/chrome/build/win64.pgo.txt
@@ -1 +1 @@
-chrome-win64-main-1676635198-8eb87c282dc69b99dd61c2aea7a5fb13608e22f6.profdata
+chrome-win64-main-1676656701-0009794feb607261fb99123d2d37723dcc62a7ba.profdata
diff --git a/chrome/common/pref_names.cc b/chrome/common/pref_names.cc
index e9a8e23..e76377ce 100644
--- a/chrome/common/pref_names.cc
+++ b/chrome/common/pref_names.cc
@@ -3371,6 +3371,11 @@
 // If not set, Chrome will choose the root store based on experiments.
 const char kChromeRootStoreEnabled[] = "chrome_root_store_enabled";
 #endif
+#if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC) || BUILDFLAG(IS_LINUX) || \
+    BUILDFLAG(IS_CHROMEOS) || BUILDFLAG(IS_ANDROID)
+const char kEnforceLocalAnchorConstraintsEnabled[] =
+    "enforce_local_anchor_constraints_enabled";
+#endif
 
 const char kSharingVapidKey[] = "sharing.vapid_key";
 const char kSharingFCMRegistration[] = "sharing.fcm_registration";
diff --git a/chrome/common/pref_names.h b/chrome/common/pref_names.h
index 89f33d4f..77a3cfb 100644
--- a/chrome/common/pref_names.h
+++ b/chrome/common/pref_names.h
@@ -1163,6 +1163,11 @@
 #if BUILDFLAG(CHROME_ROOT_STORE_POLICY_SUPPORTED)
 extern const char kChromeRootStoreEnabled[];
 #endif
+#if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC) || BUILDFLAG(IS_LINUX) || \
+    BUILDFLAG(IS_CHROMEOS) || BUILDFLAG(IS_ANDROID)
+// TODO(https://crbug.com/1406103): delete this after a few milestones.
+extern const char kEnforceLocalAnchorConstraintsEnabled[];
+#endif
 
 extern const char kSharingVapidKey[];
 extern const char kSharingFCMRegistration[];
diff --git a/chrome/common/webui_url_constants.cc b/chrome/common/webui_url_constants.cc
index f6ddbcbd..5dffc00 100644
--- a/chrome/common/webui_url_constants.cc
+++ b/chrome/common/webui_url_constants.cc
@@ -153,6 +153,8 @@
 const char kChromeUIPasswordManagerInternalsHost[] =
     "password-manager-internals";
 const char kChromeUIPasswordManagerURL[] = "chrome://password-manager";
+const char kChromeUIPasswordManagerCheckupURL[] =
+    "chrome://password-manager/checkup?start=true";
 const char kChromeUIPerformanceSettingsURL[] = "chrome://settings/performance";
 const char kChromeUIPolicyHost[] = "policy";
 const char kChromeUIPolicyURL[] = "chrome://policy/";
diff --git a/chrome/common/webui_url_constants.h b/chrome/common/webui_url_constants.h
index 59b58a2..9aa5180d 100644
--- a/chrome/common/webui_url_constants.h
+++ b/chrome/common/webui_url_constants.h
@@ -157,6 +157,7 @@
 #endif
 extern const char kChromeUIPasswordManagerInternalsHost[];
 extern const char kChromeUIPasswordManagerURL[];
+extern const char kChromeUIPasswordManagerCheckupURL[];
 extern const char kChromeUIPerformanceSettingsURL[];
 extern const char kChromeUIPolicyHost[];
 extern const char kChromeUIPolicyURL[];
diff --git a/chrome/test/base/chrome_test_launcher.cc b/chrome/test/base/chrome_test_launcher.cc
index 9cade91..e78c7d6c 100644
--- a/chrome/test/base/chrome_test_launcher.cc
+++ b/chrome/test/base/chrome_test_launcher.cc
@@ -77,13 +77,6 @@
 #include "testing/gtest/include/gtest/gtest.h"
 #endif
 
-#if BUILDFLAG(IS_CHROMEOS_LACROS)
-#include "base/files/file_enumerator.h"
-#include "base/files/file_util.h"
-#include "base/strings/string_number_conversions.h"
-#include "content/public/test/browser_test_switches.h"
-#endif
-
 // static
 int ChromeTestSuiteRunner::RunTestSuiteInternal(ChromeTestSuite* test_suite) {
   // Browser tests are expected not to tear-down various globals.
@@ -240,35 +233,12 @@
   if (IsUserAnAdmin())
     firewall_rules_ = std::make_unique<ScopedFirewallRules>();
 #endif
-
-#if BUILDFLAG(IS_CHROMEOS_LACROS)
-  CHECK(ash_processes_dir_.CreateUniqueTempDir());
-  base::CommandLine::ForCurrentProcess()->AppendSwitchPath(
-      content::test::switches::kAshProcessesDirPath,
-      ash_processes_dir_.GetPath());
-#endif
 }
 
 void ChromeTestLauncherDelegate::OnDoneRunningTests() {
 #if BUILDFLAG(IS_WIN)
   firewall_rules_.reset();
 #endif
-
-// In test runners, they may create ash processes outlive the runner process.
-// So we need to terminate those ash processes in the test launcher.
-#if BUILDFLAG(IS_CHROMEOS_LACROS)
-  base::FileEnumerator files(ash_processes_dir_.GetPath(), false,
-                             base::FileEnumerator::FILES);
-  for (base::FilePath name = files.Next(); !name.empty(); name = files.Next()) {
-    std::string str_pid;
-    CHECK(base::ReadFileToString(name, &str_pid));
-    int pid;
-    CHECK(base::StringToInt(str_pid, &pid)) << "Cannot read pid. The content "
-                                            << "of the file is: " << str_pid;
-    base::Process process = base::Process::Open(pid);
-    process.Terminate(0, false);
-  }
-#endif
 }
 
 int LaunchChromeTests(size_t parallel_jobs,
diff --git a/chrome/test/base/chrome_test_launcher.h b/chrome/test/base/chrome_test_launcher.h
index ccbcf2e..eb54742 100644
--- a/chrome/test/base/chrome_test_launcher.h
+++ b/chrome/test/base/chrome_test_launcher.h
@@ -18,10 +18,6 @@
 #include "chrome/app/chrome_main_delegate.h"
 #endif
 
-#if BUILDFLAG(IS_CHROMEOS_LACROS)
-#include "base/files/scoped_temp_dir.h"
-#endif
-
 class ChromeTestSuite;
 
 // Allows a test suite to override the TestSuite class used. By default it is an
@@ -90,10 +86,6 @@
 #endif
 
   raw_ptr<ChromeTestSuiteRunner> runner_;
-
-#if BUILDFLAG(IS_CHROMEOS_LACROS)
-  base::ScopedTempDir ash_processes_dir_;
-#endif
 };
 
 // Launches Chrome browser tests. |parallel_jobs| is number of test jobs to be
diff --git a/chrome/test/data/extensions/api_test/tts_engine/lacros_tts_support/tts_pause_before_speak_lacros_engine/manifest.json b/chrome/test/data/extensions/api_test/tts_engine/lacros_tts_support/tts_pause_before_speak_lacros_engine/manifest.json
new file mode 100644
index 0000000..e68bb7f
--- /dev/null
+++ b/chrome/test/data/extensions/api_test/tts_engine/lacros_tts_support/tts_pause_before_speak_lacros_engine/manifest.json
@@ -0,0 +1,18 @@
+{
+  "name": "chrome.ttsEngine",
+  "version": "0.1",
+  "manifest_version": 2,
+  "description": "browser test for chrome.ttsEngine API",
+  "background": {
+    "page": "test.html"
+  },
+  "tts_engine": {
+    "voices": [{
+      "voice_name": "Alice",
+      "lang": "en-US",
+      "gender": "female",
+      "event_types": ["start", "end"]
+    }]
+  },
+  "permissions": ["tts", "ttsEngine"]
+}
diff --git a/chrome/test/data/extensions/api_test/tts_engine/lacros_tts_support/tts_pause_before_speak_lacros_engine/test.html b/chrome/test/data/extensions/api_test/tts_engine/lacros_tts_support/tts_pause_before_speak_lacros_engine/test.html
new file mode 100644
index 0000000..f39ab08a
--- /dev/null
+++ b/chrome/test/data/extensions/api_test/tts_engine/lacros_tts_support/tts_pause_before_speak_lacros_engine/test.html
@@ -0,0 +1,6 @@
+<!--
+ * 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.
+-->
+<script src="test.js"></script>
diff --git a/chrome/test/data/extensions/api_test/tts_engine/lacros_tts_support/tts_pause_before_speak_lacros_engine/test.js b/chrome/test/data/extensions/api_test/tts_engine/lacros_tts_support/tts_pause_before_speak_lacros_engine/test.js
new file mode 100644
index 0000000..562368c
--- /dev/null
+++ b/chrome/test/data/extensions/api_test/tts_engine/lacros_tts_support/tts_pause_before_speak_lacros_engine/test.js
@@ -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.
+
+// TTS api test running from Lacros with Ash.
+// build/lacros/test_runner.py test
+//     {path_to_lacros_build}/lacros_chrome_browsertests_run_in_series
+//     --gtest_filter=LacrosTtsApiTest.PauseBeforeSpeakWithLacrosTtsEngine
+//     --ash-chrome-path {path_to_ash_build}/test_ash_chrome
+
+chrome.test.runTests([
+  async function testPauseBeforeSpeakWithLacrosTtsEngine() {
+    const utteranceText = "Hello";
+
+    // Register listeners for speech functions, enabling this extension
+    // to be a TTS engine.
+    let speakListener = function (utterance, options, sendTtsEvent) {
+      chrome.test.assertNoLastError();
+      chrome.test.assertEq(utteranceText, utterance);
+      sendTtsEvent({
+        'type': 'end',
+        'charIndex': utterance.length
+      });
+    };
+    chrome.ttsEngine.onSpeak.addListener(speakListener);
+
+    let stopListener = function () {};
+    chrome.ttsEngine.onStop.addListener(stopListener);
+
+    // Pause speech synthesis before calling tts.speak.
+    chrome.tts.pause();
+
+    // Request to speak an utterance with Lacros speech engine. The utterance
+    // should be queued by Ash's TtsController.
+    let ttsSpeakPromise = new Promise((resolve) => {
+      chrome.tts.speak(
+        utteranceText, {
+          'voiceName': 'Alice',
+          'onEvent': function (event) {
+            if (event.type == 'end') {
+              resolve();
+            }
+          }
+        },
+        function () {
+          chrome.test.assertNoLastError();
+        });
+    });
+
+    // Resume speech synthesis. This should enable the Ash TtsController to send
+    // the queued utterance to Lacros speech engine.
+    chrome.tts.resume();
+
+    await ttsSpeakPromise.then(() => {
+      chrome.ttsEngine.onSpeak.removeListener(speakListener);
+      chrome.ttsEngine.onStop.removeListener(stopListener);
+      chrome.test.succeed();
+    });
+  }
+]);
diff --git a/chrome/test/data/extensions/api_test/tts_engine/lacros_tts_support/tts_pause_during_speak_lacros_engine/manifest.json b/chrome/test/data/extensions/api_test/tts_engine/lacros_tts_support/tts_pause_during_speak_lacros_engine/manifest.json
new file mode 100644
index 0000000..e68bb7f
--- /dev/null
+++ b/chrome/test/data/extensions/api_test/tts_engine/lacros_tts_support/tts_pause_during_speak_lacros_engine/manifest.json
@@ -0,0 +1,18 @@
+{
+  "name": "chrome.ttsEngine",
+  "version": "0.1",
+  "manifest_version": 2,
+  "description": "browser test for chrome.ttsEngine API",
+  "background": {
+    "page": "test.html"
+  },
+  "tts_engine": {
+    "voices": [{
+      "voice_name": "Alice",
+      "lang": "en-US",
+      "gender": "female",
+      "event_types": ["start", "end"]
+    }]
+  },
+  "permissions": ["tts", "ttsEngine"]
+}
diff --git a/chrome/test/data/extensions/api_test/tts_engine/lacros_tts_support/tts_pause_during_speak_lacros_engine/test.html b/chrome/test/data/extensions/api_test/tts_engine/lacros_tts_support/tts_pause_during_speak_lacros_engine/test.html
new file mode 100644
index 0000000..f39ab08a
--- /dev/null
+++ b/chrome/test/data/extensions/api_test/tts_engine/lacros_tts_support/tts_pause_during_speak_lacros_engine/test.html
@@ -0,0 +1,6 @@
+<!--
+ * 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.
+-->
+<script src="test.js"></script>
diff --git a/chrome/test/data/extensions/api_test/tts_engine/lacros_tts_support/tts_pause_during_speak_lacros_engine/test.js b/chrome/test/data/extensions/api_test/tts_engine/lacros_tts_support/tts_pause_during_speak_lacros_engine/test.js
new file mode 100644
index 0000000..25e6597
--- /dev/null
+++ b/chrome/test/data/extensions/api_test/tts_engine/lacros_tts_support/tts_pause_during_speak_lacros_engine/test.js
@@ -0,0 +1,98 @@
+// 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.
+
+// TTS api test running from Lacros with Ash.
+// build/lacros/test_runner.py test
+//     {path_to_lacros_build}/lacros_chrome_browsertests_run_in_series
+//     --gtest_filter=LacrosTtsApiTest.PauseDuringSpeakWithLacrosTtsEngine
+//     --ash-chrome-path {path_to_ash_build}/test_ash_chrome
+
+chrome.test.runTests([
+  function testPauseDuringSpeakWithLacrosTtsEngine() {
+    let sendTtsEventFunc;
+    const utteranceText = "Hello";
+
+    // Register listeners for speech functions, enabling this extension
+    // to be a TTS engine.
+    let onSpeakPromise = new Promise((resolve) => {
+      chrome.ttsEngine.onSpeak.addListener(function listener(
+        utterance, options, sendTtsEvent) {
+        chrome.test.assertNoLastError();
+        sendTtsEventFunc = sendTtsEvent;
+        chrome.test.assertEq(utteranceText, utterance);
+        sendTtsEvent({
+          'type': 'start',
+          'charIndex': utterance.length
+        });
+        chrome.ttsEngine.onSpeak.removeListener(listener);
+        resolve();
+      });
+    });
+
+    let stopListener = function () {};
+    chrome.ttsEngine.onStop.addListener(stopListener);
+
+    let onPausePromise = new Promise((resolve) => {
+      chrome.ttsEngine.onPause.addListener(function listener() {
+        chrome.test.assertNoLastError();
+        chrome.ttsEngine.onPause.removeListener(listener);
+        resolve();
+      })
+    });
+
+    let onResumePromise = new Promise((resolve) => {
+      chrome.ttsEngine.onResume.addListener(function listener() {
+        chrome.test.assertNoLastError();
+        // Simulate the speech engine resuming speech synthesis and finish
+        // the utterance.
+        sendTtsEventFunc({
+          'type': 'end',
+          'charIndex': utteranceText.length
+        });
+        chrome.ttsEngine.onResume.removeListener(listener);
+        resolve();
+      })
+    });
+
+    // Call tts.speak to speak an utterance with our own speech engine running
+    // in Lacros.
+    let ttsSpeakPromise = new Promise((resolve) => {
+      let startEventReceived = false;
+      chrome.tts.speak(
+        utteranceText, {
+          'voiceName': 'Alice',
+          'onEvent': function (event) {
+            if (event.type == 'start') {
+              startEventReceived = true;
+            } else if (event.type == 'end') {
+              chrome.test.assertEq(startEventReceived, true);
+              resolve();
+            }
+          }
+        },
+        function () {
+          chrome.test.assertNoLastError();
+        });
+    });
+
+    // Request pausing speech synthesis during the Lacros speech engine speaking
+    // an utterance. Ash's TtsController will pause speech synthesis and send
+    // onPause event to the Lacros speech engine speaking the current utterance.
+    chrome.tts.pause();
+
+    // Request resuming speech synthesis. Ash's TtsController will resume speech
+    // synthesis and send onResume event to the Lacros speech engine it has
+    // paused previously.
+    chrome.tts.resume();
+
+    // Verify that all ttsEngine events have been delivered to the speech
+    // engine, and tts.speak finishes the utterance.
+    Promise.all([onSpeakPromise, onPausePromise, onResumePromise,
+      ttsSpeakPromise
+    ]).then(() => {
+      chrome.ttsEngine.onStop.removeListener(stopListener);
+      chrome.test.succeed();
+    });
+  }
+]);
diff --git a/chrome/test/data/policy/policy_test_cases.json b/chrome/test/data/policy/policy_test_cases.json
index 3720461..e6dce78 100644
--- a/chrome/test/data/policy/policy_test_cases.json
+++ b/chrome/test/data/policy/policy_test_cases.json
@@ -2732,6 +2732,50 @@
       }
     ]
   },
+  "EnforceLocalAnchorConstraintsEnabled": {
+    "os": [
+      "win",
+      "mac",
+      "linux",
+      "chromeos_ash",
+      "chromeos_lacros",
+      "android"
+    ],
+    "policy_pref_mapping_tests": [
+      {
+        "note": "Policy not set. (The actual value isn't used as long as the pref is unmanaged.)",
+        "policies": {},
+        "prefs": {
+          "enforce_local_anchor_constraints_enabled": {
+            "location": "local_state",
+            "default_value": true
+          }
+        }
+      },
+      {
+        "policies": {
+          "EnforceLocalAnchorConstraintsEnabled": false
+        },
+        "prefs": {
+          "enforce_local_anchor_constraints_enabled": {
+            "location": "local_state",
+            "value": false
+          }
+        }
+      },
+      {
+        "policies": {
+          "EnforceLocalAnchorConstraintsEnabled": true
+        },
+        "prefs": {
+          "enforce_local_anchor_constraints_enabled": {
+            "location": "local_state",
+            "value": true
+          }
+        }
+      }
+    ]
+  },
   "AuthSchemes": {
     "os": [
       "win",
diff --git a/chrome/test/data/webui/side_panel/customize_chrome/categories_test.ts b/chrome/test/data/webui/side_panel/customize_chrome/categories_test.ts
index ea78ca6e..46e86f0 100644
--- a/chrome/test/data/webui/side_panel/customize_chrome/categories_test.ts
+++ b/chrome/test/data/webui/side_panel/customize_chrome/categories_test.ts
@@ -143,6 +143,9 @@
         categoriesElement.shadowRoot!.querySelectorAll('[checked]');
     assertEquals(1, checkedCategories.length);
     assertEquals(checkedCategories[0]!.parentElement!.id, 'classicChromeTile');
+    assertEquals(
+        checkedCategories[0]!.parentElement!.getAttribute('aria-current'),
+        'true');
 
     // Set a theme with a color.
     theme.foregroundColor = {value: 0xffff0000};
@@ -155,6 +158,9 @@
         categoriesElement.shadowRoot!.querySelectorAll('[checked]');
     assertEquals(1, checkedCategories.length);
     assertEquals(checkedCategories[0]!.parentElement!.id, 'chromeColorsTile');
+    assertEquals(
+        checkedCategories[0]!.parentElement!.getAttribute('aria-current'),
+        'true');
 
     // Set a theme with local background.
     const backgroundImage = createBackgroundImage('https://test.jpg');
@@ -169,6 +175,9 @@
         categoriesElement.shadowRoot!.querySelectorAll('[checked]');
     assertEquals(1, checkedCategories.length);
     assertEquals(checkedCategories[0]!.parentElement!.id, 'uploadImageTile');
+    assertEquals(
+        checkedCategories[0]!.parentElement!.getAttribute('aria-current'),
+        'true');
 
     // Set a theme with collection background.
     backgroundImage.isUploadedImage = false;
@@ -184,6 +193,9 @@
     assertEquals(1, checkedCategories.length);
     assertEquals(
         checkedCategories[0]!.parentElement!.className, 'tile collection');
+    assertEquals(
+        checkedCategories[0]!.parentElement!.getAttribute('aria-current'),
+        'true');
 
     // Set a CWS theme.
     theme.thirdPartyThemeInfo = {
diff --git a/chrome/test/data/webui/side_panel/customize_chrome/chrome_colors_test.ts b/chrome/test/data/webui/side_panel/customize_chrome/chrome_colors_test.ts
index 19ba447..e0a873b 100644
--- a/chrome/test/data/webui/side_panel/customize_chrome/chrome_colors_test.ts
+++ b/chrome/test/data/webui/side_panel/customize_chrome/chrome_colors_test.ts
@@ -122,6 +122,7 @@
         chromeColorsElement.shadowRoot!.querySelectorAll('[checked]');
     assertEquals(1, checkedColors.length);
     assertEquals(defaultColorElement, checkedColors[0]);
+    assertEquals('true', checkedColors[0]!.getAttribute('aria-current'));
 
     // Set Chrome color.
     theme.seedColor = {value: 1};
@@ -136,6 +137,7 @@
     assertEquals(1, checkedColors.length);
     assertEquals('chrome-color tile', checkedColors[0]!.className);
     assertEquals(3, (checkedColors[0]! as ColorElement).foregroundColor.value);
+    assertEquals('true', checkedColors[0]!.getAttribute('aria-current'));
 
     // Set custom color.
     theme.seedColor = {value: 10};
@@ -149,6 +151,8 @@
         chromeColorsElement.shadowRoot!.querySelectorAll('[checked]');
     assertEquals(1, checkedColors.length);
     assertEquals(chromeColorsElement.$.customColor, checkedColors[0]);
+    assertEquals(
+        'true', checkedColors[0]!.parentElement!.getAttribute('aria-current'));
 
     // Set a CWS theme.
     theme.thirdPartyThemeInfo = {
diff --git a/chrome/test/data/webui/side_panel/customize_chrome/themes_test.ts b/chrome/test/data/webui/side_panel/customize_chrome/themes_test.ts
index cad3cbc..a9d0c1f3 100644
--- a/chrome/test/data/webui/side_panel/customize_chrome/themes_test.ts
+++ b/chrome/test/data/webui/side_panel/customize_chrome/themes_test.ts
@@ -215,6 +215,7 @@
     assertEquals(1, checkedThemes.length);
     const checkedTile = checkedThemes[0]!.parentElement as HTMLElement;
     assertEquals(checkedTile!.title, 'attribution1_1');
+    assertEquals(checkedTile!.getAttribute('aria-current'), 'true');
 
     // Set daily refresh.
     theme.backgroundImage.collectionId = 'test_collection_id';
diff --git a/chrome/updater/mac/BUILD.gn b/chrome/updater/mac/BUILD.gn
index bf3cafc1..37d5f665 100644
--- a/chrome/updater/mac/BUILD.gn
+++ b/chrome/updater/mac/BUILD.gn
@@ -65,7 +65,7 @@
 
   sources = [ "main.cc" ]
   deps = [
-    ":keystone_agent_bundle_resource_executable",
+    ":keystone_agent_bundle_resource_executable_test",
     ":keystone_agent_bundle_resource_plist",
     ":updater_bundle_helpers_test",
     ":updater_bundle_keystone_executable",
@@ -74,6 +74,9 @@
     "//chrome/updater:base",
     "//chrome/updater:constants_test",
   ]
+  if (is_asan) {
+    deps += [ ":keystone_agent_bundle_resource_executable_test_asan_dylib" ]
+  }
   public_deps = [
     ":ksadmin_test_copy",
     ":ksadmin_test_link",
@@ -422,6 +425,26 @@
     "MAC_BUNDLE_IDENTIFIER=${keystone_bundle_identifier}.Agent",
     "PACKAGE_TYPE=APPL",
   ]
+  deps = [
+    "//base",
+    "//chrome/updater:base",
+    "//chrome/updater:constants_prod",
+  ]
+}
+
+mac_app_bundle("keystone_agent_bundle_test") {
+  info_plist = "keystone/Info.plist"
+  sources = [ "keystone/agent_main.cc" ]
+  output_name = keystone_app_name + "Agent_test"
+  extra_substitutions = [
+    "MAC_BUNDLE_IDENTIFIER=${keystone_bundle_identifier}.Agent",
+    "PACKAGE_TYPE=APPL",
+  ]
+  deps = [
+    "//base",
+    "//chrome/updater:base",
+    "//chrome/updater:constants_test",
+  ]
 }
 
 bundle_data("keystone_agent_bundle_resource_executable") {
@@ -435,6 +458,21 @@
   }
 }
 
+bundle_data("keystone_agent_bundle_resource_executable_test") {
+  sources = [ "$root_out_dir/${keystone_app_name}Agent_test.app/Contents/MacOS/${keystone_app_name}Agent_test" ]
+
+  outputs = [ "{{bundle_contents_dir}}/Helpers/$keystone_app_name.bundle/Contents/Resources/${keystone_app_name}Agent.app/Contents/MacOS/${keystone_app_name}Agent" ]
+  public_deps = [ ":keystone_agent_bundle_test" ]
+}
+
+if (is_asan) {
+  bundle_data("keystone_agent_bundle_resource_executable_test_asan_dylib") {
+    sources = [ "$root_out_dir/${keystone_app_name}Agent_test.app/Contents/MacOS/libclang_rt.asan_osx_dynamic.dylib" ]
+    outputs = [ "{{bundle_contents_dir}}/Helpers/$keystone_app_name.bundle/Contents/Resources/${keystone_app_name}Agent.app/Contents/MacOS/{{source_file_part}}" ]
+    public_deps = [ ":keystone_agent_bundle_test" ]
+  }
+}
+
 bundle_data("keystone_agent_bundle_resource_plist") {
   sources =
       [ "$root_out_dir/${keystone_app_name}Agent.app/Contents/Info.plist" ]
diff --git a/chrome/updater/mac/keystone/agent_main.cc b/chrome/updater/mac/keystone/agent_main.cc
index e5c14e9..537cc5b 100644
--- a/chrome/updater/mac/keystone/agent_main.cc
+++ b/chrome/updater/mac/keystone/agent_main.cc
@@ -2,5 +2,39 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-// The agent is a shim to trick the keystone registration framework.
-int main() {}
+#include "base/command_line.h"
+#include "base/files/file_path.h"
+#include "base/process/launch.h"
+#include "chrome/updater/constants.h"
+#include "chrome/updater/updater_scope.h"
+#include "chrome/updater/util/util.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
+
+namespace updater {
+
+void Main() {
+  for (UpdaterScope scope : {UpdaterScope::kSystem, UpdaterScope::kUser}) {
+    absl::optional<base::FilePath> path = GetUpdaterExecutablePath(scope);
+    if (!path) {
+      continue;
+    }
+    base::CommandLine command(*path);
+    command.AppendSwitch(kWakeSwitch);
+    if (scope == UpdaterScope::kSystem) {
+      command.AppendSwitch(kSystemSwitch);
+    }
+    command.AppendSwitch(kEnableLoggingSwitch);
+    command.AppendSwitchNative(kLoggingModuleSwitch, kLoggingModuleSwitchValue);
+    base::LaunchProcess(command, {});
+  }
+}
+
+}  // namespace updater
+
+// The agent is a shim to trick the keystone registration framework. When run,
+// it should launch the --wake task. Not all callers correctly provide a scope,
+// so it will wake both scopes (if present).
+int main() {
+  updater::Main();
+  return 0;
+}
diff --git a/chromecast/media/cma/backend/BUILD.gn b/chromecast/media/cma/backend/BUILD.gn
index 841b4ee..0537f9d 100644
--- a/chromecast/media/cma/backend/BUILD.gn
+++ b/chromecast/media/cma/backend/BUILD.gn
@@ -195,6 +195,7 @@
     ":volume_map",
     "//base",
     "//base/test:run_all_unittests",
+    "//base/test:test_support",
     "//chromecast/media/cma/backend/mixer:unittests",
     "//media",
     "//testing/gmock",
diff --git a/chromecast/media/cma/backend/cast_audio_json.cc b/chromecast/media/cma/backend/cast_audio_json.cc
index c5f8163..45761538 100644
--- a/chromecast/media/cma/backend/cast_audio_json.cc
+++ b/chromecast/media/cma/backend/cast_audio_json.cc
@@ -29,10 +29,9 @@
 
   std::string contents;
   base::ReadFileToString(path, &contents);
-  std::unique_ptr<base::Value> value =
-      base::JSONReader::ReadDeprecated(contents);
-  if (value) {
-    callback.Run(std::move(value));
+  absl::optional<base::Value> value = base::JSONReader::Read(contents);
+  if (value && value->is_dict()) {
+    callback.Run(std::move(*value).TakeDict());
     return;
   }
   LOG(ERROR) << "Unable to parse JSON in " << path;
@@ -77,10 +76,16 @@
 
 CastAudioJsonProviderImpl::~CastAudioJsonProviderImpl() = default;
 
-std::unique_ptr<base::Value> CastAudioJsonProviderImpl::GetCastAudioConfig() {
+absl::optional<base::Value::Dict>
+CastAudioJsonProviderImpl::GetCastAudioConfig() {
   std::string contents;
   base::ReadFileToString(CastAudioJson::GetFilePath(), &contents);
-  return base::JSONReader::ReadDeprecated(contents);
+  absl::optional<base::Value> value = base::JSONReader::Read(contents);
+  if (!value || value->is_dict()) {
+    return absl::nullopt;
+  }
+
+  return std::move(*value).TakeDict();
 }
 
 void CastAudioJsonProviderImpl::SetTuningChangedCallback(
diff --git a/chromecast/media/cma/backend/cast_audio_json.h b/chromecast/media/cma/backend/cast_audio_json.h
index 40a045d..eeaa391 100644
--- a/chromecast/media/cma/backend/cast_audio_json.h
+++ b/chromecast/media/cma/backend/cast_audio_json.h
@@ -5,14 +5,13 @@
 #ifndef CHROMECAST_MEDIA_CMA_BACKEND_CAST_AUDIO_JSON_H_
 #define CHROMECAST_MEDIA_CMA_BACKEND_CAST_AUDIO_JSON_H_
 
-#include <memory>
-
 #include "base/files/file_path.h"
 #include "base/files/file_path_watcher.h"
 #include "base/functional/callback.h"
 #include "base/memory/scoped_refptr.h"
 #include "base/threading/sequence_bound.h"
 #include "base/values.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
 
 namespace chromecast {
 namespace media {
@@ -31,7 +30,7 @@
 class CastAudioJsonProvider {
  public:
   using TuningChangedCallback =
-      base::RepeatingCallback<void(std::unique_ptr<base::Value> contents)>;
+      base::RepeatingCallback<void(absl::optional<base::Value::Dict> contents)>;
 
   virtual ~CastAudioJsonProvider() = default;
 
@@ -41,7 +40,7 @@
   // at CastAudioJson::GetReadOnlyFilePath() will be returned.
   // This function will run on the thread on which it is called, and may
   // perform blocking I/O.
-  virtual std::unique_ptr<base::Value> GetCastAudioConfig() = 0;
+  virtual absl::optional<base::Value::Dict> GetCastAudioConfig() = 0;
 
   // |callback| will be called when a new cast_audio config is available.
   // |callback| will always be called from the same thread, but not necessarily
@@ -72,7 +71,7 @@
   };
 
   // CastAudioJsonProvider implementation:
-  std::unique_ptr<base::Value> GetCastAudioConfig() override;
+  absl::optional<base::Value::Dict> GetCastAudioConfig() override;
   void SetTuningChangedCallback(TuningChangedCallback callback) override;
 
   base::SequenceBound<FileWatcher> cast_audio_watcher_;
diff --git a/chromecast/media/cma/backend/cplay/cplay.cc b/chromecast/media/cma/backend/cplay/cplay.cc
index d68a830..b1d18e60 100644
--- a/chromecast/media/cma/backend/cplay/cplay.cc
+++ b/chromecast/media/cma/backend/cplay/cplay.cc
@@ -340,7 +340,12 @@
   // Set volume.
   std::string contents;
   base::ReadFileToString(params.cast_audio_json_path, &contents);
-  GetVolumeMap().LoadVolumeMap(base::JSONReader::ReadDeprecated(contents));
+  absl::optional<base::Value> parsed_json = base::JSONReader::Read(contents);
+  if (parsed_json && parsed_json->is_dict()) {
+    GetVolumeMap().LoadVolumeMap(std::move(*parsed_json).TakeDict());
+  } else {
+    GetVolumeMap().LoadVolumeMap(absl::nullopt);
+  }
   float volume_dbfs = GetVolumeMap().VolumeToDbFS(params.cast_volume);
   float volume_multiplier = std::pow(10.0, volume_dbfs / 20.0);
   mixer_input.SetVolumeMultiplier(1.0);
diff --git a/chromecast/media/cma/backend/volume_map.cc b/chromecast/media/cma/backend/volume_map.cc
index d5060a6..200e69a 100644
--- a/chromecast/media/cma/backend/volume_map.cc
+++ b/chromecast/media/cma/backend/volume_map.cc
@@ -48,15 +48,16 @@
   LoadVolumeMap(config_provider_->GetCastAudioConfig());
 }
 
-void VolumeMap::LoadVolumeMap(std::unique_ptr<base::Value> cast_audio_config) {
-  if (!cast_audio_config || !cast_audio_config->is_dict()) {
+void VolumeMap::LoadVolumeMap(
+    absl::optional<base::Value::Dict> cast_audio_config) {
+  if (!cast_audio_config) {
     LOG(WARNING) << "No cast audio config found; using default volume map.";
     UseDefaultVolumeMap();
     return;
   }
 
   const base::Value::List* volume_map_list =
-      cast_audio_config->GetDict().FindList(kKeyVolumeMap);
+      cast_audio_config->FindList(kKeyVolumeMap);
   if (!volume_map_list) {
     LOG(WARNING) << "No volume map found; using default volume map.";
     UseDefaultVolumeMap();
diff --git a/chromecast/media/cma/backend/volume_map.h b/chromecast/media/cma/backend/volume_map.h
index ad8c11cd..4d3bdb9 100644
--- a/chromecast/media/cma/backend/volume_map.h
+++ b/chromecast/media/cma/backend/volume_map.h
@@ -36,7 +36,7 @@
 
   float DbFSToVolume(float db);
 
-  void LoadVolumeMap(std::unique_ptr<base::Value> cast_audio_config);
+  void LoadVolumeMap(absl::optional<base::Value::Dict> cast_audio_config);
 
  private:
   struct LevelToDb {
diff --git a/chromecast/media/cma/backend/volume_map_unittest.cc b/chromecast/media/cma/backend/volume_map_unittest.cc
index 403df9d..aa0c3ef 100644
--- a/chromecast/media/cma/backend/volume_map_unittest.cc
+++ b/chromecast/media/cma/backend/volume_map_unittest.cc
@@ -7,7 +7,7 @@
 #include <string>
 
 #include "base/check.h"
-#include "base/json/json_reader.h"
+#include "base/test/values_test_util.h"
 #include "chromecast/media/cma/backend/cast_audio_json.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
@@ -35,12 +35,12 @@
 
   void CallTuningChangedCallback(const std::string& new_config) {
     DCHECK(callback_);
-    callback_.Run(base::JSONReader::ReadDeprecated(new_config));
+    callback_.Run(base::test::ParseJsonDict(new_config));
   }
 
  private:
-  std::unique_ptr<base::Value> GetCastAudioConfig() override {
-    return base::JSONReader::ReadDeprecated(file_contents_);
+  absl::optional<base::Value::Dict> GetCastAudioConfig() override {
+    return base::test::ParseJsonDict(file_contents_);
   }
 
   void SetTuningChangedCallback(TuningChangedCallback callback) override {
@@ -52,7 +52,7 @@
 };
 
 TEST(VolumeMapTest, UsesDefaultMapIfConfigEmpty) {
-  VolumeMap volume_map(std::make_unique<TestFileProvider>(""));
+  VolumeMap volume_map(std::make_unique<TestFileProvider>("{}"));
   EXPECT_NEAR(-58.0f, volume_map.VolumeToDbFS(0.01f), kEpsilon);
   EXPECT_NEAR(-48.0f, volume_map.VolumeToDbFS(1.0 / 11.0), kEpsilon);
   EXPECT_NEAR(-8.0f, volume_map.VolumeToDbFS(9.0 / 11.0), kEpsilon);
@@ -82,7 +82,7 @@
 }
 
 TEST(VolumeMapTest, LoadsNewMapWhenFileChanges) {
-  auto provider = std::make_unique<TestFileProvider>("");
+  auto provider = std::make_unique<TestFileProvider>("{}");
   TestFileProvider* provider_ptr = provider.get();
   VolumeMap volume_map(std::move(provider));
 
diff --git a/chromeos/ash/services/BUILD.gn b/chromeos/ash/services/BUILD.gn
index 7e48892..2f7dcda0 100644
--- a/chromeos/ash/services/BUILD.gn
+++ b/chromeos/ash/services/BUILD.gn
@@ -34,6 +34,7 @@
     "//chromeos/ash/services/network_config:unit_tests",
     "//chromeos/ash/services/network_health:unit_tests",
     "//chromeos/ash/services/quick_pair:unit_tests",
+    "//chromeos/ash/services/recording:unit_tests",
     "//chromeos/ash/services/secure_channel:unit_tests",
     "//chromeos/services/machine_learning/public/cpp:ash_unit_tests",
   ]
diff --git a/chromeos/ash/services/recording/BUILD.gn b/chromeos/ash/services/recording/BUILD.gn
index 6f9ef4e..13a6edda 100644
--- a/chromeos/ash/services/recording/BUILD.gn
+++ b/chromeos/ash/services/recording/BUILD.gn
@@ -10,6 +10,10 @@
   sources = [
     "gif_encoder.cc",
     "gif_encoder.h",
+    "gif_file_writer.cc",
+    "gif_file_writer.h",
+    "lzw_pixel_color_indices_writer.cc",
+    "lzw_pixel_color_indices_writer.h",
     "recording_encoder.cc",
     "recording_encoder.h",
     "recording_file_io_helper.cc",
@@ -47,3 +51,16 @@
     "//media",
   ]
 }
+
+source_set("unit_tests") {
+  testonly = true
+
+  sources = [ "lzw_pixel_color_indices_writer_test.cc" ]
+
+  deps = [
+    ":recording",
+    "//base",
+    "//base/test:test_support",
+    "//chromeos/ash/services/recording/public/mojom",
+  ]
+}
diff --git a/chromeos/ash/services/recording/DEPS b/chromeos/ash/services/recording/DEPS
index dd2b1c5..375f8a1a 100644
--- a/chromeos/ash/services/recording/DEPS
+++ b/chromeos/ash/services/recording/DEPS
@@ -10,6 +10,8 @@
   "+mojo/public",
   "+services/audio/public",
   "+services/viz/privileged/mojom/compositing",
+  "+testing/gmock/include/gmock/gmock.h",
+  "+testing/gtest/include/gtest/gtest.h",
   "+ui/gfx",
 
   # Abseil is allowed by default, but some features are banned. See
diff --git a/chromeos/ash/services/recording/gif_encoder.cc b/chromeos/ash/services/recording/gif_encoder.cc
index 8754d5e..d67de4bd 100644
--- a/chromeos/ash/services/recording/gif_encoder.cc
+++ b/chromeos/ash/services/recording/gif_encoder.cc
@@ -5,8 +5,8 @@
 #include "chromeos/ash/services/recording/gif_encoder.h"
 
 #include "base/notreached.h"
+#include "chromeos/ash/services/recording/lzw_pixel_color_indices_writer.h"
 #include "chromeos/ash/services/recording/recording_encoder.h"
-#include "chromeos/ash/services/recording/recording_file_io_helper.h"
 #include "media/base/audio_bus.h"
 #include "media/base/video_frame.h"
 
@@ -32,11 +32,10 @@
     const base::FilePath& gif_file_path,
     OnFailureCallback on_failure_callback)
     : RecordingEncoder(std::move(on_failure_callback)),
-      gif_file_(gif_file_path,
-                base::File::FLAG_CREATE_ALWAYS | base::File::FLAG_WRITE),
-      file_io_helper_(gif_file_path,
-                      std::move(drive_fs_quota_delegate),
-                      /*delegate=*/this) {
+      gif_file_writer_(std::move(drive_fs_quota_delegate),
+                       gif_file_path,
+                       /*file_io_helper_delegate=*/this),
+      lzw_encoder_(&gif_file_writer_) {
   InitializeVideoEncoder(video_encoder_options);
 }
 
diff --git a/chromeos/ash/services/recording/gif_encoder.h b/chromeos/ash/services/recording/gif_encoder.h
index 2ad7cbd4..01412c7 100644
--- a/chromeos/ash/services/recording/gif_encoder.h
+++ b/chromeos/ash/services/recording/gif_encoder.h
@@ -5,11 +5,11 @@
 #ifndef CHROMEOS_ASH_SERVICES_RECORDING_GIF_ENCODER_H_
 #define CHROMEOS_ASH_SERVICES_RECORDING_GIF_ENCODER_H_
 
-#include "base/files/file.h"
 #include "base/threading/sequence_bound.h"
 #include "base/types/pass_key.h"
+#include "chromeos/ash/services/recording/gif_file_writer.h"
+#include "chromeos/ash/services/recording/lzw_pixel_color_indices_writer.h"
 #include "chromeos/ash/services/recording/recording_encoder.h"
-#include "chromeos/ash/services/recording/recording_file_io_helper.h"
 
 namespace recording {
 
@@ -67,14 +67,14 @@
   void FlushAndFinalize(base::OnceClosure on_done) override;
 
  private:
-  // The file created at `gif_file_path` to which the output of the GIF encoder
-  // will be written.
-  base::File gif_file_;
+  // Abstracts writing bytes to the GIF file, and takes care of handling IO
+  // errors and remaining disk space / DriveFS quota issues.
+  GifFileWriter gif_file_writer_;
 
-  // A helper that will be used to calculate either the remaining disk space (if
-  // writing to a local file), or the remaining quota if the file exists on
-  // DriveFS.
-  RecordingFileIoHelper file_io_helper_;
+  // Abstracts encoding the video frame's image color indices using the
+  // Variable-Length-Code LZW compression algorithm and writing the output
+  // stream to the GIF file.
+  LzwPixelColorIndicesWriter lzw_encoder_;
 };
 
 }  // namespace recording
diff --git a/chromeos/ash/services/recording/gif_file_writer.cc b/chromeos/ash/services/recording/gif_file_writer.cc
new file mode 100644
index 0000000..d28ba48
--- /dev/null
+++ b/chromeos/ash/services/recording/gif_file_writer.cc
@@ -0,0 +1,40 @@
+// 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/services/recording/gif_file_writer.h"
+#include "base/containers/span.h"
+
+namespace recording {
+
+GifFileWriter::GifFileWriter(
+    mojo::PendingRemote<mojom::DriveFsQuotaDelegate> drive_fs_quota_delegate,
+    const base::FilePath& gif_file_path,
+    RecordingFileIoHelper::Delegate* file_io_helper_delegate)
+    : gif_file_(gif_file_path,
+                base::File::FLAG_CREATE_ALWAYS | base::File::FLAG_WRITE),
+      file_io_helper_(gif_file_path,
+                      std::move(drive_fs_quota_delegate),
+                      file_io_helper_delegate) {}
+
+GifFileWriter::~GifFileWriter() = default;
+
+void GifFileWriter::WriteByte(uint8_t byte) {
+  WriteBytesAndCheck(base::make_span(&byte, sizeof(byte)));
+}
+
+void GifFileWriter::WriteBuffer(const uint8_t* const buffer,
+                                size_t buffer_size) {
+  WriteBytesAndCheck(base::make_span(buffer, buffer_size));
+}
+
+void GifFileWriter::WriteBytesAndCheck(base::span<const uint8_t> data) {
+  if (!gif_file_.WriteAtCurrentPosAndCheck(data)) {
+    file_io_helper_.delegate()->NotifyFailure(mojom::RecordingStatus::kIoError);
+    return;
+  }
+
+  file_io_helper_.OnBytesWritten(data.size_bytes());
+}
+
+}  // namespace recording
diff --git a/chromeos/ash/services/recording/gif_file_writer.h b/chromeos/ash/services/recording/gif_file_writer.h
new file mode 100644
index 0000000..6345c9a
--- /dev/null
+++ b/chromeos/ash/services/recording/gif_file_writer.h
@@ -0,0 +1,53 @@
+// 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_SERVICES_RECORDING_GIF_FILE_WRITER_H_
+#define CHROMEOS_ASH_SERVICES_RECORDING_GIF_FILE_WRITER_H_
+
+#include <cstdint>
+
+#include "base/files/file.h"
+#include "chromeos/ash/services/recording/recording_file_io_helper.h"
+
+namespace recording {
+
+// Creates a file at the given `gif_file_path`, and provides various APIs to
+// allow writing bytes to it with various lengths. It also takes care of
+// handling any IO errors while writing as well as running out of disk space or
+// DriveFS quota conditions.
+class GifFileWriter {
+ public:
+  GifFileWriter(
+      mojo::PendingRemote<mojom::DriveFsQuotaDelegate> drive_fs_quota_delegate,
+      const base::FilePath& gif_file_path,
+      RecordingFileIoHelper::Delegate* file_io_helper_delegate);
+  GifFileWriter(const GifFileWriter&) = delete;
+  GifFileWriter& operator=(const GifFileWriter&) = delete;
+  ~GifFileWriter();
+
+  // Writes the given `bytes` to the `gif_file_`.
+  void WriteByte(uint8_t byte);
+
+  // Writes the contents of the given `buffer` whose length is `buffer_size` to
+  // the `gif_file_`.
+  void WriteBuffer(const uint8_t* const buffer, size_t buffer_size);
+
+ private:
+  // Writes the given `data` to the `gif_file_` and check for IO errors or disk
+  // space / DriveFS quota issues.
+  void WriteBytesAndCheck(base::span<const uint8_t> data);
+
+  // The file created at `gif_file_path` to which the output of the GIF encoder
+  // will be written.
+  base::File gif_file_;
+
+  // A helper that will be used to calculate either the remaining disk space (if
+  // writing to a local file), or the remaining quota if the file exists on
+  // DriveFS.
+  RecordingFileIoHelper file_io_helper_;
+};
+
+}  // namespace recording
+
+#endif  // CHROMEOS_ASH_SERVICES_RECORDING_GIF_FILE_WRITER_H_
diff --git a/chromeos/ash/services/recording/lzw_pixel_color_indices_writer.cc b/chromeos/ash/services/recording/lzw_pixel_color_indices_writer.cc
new file mode 100644
index 0000000..ccf7bfb
--- /dev/null
+++ b/chromeos/ash/services/recording/lzw_pixel_color_indices_writer.cc
@@ -0,0 +1,210 @@
+// 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/services/recording/lzw_pixel_color_indices_writer.h"
+
+#include <cstdint>
+
+#include "chromeos/ash/services/recording/gif_file_writer.h"
+
+namespace recording {
+
+namespace {
+
+// The GIF specs specify a maximum of 12 bits per LZW compression code, which
+// means that the maximum possible value for the codes is (2 ^ 12) - 1, which is
+// equal to 4095, and the maximum number of codes is (2 ^ 12 = 4096).
+constexpr LzwCode kMaxNumberOfLzwCodes = 1 << 12;
+
+// The maximum number of bytes contained in a data sub block of the LZW encoded
+// output.
+constexpr uint8_t kMaxBytesPerDataSubBlock = 0xFF;
+
+}  // namespace
+
+LzwPixelColorIndicesWriter::LzwPixelColorIndicesWriter(
+    GifFileWriter* gif_file_writer)
+    : gif_file_writer_(gif_file_writer) {
+  code_table_.reserve(kMaxNumberOfLzwCodes);
+  byte_stream_buffer_.reserve(kMaxBytesPerDataSubBlock);
+}
+
+LzwPixelColorIndicesWriter::~LzwPixelColorIndicesWriter() = default;
+
+void LzwPixelColorIndicesWriter::EncodeAndWrite(
+    const ColorIndices& pixel_color_indices,
+    uint8_t color_bit_depth) {
+  if (pixel_color_indices.empty()) {
+    return;
+  }
+
+  // Start the process with an empty table.
+  code_table_.clear();
+
+  // The `color_bit_depth` is the minimum number of bits needed to represent all
+  // the indices in `pixel_color_indices`. For example, if we have 4 colors,
+  // with indices 0, 1, 2, 3, The minimum number of bits needed to represent the
+  // largest index (3) is 2 bits. This is the `color_bit_depth` and it's also
+  // the value of the LZW minimum code size, and is required to be written to
+  // the file as the first byte of the image data block.
+  const LzwCode lzw_minimum_code_size = color_bit_depth;
+  gif_file_writer_->WriteByte(lzw_minimum_code_size);
+
+  // Remember, we're not writing color indices to the file, but rather LZW
+  // compression codes. So we map each color index to an LZW code. So in the
+  // above example with 4 colors, our LZW codes will be as follows:
+  //
+  // +-------------+----------+
+  // | Color Index | LZW Code |
+  // +-------------+----------+
+  // |      0      |     0    |
+  // |      1      |     1    |
+  // |      2      |     2    |
+  // |      3      |     3    |
+  // +-------------+----------+
+  //
+  // We add 2 more control codes that act as directives to the decoder, a clear
+  // code, and an End-of-Information (EoI) code. They're assigned the next
+  // available codes 4, and 5 respectively.
+  const LzwCode clear_code = 1 << color_bit_depth;
+  const LzwCode eoi_code = clear_code + 1;
+
+  // EoI code is currently the maximum LZW code used, and requires the minimum
+  // number of bits `color_bit_depth + 1` to be represented in binary, which is
+  // 3 bits in our above example.
+  uint8_t current_code_bit_size = color_bit_depth + 1;
+
+  // The next available unassigned LZW code is the value after the EoI code,
+  // which is 6 in our above example.
+  LzwCode next_available_code = eoi_code + 1;
+
+  // First, output the `clear_code` with the `current_code_bit_size` to start
+  // the compression process.
+  AppendCodeToStreamBuffer(clear_code, current_code_bit_size);
+
+  // We start at the very first pixel color index, and we use the value of that
+  // index as the current LZW code (remember from the above table that color
+  // indices map to LZW codes that have the same values).
+  LzwCode current_code = pixel_color_indices[0];
+
+  // Then we start iterating at the following index ...
+  for (size_t i = 1; i < pixel_color_indices.size(); ++i) {
+    // ... asking if the current sequence of indices represented by the
+    // `current_code` when it gets appended with `next_color_index`, does it map
+    // to an existing LZW code in the table?
+    const ColorIndex next_color_index = pixel_color_indices[i];
+    LzwCode& code_in_table =
+        code_table_[current_code].next_index_to_code[next_color_index];
+    if (code_in_table) {
+      // If yes, then `code_in_table` is a valid one, which means we have seen
+      // this pattern of indices before and mapped it to a code in the table.
+      // So we use that code we just found and the current code to represent the
+      // new pattern that results from appending `next_color_index` to the
+      // pattern of indices we had before.
+      current_code = code_in_table;
+    } else {
+      // If no, then this is the first time ever we see this new pattern, so we
+      // output `current_code` since we're starting now a new pattern for a
+      // sequence of indices that begin with `next_color_index`.
+      AppendCodeToStreamBuffer(current_code, current_code_bit_size);
+
+      // We also assign a new LZW code to that pattern we didn't see before.
+      // Note that `code_in_table` is a reference, so changing its value changes
+      // the entry in the table.
+      code_in_table = next_available_code++;
+      current_code = next_color_index;
+
+      // If the code we just added can no longer be represented as a binary
+      // value using the current minimum number of bits `current_code_bit_size`,
+      // we need to increment it by 1.
+      if (code_in_table >= (1 << current_code_bit_size)) {
+        ++current_code_bit_size;
+      }
+
+      // If we are about to exceed the maximum 12 bits per LZW code defined by
+      // the specs, we need to signal to the decoder that we're starting a new
+      // compression table.
+      if (next_available_code >= kMaxNumberOfLzwCodes) {
+        AppendCodeToStreamBuffer(clear_code, current_code_bit_size);
+
+        code_table_.clear();
+        next_available_code = eoi_code + 1;
+        current_code_bit_size = color_bit_depth + 1;
+      }
+    }
+  }
+
+  // At the end, we output the last code to the stream, and the control codes
+  // `clear_code` and `eoi_code` to signal the end of the compression.
+  AppendCodeToStreamBuffer(current_code, current_code_bit_size);
+  AppendCodeToStreamBuffer(clear_code, current_code_bit_size);
+  AppendCodeToStreamBuffer(eoi_code, current_code_bit_size);
+
+  // We need to flush all the remaining data in `pending_byte_` and
+  // `byte_stream_buffer_` to the file.
+  if (next_bit_ != 0) {
+    FlushPendingByteToStream();
+  }
+
+  if (!byte_stream_buffer_.empty()) {
+    FlushStreamBufferToFile();
+  }
+
+  // Finally, we write the block terminator.
+  gif_file_writer_->WriteByte(0x00);
+}
+
+// -----------------------------------------------------------------------------
+// LzwPixelColorIndicesWriter::CodeTableEntry:
+
+LzwPixelColorIndicesWriter::CodeTableEntry::CodeTableEntry() = default;
+LzwPixelColorIndicesWriter::CodeTableEntry::CodeTableEntry(CodeTableEntry&&) =
+    default;
+LzwPixelColorIndicesWriter::CodeTableEntry&
+LzwPixelColorIndicesWriter::CodeTableEntry::operator=(CodeTableEntry&&) =
+    default;
+LzwPixelColorIndicesWriter::CodeTableEntry::~CodeTableEntry() = default;
+
+// -----------------------------------------------------------------------------
+// LzwPixelColorIndicesWriter:
+
+void LzwPixelColorIndicesWriter::AppendCodeToStreamBuffer(
+    LzwCode code,
+    uint8_t code_bit_size) {
+  for (uint8_t i = 0; i < code_bit_size; ++i) {
+    AppendBitToPendingByte(code & 0x01);
+    code >>= 1;
+  }
+}
+
+void LzwPixelColorIndicesWriter::AppendBitToPendingByte(uint8_t bit) {
+  bit <<= next_bit_;
+  pending_byte_ |= bit;
+  ++next_bit_;
+
+  if (next_bit_ > 7) {
+    FlushPendingByteToStream();
+  }
+}
+
+void LzwPixelColorIndicesWriter::FlushPendingByteToStream() {
+  byte_stream_buffer_.push_back(pending_byte_);
+  if (byte_stream_buffer_.size() == kMaxBytesPerDataSubBlock) {
+    FlushStreamBufferToFile();
+  }
+  DCHECK_LE(byte_stream_buffer_.size(), kMaxBytesPerDataSubBlock);
+  pending_byte_ = 0;
+  next_bit_ = 0;
+}
+
+void LzwPixelColorIndicesWriter::FlushStreamBufferToFile() {
+  DCHECK(!byte_stream_buffer_.empty());
+
+  gif_file_writer_->WriteByte(byte_stream_buffer_.size());
+  gif_file_writer_->WriteBuffer(byte_stream_buffer_.data(),
+                                byte_stream_buffer_.size());
+  byte_stream_buffer_.clear();
+}
+
+}  // namespace recording
diff --git a/chromeos/ash/services/recording/lzw_pixel_color_indices_writer.h b/chromeos/ash/services/recording/lzw_pixel_color_indices_writer.h
new file mode 100644
index 0000000..b7094712
--- /dev/null
+++ b/chromeos/ash/services/recording/lzw_pixel_color_indices_writer.h
@@ -0,0 +1,168 @@
+// 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_SERVICES_RECORDING_LZW_PIXEL_COLOR_INDICES_WRITER_H_
+#define CHROMEOS_ASH_SERVICES_RECORDING_LZW_PIXEL_COLOR_INDICES_WRITER_H_
+
+#include <cstdint>
+#include <vector>
+
+#include "base/containers/flat_map.h"
+
+namespace recording {
+
+class GifFileWriter;
+
+// The specs specify a maximum of 12 bits per LZW compression code, so a 16-bit
+// unsigned integer is perfect for this type.
+using LzwCode = uint16_t;
+
+// We have a maximum of 256 colors in our color palette, so an 8-bit unsigned
+// integer is enough to represent indices to these colors.
+using ColorIndex = uint8_t;
+using ColorIndices = std::vector<ColorIndex>;
+
+// Background: After color quantization is performed on the video frame, we end
+// up with:
+// 1- A color table (or a color palette) that contains a list of colors (up to
+//    256 colors) that can best represent all the colors in the video frame.
+// 2- A list of indices (`pixel_color_indices`), which defines a mapping between
+//    each pixel in the video frame and an index of a color in the above
+//    mentioned color palette. Pixels are processed from left to right and from
+//    top to bottom.
+// If we try to write these pixel indices as they are to the GIF file, that
+// could be a huge waste of space (magine an animated GIF image of size 1920 x
+// 1080, this means we would write (1920 x 1080 = 2073600) indices for each
+// video frame.
+// In order to solve this problem, GIF uses the Variable-Length-Code Lempel-Ziv-
+// Welch (LZW) compression algorithm, which tries to take advantage of the fact
+// that many neighboring pixels may share the same color, and therefore, it’s
+// possible to use special codes to indicate a sequence of colors, rather than
+// one color at a time.
+// To have even more compression when writing those LZW codes to the file, the
+// GIF specs employs the Variable-Length-Code aspect of the algorithm as
+// follows:
+// - The number of bits needed to represent the currently used compression codes
+//   is tracked as `current_code_bit_size` (see .cc file).
+// - Instead of writing whole bytes for these codes to the file, we write only
+//   the bits needed to represent them using the `current_code_bit_size`. These
+//   bits are packages in a byte stream and eventually get written to the file.
+// - As we add new codes, when the new number of codes exceeds what's currently
+//   representable by `current_code_bit_size`, we increment it by one.
+//
+// This class implements the GIF LZW compression of a given
+// `pixel_color_indices`, and takes care of writing the output to the GIF file
+// using the given `gif_file_writer`.
+class LzwPixelColorIndicesWriter {
+ public:
+  explicit LzwPixelColorIndicesWriter(GifFileWriter* gif_file_writer);
+  LzwPixelColorIndicesWriter(const LzwPixelColorIndicesWriter&) = delete;
+  LzwPixelColorIndicesWriter& operator=(const LzwPixelColorIndicesWriter&) =
+      delete;
+  ~LzwPixelColorIndicesWriter();
+
+  // Encodes (using the variable-length-code LZW algorithm) the given
+  // `pixel_color_indices`, which correspond to a new video frame, and writes
+  // the output to the GIF file using `gif_file_writer_`. Note that the given
+  // `color_bit_depth` is the least number of bits needed to represent all the
+  // color indices in `pixel_color_indices` as binary values (e.g. if we have 4
+  // colors, then 2 bits are needed to represent them as binary values, and
+  // therefore `color_bit_depth` is 2).
+  void EncodeAndWrite(const ColorIndices& pixel_color_indices,
+                      uint8_t color_bit_depth);
+
+ private:
+  // Defines an entry in the below `code_table_`. An LZW compression code maps
+  // in `code_table_` to a value of `CodeTableEntry` to determine if followed by
+  // a certain pixel color index, does this map to an existing LZW code or not.
+  struct CodeTableEntry {
+    CodeTableEntry();
+    CodeTableEntry(CodeTableEntry&&);
+    CodeTableEntry& operator=(CodeTableEntry&&);
+    ~CodeTableEntry();
+
+    // Maps from a color index appearing in a sequence of color indices in the
+    // input stream to an LZW compression code that represents this sequence of
+    // indices.
+    base::flat_map<ColorIndex, LzwCode> next_index_to_code;
+  };
+
+  // Appends the given `code` to the `byte_stream_buffer_` by first appending it
+  // bit-by-bit to `pending_byte_` using the number of bits provided in
+  // `code_bit_size` (this is done by calling `AppendBitToPendingByte()`). Once
+  // `pending_byte_` is complete, it will be pushed back to
+  // `byte_stream_buffer_` and `byte_stream_buffer_` will be flushed to the GIF
+  // file once it reaches the `kMaxBytesPerDataSubBlock`.
+  void AppendCodeToStreamBuffer(LzwCode code, uint8_t code_bit_size);
+
+  // Appends the least significant bit of `bit` to `pending_byte_` at the
+  // `next_bit_` index. Once `pending_byte_` gets filled completely, i.e. 8 bits
+  // have been appended to it (i.e. `next_bit_` > 7), `pending_byte_` will be
+  // pushed back to the `byte_stream_buffer_` using the below call to
+  // `FlushPendingByteToStream()`.
+  void AppendBitToPendingByte(uint8_t bit);
+
+  // Pushes back the `pending_byte_` to the `byte_stream_buffer_` and resets
+  // `pending_byte_` and `next_bit_` back to zeros. Once the size of
+  // `byte_stream_buffer_` exceeds `kMaxBytesPerDataSubBlock`, the contents of
+  // the buffer will be flushed to the GIF file via a call to
+  // `FlushStreamBufferToFile()`.
+  void FlushPendingByteToStream();
+
+  // Writes the number of bytes in `byte_stream_buffer_` to the GIF file,
+  // followed by the contents of the buffer. The buffer is then cleared.
+  void FlushStreamBufferToFile();
+
+  // Used for writing bytes to the GIF file and takes care of handling IO errors
+  // and disk space / DriveFS quota issues.
+  GifFileWriter* const gif_file_writer_;
+
+  // See above background, we don't write the generated LZW codes directly to
+  // the file, however we try to do further compression by writing only the bits
+  // needed to represent them with the number bits as the value of
+  // `current_code_bit_size`. Those bits are packages in `pending_byte_` at the
+  // current `next_bit_` (which is the index of the bit at which the next bit
+  // will be appended to `pending_byte_`). Once a whole byte has been written to
+  // `pending_byte_` (i.e. when `next_bit_` is greater than 7), `pending_byte_`
+  // will be pushed back to the below `byte_stream_buffer_`, and both
+  // `pending_byte_` and `next_bit_` will be reset back to zeros waiting for the
+  // next bits to be appended.
+  uint8_t pending_byte_ = 0;
+  uint8_t next_bit_ = 0;
+
+  // See the above comment for `pending_byte_`. Once `pending_byte_` is
+  // complete, it will be pushed back to this vector. The contents of this
+  // vector will not be written to the file until:
+  // - Either the number of bytes reached `kMaxBytesPerDataSubBlock` (which is
+  //   `0xFF` or 255).
+  // - Or at the end of the compression, to output the remaining bytes in this
+  //   buffer resulting in a partial data sub-block.
+  // Before the bytes in this buffer is written to the file, the number of bytes
+  // is written to the file first (see `FlushStreamBufferToFile()`).
+  std::vector<uint8_t> byte_stream_buffer_;
+
+  // This is the LZW compression table, which is re-built every frame or when
+  // the number of generated codes exceeds the `kMaxNumberOfLzwCodes` (4096).
+  // We key into this table with the "current LZW compression code" (which
+  // represents the pattern of color indices seen so far in the input stream
+  // `pixel_color_indices`), to get a value of type `CodeTableEntry` (which is
+  // another map from a color index to an LZW compression code). What this table
+  // tells us is, for the current LZW code [G] when the next color index in the
+  // input stream [K] is appended: Do we have an existing LZW code for this new
+  // pattern (indices represented by [G])[K]?
+  // - Either yes, and in this `case code_table_[G].next_index_to_code[K]` is a
+  //   valid code [H], so we now use [H] to represent all the pattern or color
+  //   indices seen so far in the input stream.
+  // - Or no, and therefore we append the code [G] to the `byte_stream_buffer_`,
+  //   add the next available LZW code to the table to represent the pattern
+  //   (sequence of indices mapped to [G])[K], and use [K] as the code that
+  //   represents a new color indices pattern that starts at this pixel.
+  //
+  // This table implements a variant of an R-way Trie data structure.
+  base::flat_map<LzwCode, CodeTableEntry> code_table_;
+};
+
+}  // namespace recording
+
+#endif  // CHROMEOS_ASH_SERVICES_RECORDING_LZW_PIXEL_COLOR_INDICES_WRITER_H_
diff --git a/chromeos/ash/services/recording/lzw_pixel_color_indices_writer_test.cc b/chromeos/ash/services/recording/lzw_pixel_color_indices_writer_test.cc
new file mode 100644
index 0000000..cc573578
--- /dev/null
+++ b/chromeos/ash/services/recording/lzw_pixel_color_indices_writer_test.cc
@@ -0,0 +1,94 @@
+// 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 <cstdint>
+#include <string>
+#include <vector>
+
+#include "base/files/file_path.h"
+#include "base/files/file_util.h"
+#include "base/files/scoped_temp_dir.h"
+#include "chromeos/ash/services/recording/gif_file_writer.h"
+#include "chromeos/ash/services/recording/lzw_pixel_color_indices_writer.h"
+#include "chromeos/ash/services/recording/public/mojom/recording_service.mojom.h"
+#include "chromeos/ash/services/recording/recording_file_io_helper.h"
+#include "mojo/public/cpp/bindings/pending_remote.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
+
+namespace recording {
+
+class FakeDelegate : public RecordingFileIoHelper::Delegate {
+ public:
+  FakeDelegate() = default;
+  FakeDelegate(const FakeDelegate&) = delete;
+  FakeDelegate& operator=(const FakeDelegate&) = delete;
+  ~FakeDelegate() override = default;
+
+  // RecordingFileIoHelper::Delegate:
+  void NotifyFailure(mojom::RecordingStatus status) override {}
+};
+
+class LzwTest : public testing::Test {
+ public:
+  LzwTest() {
+    const bool dir_created = temp_dir_.CreateUniqueTempDir();
+    DCHECK(dir_created);
+  }
+  LzwTest(const LzwTest&) = delete;
+  LzwTest& operator=(const LzwTest&) = delete;
+  ~LzwTest() override = default;
+
+  // Returns a path for the given `file_name` under the temp dir created by this
+  // fixture.
+  base::FilePath GetPathInTempDir(const std::string& file_name) {
+    return temp_dir_.GetPath().Append(file_name);
+  }
+
+ private:
+  base::ScopedTempDir temp_dir_;
+};
+
+TEST_F(LzwTest, VerifyOutputStream) {
+  const auto gif_path = GetPathInTempDir("test.gif");
+  FakeDelegate delegate;
+  GifFileWriter gif_file_writer(
+      mojo::PendingRemote<mojom::DriveFsQuotaDelegate>(), gif_path, &delegate);
+  LzwPixelColorIndicesWriter lzw_encoder(&gif_file_writer);
+
+  // Let's assume we have the following image with only 3 colors; red, green,
+  // and blue.
+  //
+  // +---+---+---+
+  // | R | R | R |
+  // +---+---+---+
+  // | G | G | G |
+  // +---+---+---+
+  // | B | B | B |
+  // +---+---+---+
+  //
+  // This means we only have 3 color indices; R => 0, G => 1, B => 2. The bit
+  // depth in this case is 2. Let's feed this pixel color indices to the LZW
+  // encoder.
+  const ColorIndices color_indices{0, 0, 0, 1, 1, 1, 2, 2, 2};
+  lzw_encoder.EncodeAndWrite(color_indices, /*color_bit_depth=*/2);
+
+  // Verify that the contents of the file are the expected output of the
+  // encoder.
+  absl::optional<std::vector<uint8_t>> actual_file_contents =
+      base::ReadFileToBytes(gif_path);
+  ASSERT_TRUE(actual_file_contents.has_value());
+  EXPECT_THAT(
+      *actual_file_contents,
+      testing::ElementsAre(0x02,    // LZW minimum code size.
+                           0x04,    // Number of bytes in the data sub-block.
+                           0x84,    // -+
+                           0x83,    //  +-- LZW-compressed indices.
+                           0xA2,    // -+
+                           0x54,    // Clear code + EoI code.
+                           0x00));  // Block terminator.
+}
+
+}  // namespace recording
diff --git a/chromeos/ash/services/recording/recording_encoder.h b/chromeos/ash/services/recording/recording_encoder.h
index f4679f9d..f311459 100644
--- a/chromeos/ash/services/recording/recording_encoder.h
+++ b/chromeos/ash/services/recording/recording_encoder.h
@@ -10,6 +10,7 @@
 #include "base/sequence_checker.h"
 #include "base/thread_annotations.h"
 #include "base/time/time.h"
+#include "chromeos/ash/services/recording/recording_file_io_helper.h"
 #include "media/base/encoder_status.h"
 #include "media/base/video_encoder.h"
 
@@ -32,12 +33,12 @@
 // Defines a common interface for encoding audio and video frames. The concrete
 // implementation classes decides how encoding is done, and the type of the
 // underlying actual encoders.
-class RecordingEncoder {
+class RecordingEncoder : public RecordingFileIoHelper::Delegate {
  public:
   explicit RecordingEncoder(OnFailureCallback on_failure_callback);
   RecordingEncoder(const RecordingEncoder&) = delete;
   RecordingEncoder& operator=(const RecordingEncoder&) = delete;
-  virtual ~RecordingEncoder();
+  ~RecordingEncoder() override;
 
   bool did_failure_occur() const {
     DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
@@ -71,10 +72,8 @@
   // of `base::BindPostTask()`.
   virtual void FlushAndFinalize(base::OnceClosure on_done) = 0;
 
-  // Notifies the owner of this object (via `on_failure_callback_`) that a
-  // failure noted by `status` has occurred during encoding or saving to the
-  // output file.
-  void NotifyFailure(mojom::RecordingStatus status);
+  // RecordingFileIoHelper::Delegate:
+  void NotifyFailure(mojom::RecordingStatus status) override;
 
  protected:
   // Called by both the audio and video encoders to provide the `status` of
diff --git a/chromeos/ash/services/recording/recording_file_io_helper.cc b/chromeos/ash/services/recording/recording_file_io_helper.cc
index a0c4c93..674025b 100644
--- a/chromeos/ash/services/recording/recording_file_io_helper.cc
+++ b/chromeos/ash/services/recording/recording_file_io_helper.cc
@@ -24,7 +24,7 @@
 RecordingFileIoHelper::RecordingFileIoHelper(
     const base::FilePath& output_file_path,
     mojo::PendingRemote<mojom::DriveFsQuotaDelegate> drive_fs_quota_delegate,
-    RecordingEncoder* delegate)
+    Delegate* delegate)
     : output_file_path_(output_file_path),
       drive_fs_quota_delegate_remote_(std::move(drive_fs_quota_delegate)),
       delegate_(delegate) {
diff --git a/chromeos/ash/services/recording/recording_file_io_helper.h b/chromeos/ash/services/recording/recording_file_io_helper.h
index c14b47b..3aa83af 100644
--- a/chromeos/ash/services/recording/recording_file_io_helper.h
+++ b/chromeos/ash/services/recording/recording_file_io_helper.h
@@ -6,9 +6,7 @@
 #define CHROMEOS_ASH_SERVICES_RECORDING_RECORDING_FILE_IO_HELPER_H_
 
 #include "base/files/file_path.h"
-#include "base/functional/callback_forward.h"
 #include "chromeos/ash/services/recording/public/mojom/recording_service.mojom.h"
-#include "chromeos/ash/services/recording/recording_encoder.h"
 #include "mojo/public/cpp/bindings/remote.h"
 
 namespace recording {
@@ -17,21 +15,30 @@
 enum class RecordingStatus;
 }  // namespace mojom
 
-class RecordingEncoder;
-
-// Defines a helper that can be used to performs remaining free space or
+// Defines a helper that can be used to perform remaining free space or
 // remaining DriveFs quota checks when writing bytes to the output file.
 class RecordingFileIoHelper {
  public:
+  // Defines an API so that the helper can notify with any IO errors that happen
+  // while writing to the file, or when the remaining disk space / DriveFS quota
+  // are less than the minimum threshold.
+  class Delegate {
+   public:
+    virtual void NotifyFailure(mojom::RecordingStatus status) = 0;
+
+   protected:
+    virtual ~Delegate() = default;
+  };
+
   RecordingFileIoHelper(
       const base::FilePath& output_file_path,
       mojo::PendingRemote<mojom::DriveFsQuotaDelegate> drive_fs_quota_delegate,
-      RecordingEncoder* delegate);
+      Delegate* delegate);
   RecordingFileIoHelper(const RecordingFileIoHelper&) = delete;
   RecordingFileIoHelper& operator=(const RecordingFileIoHelper&) = delete;
   ~RecordingFileIoHelper();
 
-  RecordingEncoder* delegate() { return delegate_; }
+  Delegate* delegate() { return delegate_; }
 
   // Tells this helper that `num_bytes` have been written to the output file so
   // that it can perform any remaining disk space checks if needed.
@@ -63,7 +70,7 @@
   // The `RecordingEncoder` that owns this helper (either directly or
   // indirectly) which acts as a delegate of this class to notify with any IO
   // errors.
-  RecordingEncoder* const delegate_;
+  Delegate* const delegate_;
 
   // Once this value becomes <= 0, we trigger a remaining disk space poll.
   // Initialized to 0, so that we poll the disk space on the very first write
diff --git a/chromeos/crosapi/mojom/feedback.mojom b/chromeos/crosapi/mojom/feedback.mojom
index 4a55af3b..55ea975f 100644
--- a/chromeos/crosapi/mojom/feedback.mojom
+++ b/chromeos/crosapi/mojom/feedback.mojom
@@ -10,6 +10,10 @@
 // The feedback sources supported for invoking feedback report from Lacros.
 // Note: When you add a new value, please add a test case accordingly in:
 // chrome/browser/feedback/show_feedback_page_lacros_browertest.cc.
+//
+// Next MinVersion: 4
+// Next ID: 8
+//
 [Stable, Extensible]
 enum LacrosFeedbackSource {
   kLacrosBrowserCommand = 0,
@@ -19,6 +23,7 @@
   [MinVersion=2] kLacrosSadTabPage = 4,
   [MinVersion=2] kLacrosChromeLabs = 5,
   [MinVersion=2] kLacrosQuickAnswers = 6,
+  [MinVersion=3] kLacrosWindowLayoutMenu = 7,
 };
 
 [Stable]
diff --git a/chromeos/crosapi/mojom/tts.mojom b/chromeos/crosapi/mojom/tts.mojom
index 352db542..f25eef1 100644
--- a/chromeos/crosapi/mojom/tts.mojom
+++ b/chromeos/crosapi/mojom/tts.mojom
@@ -114,8 +114,8 @@
 // Interface for Tts, implemented in ash-chrome. Used by lacros-chrome to
 // communicate with ash TtsController to send the voice data and
 // speech requests to ash.
-// Next version: 3
-// Next method id: 4
+// Next version: 4
+// Next method id: 6
 [Stable, Uuid="8550e8d0-a818-49a3-93c1-d8053a33b2e6"]
 interface Tts {
   // A TtsClient can register itself with Tts, so that Tts can communicate with
@@ -140,11 +140,22 @@
   // the given |source_url|.
   [MinVersion=2]
   Stop@3(url.mojom.Url source_url);
+
+  // Requests Ash TtsController to pause speeach synthesis.
+  [MinVersion=3]
+  Pause@4();
+
+  // Requests Ash TtsController to resume speeach synthesis.
+  [MinVersion=3]
+  Resume@5();
+
 };
 
 // Interface for tts client. Implemented in lacros-chrome.
 // Each Tts client is associated with a browser context object in Lacros.
 // Used by ash-chrome to send voices to Lacros.
+// Next version: 3
+// Next method id: 5
 [Stable, Uuid="60ce0365-451e-402d-9a1c-e57350f9a202"]
 interface TtsClient {
   // Called when voices changed in ash TtsController .
@@ -163,6 +174,16 @@
   // speaking.
   [MinVersion=1]
   Stop@2(string engine_id);
+
+  // Called from Ash to request the specified Lacros speech engine to pause
+  // speaking.
+  [MinVersion=2]
+  Pause@3(string engine_id);
+
+  // Called from Ash to request the specified Lacros speech engine to resume
+  // speaking.
+  [MinVersion=2]
+  Resume@4(string engine_id);
 };
 
 // This interface receives events from an utterance which is being spoken by
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 2897edd..bd5c1f6 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
@@ -133,7 +133,7 @@
           return false;
         }
 
-        if (!chromeos::TabletState::Get()->InTabletMode()) {
+        if (!TabletState::Get()->InTabletMode()) {
           return true;
         }
 
@@ -234,6 +234,8 @@
                  views::kWindowControlCloseIcon);
 
   UpdateCaptionButtonState(/*animate=*/false);
+
+  frame_observer_.Observe(frame_);
 }
 
 FrameCaptionButtonContainerView::~FrameCaptionButtonContainerView() = default;
@@ -460,6 +462,24 @@
   }
 }
 
+void FrameCaptionButtonContainerView::OnWidgetActivationChanged(
+    views::Widget* widget,
+    bool active) {
+  if (!active) {
+    return;
+  }
+
+  // Tablet nudge is controlled by ash by another class
+  // (`::ash::TabletModeMultitaskCue`).
+  if (TabletState::Get()->InTabletMode()) {
+    return;
+  }
+
+  nudge_controller_.MaybeShowNudge(
+      widget->GetNativeWindow(),
+      /*anchor_view=*/static_cast<views::View*>(size_button_));
+}
+
 void FrameCaptionButtonContainerView::SetButtonIcon(
     views::FrameCaptionButton* button,
     views::CaptionButtonIcon icon,
@@ -571,7 +591,7 @@
   SetButtonsToNormal(Animate::kNo);
 
   frame_->Close();
-  if (chromeos::TabletState::Get()->InTabletMode()) {
+  if (TabletState::Get()->InTabletMode()) {
     base::RecordAction(
         base::UserMetricsAction("Tablet_WindowCloseFromCaptionButton"));
   } else {
diff --git a/chromeos/ui/frame/caption_buttons/frame_caption_button_container_view.h b/chromeos/ui/frame/caption_buttons/frame_caption_button_container_view.h
index c95cbc1..a019c02 100644
--- a/chromeos/ui/frame/caption_buttons/frame_caption_button_container_view.h
+++ b/chromeos/ui/frame/caption_buttons/frame_caption_button_container_view.h
@@ -14,11 +14,13 @@
 #include "chromeos/ui/frame/caption_buttons/frame_size_button.h"
 #include "chromeos/ui/frame/caption_buttons/frame_size_button_delegate.h"
 #include "chromeos/ui/frame/caption_buttons/snap_controller.h"
+#include "chromeos/ui/frame/multitask_menu/multitask_menu_nudge_controller.h"
 #include "chromeos/ui/wm/features.h"
 #include "ui/base/metadata/metadata_header_macros.h"
 #include "ui/views/animation/animation_delegate_views.h"
 #include "ui/views/layout/box_layout_view.h"
 #include "ui/views/view.h"
+#include "ui/views/widget/widget_observer.h"
 #include "ui/views/window/frame_caption_button.h"
 
 namespace gfx {
@@ -41,7 +43,8 @@
 class COMPONENT_EXPORT(CHROMEOS_UI_FRAME) FrameCaptionButtonContainerView
     : public views::BoxLayoutView,
       public FrameSizeButtonDelegate,
-      public views::AnimationDelegateViews {
+      public views::AnimationDelegateViews,
+      public views::WidgetObserver {
  public:
   METADATA_HEADER(FrameCaptionButtonContainerView);
 
@@ -87,6 +90,10 @@
       return container_view_->custom_button_;
     }
 
+    MultitaskMenuNudgeController* nudge_controller() const {
+      return &container_view_->nudge_controller_;
+    }
+
    private:
     raw_ptr<FrameCaptionButtonContainerView> container_view_;
   };
@@ -152,6 +159,9 @@
   void AnimationEnded(const gfx::Animation* animation) override;
   void AnimationProgressed(const gfx::Animation* animation) override;
 
+  // views::WidgetObserver:
+  void OnWidgetActivationChanged(views::Widget* widget, bool active) override;
+
  private:
   // Sets |button|'s icon to |icon|. If |animate| is Animate::kYes, the button
   // will crossfade to the new icon. If |animate| is Animate::kNo and
@@ -199,6 +209,9 @@
   // Stored as a `FrameSizeButton` so the multitask menu can be accessed.
   raw_ptr<chromeos::FrameSizeButton> size_button_ = nullptr;
 
+  // Handles showing the educational nudge for the clamshell multitask menu.
+  MultitaskMenuNudgeController nudge_controller_;
+
   // Mapping of the image needed to paint a button for each of the values of
   // CaptionButtonIcon.
   std::map<views::CaptionButtonIcon, const gfx::VectorIcon*> button_icon_map_;
@@ -222,6 +235,9 @@
   // Keeps track of the borderless mode being enabled or not. This defines the
   // visibility of the caption button container.
   bool is_borderless_mode_enabled_ = false;
+
+  base::ScopedObservation<views::Widget, views::WidgetObserver> frame_observer_{
+      this};
 };
 
 }  // namespace chromeos
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 4f21459..dfc8b36f 100644
--- a/chromeos/ui/frame/multitask_menu/multitask_menu_nudge_controller.cc
+++ b/chromeos/ui/frame/multitask_menu/multitask_menu_nudge_controller.cc
@@ -4,15 +4,12 @@
 
 #include "chromeos/ui/frame/multitask_menu/multitask_menu_nudge_controller.h"
 
-#include "base/check_is_test.h"
 #include "chromeos/strings/grit/chromeos_strings.h"
 #include "chromeos/ui/base/tablet_state.h"
-#include "chromeos/ui/frame/caption_buttons/frame_caption_button_container_view.h"
-#include "chromeos/ui/frame/frame_header.h"
+#include "chromeos/ui/wm/features.h"
 #include "components/prefs/pref_registry_simple.h"
 #include "ui/aura/client/screen_position_client.h"
 #include "ui/aura/window.h"
-#include "ui/aura/window_tracker.h"
 #include "ui/base/l10n/l10n_util.h"
 #include "ui/compositor/layer.h"
 #include "ui/display/screen.h"
@@ -21,9 +18,8 @@
 #include "ui/views/background.h"
 #include "ui/views/controls/label.h"
 #include "ui/views/highlight_border.h"
+#include "ui/views/layout/box_layout_view.h"
 #include "ui/views/widget/widget.h"
-#include "ui/views/window/frame_caption_button.h"
-#include "ui/wm/public/activation_client.h"
 
 namespace chromeos {
 
@@ -38,8 +34,7 @@
 
 constexpr base::TimeDelta kFadeDuration = base::Milliseconds(50);
 
-constexpr gfx::Insets kLabelInsets(10);
-constexpr int kLabelRoundingDp = 16;
+constexpr gfx::Insets kLabelInsets = gfx::Insets::VH(8, 16);
 constexpr int kLabelMaxWidth = 512;
 
 constexpr int kNudgeDistanceFromAnchor = 8;
@@ -55,6 +50,8 @@
 // Clock that can be overridden for testing.
 base::Clock* g_clock_override = nullptr;
 
+MultitaskMenuNudgeController::Delegate* g_delegate_instance = nullptr;
+
 base::Time GetTime() {
   return g_clock_override ? g_clock_override->Now() : base::Time::Now();
 }
@@ -76,11 +73,6 @@
   auto contents_view =
       views::Builder<views::BoxLayoutView>()
           .SetInsideBorderInsets(kLabelInsets)
-          .SetBackground(views::CreateThemedRoundedRectBackground(
-              ui::kColorSysSurface3, kLabelRoundingDp))
-          .SetBorder(std::make_unique<views::HighlightBorder>(
-              kLabelRoundingDp, views::HighlightBorder::Type::kHighlightBorder1,
-              /*use_light_colors=*/false))
           .AddChildren(
               views::Builder<views::Label>()
                   .SetHorizontalAlignment(gfx::ALIGN_CENTER)
@@ -94,28 +86,36 @@
                       gfx::Font::Weight::NORMAL))
                   .SetText(l10n_util::GetStringUTF16(message_id)))
           .Build();
+  const float corner_radius = contents_view->GetPreferredSize().height() / 2.0f;
+  contents_view->SetBackground(views::CreateThemedRoundedRectBackground(
+      ui::kColorSysSurface3, corner_radius));
+  contents_view->SetBorder(std::make_unique<views::HighlightBorder>(
+      corner_radius, views::HighlightBorder::Type::kHighlightBorder1,
+      /*use_light_colors=*/false));
+
   widget->SetContentsView(std::move(contents_view));
   return widget;
 }
 
 }  // namespace
 
-MultitaskMenuNudgeController::MultitaskMenuNudgeController(
-    aura::Window* root_window,
-    std::unique_ptr<Delegate> delegate)
-    : root_window_(root_window), delegate_(std::move(delegate)) {
-  display::Screen::GetScreen()->AddObserver(this);
+MultitaskMenuNudgeController::Delegate::~Delegate() {
+  DCHECK_EQ(this, g_delegate_instance);
+  g_delegate_instance = nullptr;
+}
 
-  ::wm::GetActivationClient(root_window_)->AddObserver(this);
-  root_window_observation_.Observe(root_window_);
+MultitaskMenuNudgeController::Delegate::Delegate() {
+  DCHECK_EQ(nullptr, g_delegate_instance);
+  g_delegate_instance = this;
+}
+
+MultitaskMenuNudgeController::MultitaskMenuNudgeController() {
+  display::Screen::GetScreen()->AddObserver(this);
 }
 
 MultitaskMenuNudgeController::~MultitaskMenuNudgeController() {
   DismissNudge();
   display::Screen::GetScreen()->RemoveObserver(this);
-  if (root_window_) {
-    ::wm::GetActivationClient(root_window_)->RemoveObserver(this);
-  }
 }
 
 // static
@@ -128,25 +128,33 @@
 }
 
 void MultitaskMenuNudgeController::MaybeShowNudge(aura::Window* window) {
+  MaybeShowNudge(window, /*anchor_view=*/nullptr);
+}
+
+void MultitaskMenuNudgeController::MaybeShowNudge(aura::Window* window,
+                                                  views::View* anchor_view) {
   if (!chromeos::wm::features::IsWindowLayoutMenuEnabled() ||
       g_suppress_nudge_for_testing || nudge_widget_) {
     return;
   }
 
+  // TODO(sammiequon): Delegate is not available for lacros yet.
+  if (!g_delegate_instance) {
+    return;
+  }
+
   // If the window is not visible, do not show the nudge.
   if (!window->IsVisible()) {
     return;
   }
 
-  // Tracks `window` in case it gets destroyed during the async pref fetching in
-  // lacros.
-  auto window_tracker = std::make_unique<aura::WindowTracker>();
-  window_tracker->Add(window);
-  delegate_->GetNudgePreferences(
+  // `window` and `anchor_view` can be passed safely on clamshell because they
+  // are owned by the frame which also owns `this`. They can be passed safely on
+  // tablet since tablet is controlled by ash which is sync.
+  g_delegate_instance->GetNudgePreferences(
       TabletState::Get()->InTabletMode(),
       base::BindOnce(&MultitaskMenuNudgeController::OnGetPreferences,
-                     weak_ptr_factory_.GetWeakPtr(),
-                     std::move(window_tracker)));
+                     weak_ptr_factory_.GetWeakPtr(), window, anchor_view));
 }
 
 void MultitaskMenuNudgeController::DismissNudge() {
@@ -178,28 +186,24 @@
     const gfx::Rect& old_bounds,
     const gfx::Rect& new_bounds,
     ui::PropertyChangeReason reason) {
-  if (window == window_) {
-    UpdateWidgetAndPulse();
-  }
+  DCHECK_EQ(window_, window);
+  UpdateWidgetAndPulse();
 }
 
 void MultitaskMenuNudgeController::OnWindowTargetTransformChanging(
     aura::Window* window,
     const gfx::Transform& new_transform) {
-  if (window == window_) {
-    // Prevent unintended behaviour in situations that use transforms such as
-    // overview mode.
-    // TODO(hewer): Decide how the cue behaves when adjusting the split view
-    // bounds in tablet mode.
-    DismissNudge();
-  }
+  DCHECK_EQ(window_, window);
+  // Prevent unintended behaviour in situations that use transforms such as
+  // overview mode.
+  // TODO(hewer): Decide how the cue behaves when adjusting the split view
+  // bounds in tablet mode.
+  DismissNudge();
 }
 
 void MultitaskMenuNudgeController::OnWindowStackingChanged(
     aura::Window* window) {
-  if (window != window_) {
-    return;
-  }
+  DCHECK_EQ(window_, window);
 
   // Stacking may change during the construction of the widget, at which
   // `nudge_widget_` would still be null.
@@ -214,27 +218,10 @@
 }
 
 void MultitaskMenuNudgeController::OnWindowDestroying(aura::Window* window) {
-  if (window == root_window_) {
-    ::wm::GetActivationClient(root_window_)->RemoveObserver(this);
-    root_window_ = nullptr;
-    root_window_observation_.Reset();
-    return;
-  }
-
   DCHECK_EQ(window_, window);
   DismissNudge();
 }
 
-void MultitaskMenuNudgeController::OnWindowActivated(
-    ActivationReason reason,
-    aura::Window* gained_active,
-    aura::Window* lost_active) {
-  // Tablet mode window activation is handled by `TabletModeMultitaskCue`.
-  if (gained_active && !TabletState::Get()->InTabletMode()) {
-    MaybeShowNudge(gained_active);
-  }
-}
-
 void MultitaskMenuNudgeController::OnDisplayTabletStateChanged(
     display::TabletState state) {
   switch (state) {
@@ -268,18 +255,11 @@
 }
 
 void MultitaskMenuNudgeController::OnGetPreferences(
-    std::unique_ptr<aura::WindowTracker> candidate_tracker,
+    aura::Window* window,
+    views::View* anchor_view,
     bool tablet_mode,
     int shown_count,
     base::Time last_shown_time) {
-  // Candidate window was destroyed during the async fetch preferences.
-  if (candidate_tracker->windows().empty()) {
-    return;
-  }
-
-  DCHECK_EQ(1u, candidate_tracker->windows().size());
-  aura::Window* candidate_window = candidate_tracker->windows()[0];
-
   // Tablet state has changed since we fetched preferences.
   if (tablet_mode != TabletState::Get()->InTabletMode()) {
     return;
@@ -300,36 +280,12 @@
     return;
   }
 
-  // The anchor is the button on the header that serves as the maximize or
-  // restore button (depending on the window state). Not used in tablet
-  // mode.
-  views::FrameCaptionButton* anchor_view = nullptr;
-
-  if (!tablet_mode) {
-    // Some tests create windows without a backing widget
-    // (`CreateTestWindow()`), and some widgets may not have a header, test or
-    // custom header.
-    auto* widget = views::Widget::GetWidgetForNativeWindow(candidate_window);
-    if (!widget) {
-      CHECK_IS_TEST();
-      return;
-    }
-
-    auto* frame_header = chromeos::FrameHeader::Get(widget);
-    if (!frame_header) {
-      return;
-    }
-
-    anchor_view = frame_header->caption_button_container()->size_button();
-    DCHECK(anchor_view);
-
-    // If the anchor is not visible, do not show the nudge.
-    if (!anchor_view->IsDrawn()) {
-      return;
-    }
+  // If the anchor is passed and hidden, we cannot show the nudge.
+  if (anchor_view && !anchor_view->IsDrawn()) {
+    return;
   }
 
-  window_ = candidate_window;
+  window_ = window;
   window_observation_.Observe(window_);
 
   nudge_widget_ = CreateWidget(window_);
@@ -340,8 +296,8 @@
   if (!tablet_mode) {
     // Create the layer which pulses on the maximize/restore button.
     pulse_layer_ = std::make_unique<ui::Layer>(ui::LAYER_SOLID_COLOR);
-    // TODO(b/267646118): Update the color to match the theme.
-    pulse_layer_->SetColor(SK_ColorGRAY);
+    pulse_layer_->SetColor(nudge_widget_->GetColorProvider()->GetColor(
+        ui::kColorMultitaskMenuNudgePulse));
     window_->parent()->layer()->Add(pulse_layer_.get());
   }
 
@@ -359,7 +315,8 @@
       .SetOpacity(layer, 1.0f, gfx::Tween::LINEAR);
 
   // Update the preferences.
-  delegate_->SetNudgePreferences(tablet_mode, shown_count + 1, GetTime());
+  g_delegate_instance->SetNudgePreferences(tablet_mode, shown_count + 1,
+                                           GetTime());
 
   // No need to update pulse or start timer in tablet mode.
   if (!tablet_mode) {
@@ -420,7 +377,8 @@
 
   if (tablet_mode) {
     // The nudge is placed in the top center of the window, just below the cue.
-    const int tablet_nudge_y_offset = delegate_->GetTabletNudgeYOffset();
+    const int tablet_nudge_y_offset =
+        g_delegate_instance->GetTabletNudgeYOffset();
     nudge_widget_->SetBounds(gfx::Rect(
         (window_->bounds().width() - size.width()) / 2 + window_->bounds().x(),
         tablet_nudge_y_offset + window_->bounds().y(), size.width(),
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 426c2db..7e41019b 100644
--- a/chromeos/ui/frame/multitask_menu/multitask_menu_nudge_controller.h
+++ b/chromeos/ui/frame/multitask_menu/multitask_menu_nudge_controller.h
@@ -14,7 +14,6 @@
 #include "ui/display/display_observer.h"
 #include "ui/views/view.h"
 #include "ui/views/widget/unique_widget_ptr.h"
-#include "ui/wm/public/activation_change_observer.h"
 
 class PrefRegistrySimple;
 
@@ -22,10 +21,6 @@
 class MultitaskMenuNudgeControllerTest;
 }
 
-namespace aura {
-class WindowTracker;
-}
-
 namespace ui {
 class Layer;
 }
@@ -35,7 +30,6 @@
 // Controller for showing the user education nudge for the multitask menu.
 class COMPONENT_EXPORT(CHROMEOS_UI_FRAME) MultitaskMenuNudgeController
     : public aura::WindowObserver,
-      public ::wm::ActivationChangeObserver,
       public display::DisplayObserver {
  public:
   // `tablet_mode` refers to the tablet state when the prefs are fetched. If
@@ -49,13 +43,17 @@
   // A delegate to provide platform specific implementation (ash, lacros).
   class Delegate {
    public:
-    virtual ~Delegate() = default;
+    virtual ~Delegate();
+
     virtual int GetTabletNudgeYOffset() const = 0;
     virtual void GetNudgePreferences(bool tablet_mode,
                                      GetPreferencesCallback callback) = 0;
     virtual void SetNudgePreferences(bool tablet_mode,
                                      int count,
                                      base::Time time) = 0;
+
+   protected:
+    Delegate();
   };
 
   // The name of an integer pref that counts the number of times we have shown
@@ -72,8 +70,7 @@
   static constexpr char kTabletLastShownPrefName[] =
       "cros.wm_nudge.tablet_multitask_nudge_last_shown";
 
-  MultitaskMenuNudgeController(aura::Window* root_window,
-                               std::unique_ptr<Delegate> delegate);
+  MultitaskMenuNudgeController();
   MultitaskMenuNudgeController(const MultitaskMenuNudgeController&) = delete;
   MultitaskMenuNudgeController& operator=(const MultitaskMenuNudgeController&) =
       delete;
@@ -84,6 +81,7 @@
   // Attempts to show the nudge. Reads preferences and then calls
   // `OnGetPreferences()`.
   void MaybeShowNudge(aura::Window* window);
+  void MaybeShowNudge(aura::Window* window, views::View* anchor_view);
 
   // Closes the widget and cleans up all pointers in this class.
   void DismissNudge();
@@ -101,11 +99,6 @@
   void OnWindowStackingChanged(aura::Window* window) override;
   void OnWindowDestroying(aura::Window* window) override;
 
-  // wm::ActivationChangeObserver:
-  void OnWindowActivated(ActivationReason reason,
-                         aura::Window* gained_active,
-                         aura::Window* lost_active) override;
-
   // display::DisplayObserver:
   void OnDisplayTabletStateChanged(display::TabletState state) override;
 
@@ -119,9 +112,12 @@
 
   // Callback function after fetching preferences. Shows the nudge if it can be
   // shown. The nudge can be shown if it hasn't been shown 3 times already, or
-  // shown in the last 24 hours. `candidate_tracker` tracks our candidate
-  // window; it may be destroyed during the async pref fetching in lacros.
-  void OnGetPreferences(std::unique_ptr<aura::WindowTracker> candidate_tracker,
+  // shown in the last 24 hours. `window` and `anchor_view` are the associated
+  // window and the anchor for the nudge. `anchor_view` will be null in tablet
+  // mode as the nudge shows in the top center of the window and is not anchored
+  // to anything.
+  void OnGetPreferences(aura::Window* window,
+                        views::View* anchor_view,
                         bool tablet_mode,
                         int shown_count,
                         base::Time last_shown_time);
@@ -153,14 +149,8 @@
   // button on `window_`'s frame. Null in tablet mode.
   views::View* anchor_view_ = nullptr;
 
-  aura::Window* root_window_ = nullptr;
-
-  std::unique_ptr<Delegate> delegate_;
-
   base::ScopedObservation<aura::Window, aura::WindowObserver>
       window_observation_{this};
-  base::ScopedObservation<aura::Window, aura::WindowObserver>
-      root_window_observation_{this};
 
   base::WeakPtrFactory<MultitaskMenuNudgeController> weak_ptr_factory_{this};
 };
diff --git a/components/autofill/core/browser/BUILD.gn b/components/autofill/core/browser/BUILD.gn
index f59a028c..77089c5 100644
--- a/components/autofill/core/browser/BUILD.gn
+++ b/components/autofill/core/browser/BUILD.gn
@@ -289,6 +289,8 @@
     "metrics/payments/virtual_card_enrollment_metrics.h",
     "metrics/payments/virtual_card_manual_fallback_bubble_metrics.cc",
     "metrics/payments/virtual_card_manual_fallback_bubble_metrics.h",
+    "metrics/payments/wallet_usage_data_metrics.cc",
+    "metrics/payments/wallet_usage_data_metrics.h",
     "metrics/profile_import_metrics.cc",
     "metrics/profile_import_metrics.h",
     "metrics/shadow_prediction_metrics.cc",
@@ -914,6 +916,7 @@
     "metrics/form_events/form_event_logger_base_unittest.cc",
     "metrics/payments/iban_metrics_unittest.cc",
     "metrics/payments/offers_metrics_unittest.cc",
+    "metrics/payments/wallet_usage_data_metrics_unittest.cc",
     "metrics/profile_import_metrics_unittest.cc",
     "metrics/shadow_prediction_metrics_unittest.cc",
     "metrics/stored_profile_metrics_unittest.cc",
diff --git a/components/autofill/core/browser/autofill_manager.cc b/components/autofill/core/browser/autofill_manager.cc
index 8eee2c5..c13d668c 100644
--- a/components/autofill/core/browser/autofill_manager.cc
+++ b/components/autofill/core/browser/autofill_manager.cc
@@ -30,7 +30,8 @@
 
 namespace {
 
-// Creates a reply callback for ParseFormAsync().
+// ParsingCallback(), NotifyObserversCallback(), and NotifyNoObserversCallback()
+// assemble the reply callback for ParseFormAsync().
 //
 // An event
 //   AutofillManager::OnFoo(const FormData& form, args...)
@@ -39,26 +40,58 @@
 //   AutofillManager::OnFooImpl(const FormData& form, args...)
 // unless the AutofillManager has been destructed or reset in the meantime.
 //
-// ParsingCallback(&AutofillManager::OnFooImpl, args...) creates the
-// corresponding callback to be passed to ParseFormAsync().
+// For some events, AutofillManager::Observer::On{Before,After}Foo() must be
+// called before/after AutofillManager::OnFooImpl().
 //
-// The `after_event` is supposed to be &Observer::OnAfterFoo (or nullptr if no
-// such event exists). The callback notifies the observers of `after_event`.
+// The corresponding callback for ParseFormAsync() is assembled by
+//   ParsingCallback(&AutofillManager::OnFooImpl, ...)
+//       .Then(NotifyNoObserversCallback())
+// or
+//   ParsingCallback(&AutofillManager::OnFooImpl, ...)
+//       .Then(NotifyObserversCallback(&Observer::OnAfterFoo, ...))
+//
+// `.Then(NotifyNoObserversCallback())` is needed in the first case to discard
+// the return type of ParsingCallback().
 template <typename Functor, typename... Args>
-base::OnceCallback<void(AutofillManager&, const FormData&)> ParsingCallback(
-    Functor&& functor,
-    void (AutofillManager::Observer::*after_event)(),
-    Args&&... args) {
+base::OnceCallback<AutofillManager&(AutofillManager&, const FormData&)>
+ParsingCallback(Functor&& functor, Args&&... args) {
   return base::BindOnce(
-      [](Functor&& functor, void (AutofillManager::Observer::*after_event)(),
-         std::remove_reference_t<Args&&>... args, AutofillManager& self,
-         const FormData& form) {
+      [](Functor&& functor, std::remove_reference_t<Args&&>... args,
+         AutofillManager& self, const FormData& form) -> AutofillManager& {
         base::invoke(std::forward<Functor>(functor), self, form,
                      std::forward<Args>(args)...);
-        if (after_event)
-          self.NotifyObservers(after_event);
+        return self;
       },
-      std::forward<Functor>(functor), after_event, std::forward<Args>(args)...);
+      std::forward<Functor>(functor), std::forward<Args>(args)...);
+}
+
+// See ParsingCallback().
+template <typename Functor, typename... Args>
+base::OnceCallback<void(AutofillManager&)> NotifyObserversCallback(
+    Functor&& functor,
+    Args&&... args) {
+  return base::BindOnce(
+      [](Functor&& functor, std::remove_reference_t<Args&&>... args,
+         AutofillManager& self) {
+        self.NotifyObservers(std::forward<Functor>(functor),
+                             std::forward<Args>(args)...);
+      },
+      std::forward<Functor>(functor), std::forward<Args>(args)...);
+}
+
+// See ParsingCallback().
+base::OnceCallback<void(AutofillManager&)> NotifyNoObserversCallback() {
+  return base::DoNothingAs<void(AutofillManager&)>();
+}
+
+// Collects the FormGlobalIds of `forms`.
+std::vector<FormGlobalId> GetFormGlobalIds(base::span<const FormData> forms) {
+  std::vector<FormGlobalId> form_ids;
+  form_ids.reserve(forms.size());
+  for (const FormData& form : forms) {
+    form_ids.push_back(form.global_id());
+  }
+  return form_ids;
 }
 
 // Returns the AutofillField* corresponding to |field| in |form| or nullptr,
@@ -228,9 +261,9 @@
     FillCreditCardFormImpl(form, field, credit_card, cvc);
     return;
   }
-  ParseFormAsync(
-      form, ParsingCallback(&AutofillManager::FillCreditCardFormImpl,
-                            /*after_event=*/nullptr, field, credit_card, cvc));
+  ParseFormAsync(form, ParsingCallback(&AutofillManager::FillCreditCardFormImpl,
+                                       field, credit_card, cvc)
+                           .Then(NotifyNoObserversCallback()));
 }
 
 void AutofillManager::FillProfileForm(const AutofillProfile& profile,
@@ -240,9 +273,9 @@
     FillProfileFormImpl(form, field, profile);
     return;
   }
-  ParseFormAsync(form,
-                 ParsingCallback(&AutofillManager::FillProfileFormImpl,
-                                 /*after_event=*/nullptr, field, profile));
+  ParseFormAsync(form, ParsingCallback(&AutofillManager::FillProfileFormImpl,
+                                       field, profile)
+                           .Then(NotifyNoObserversCallback()));
 }
 
 void AutofillManager::OnDidFillAutofillFormData(
@@ -251,16 +284,19 @@
   if (!IsValidFormData(form))
     return;
 
-  NotifyObservers(&Observer::OnBeforeDidFillAutofillFormData);
+  NotifyObservers(&Observer::OnBeforeDidFillAutofillFormData, form.global_id());
   if (!base::FeatureList::IsEnabled(features::kAutofillParseAsync)) {
     OnDidFillAutofillFormDataImpl(form, timestamp);
-    NotifyObservers(&Observer::OnAfterDidFillAutofillFormData);
+    NotifyObservers(&Observer::OnAfterDidFillAutofillFormData,
+                    form.global_id());
     return;
   }
   ParseFormAsync(
       form,
       ParsingCallback(&AutofillManager::OnDidFillAutofillFormDataImpl,
-                      &Observer::OnAfterDidFillAutofillFormData, timestamp));
+                      timestamp)
+          .Then(NotifyObserversCallback(
+              &Observer::OnAfterDidFillAutofillFormData, form.global_id())));
 }
 
 void AutofillManager::OnFormSubmitted(const FormData& form,
@@ -270,10 +306,10 @@
     return;
   }
 
-  NotifyObservers(&Observer::OnBeforeFormSubmitted);
+  NotifyObservers(&Observer::OnBeforeFormSubmitted, form.global_id());
   NotifyObservers(&Observer::OnFormSubmitted);
   OnFormSubmittedImpl(form, known_success, source);
-  NotifyObservers(&Observer::OnAfterFormSubmitted);
+  NotifyObservers(&Observer::OnAfterFormSubmitted, form.global_id());
 }
 
 void AutofillManager::OnFormsSeen(
@@ -293,7 +329,8 @@
   if (!ShouldParseForms(updated_forms))
     return;
 
-  NotifyObservers(&Observer::OnBeforeFormsSeen);
+  NotifyObservers(&Observer::OnBeforeFormsSeen,
+                  GetFormGlobalIds(updated_forms));
   if (!base::FeatureList::IsEnabled(features::kAutofillParseAsync)) {
     std::vector<FormData> parsed_forms;
     for (const FormData& form : updated_forms) {
@@ -325,7 +362,8 @@
     }
     if (!parsed_forms.empty())
       OnFormsParsed(parsed_forms);
-    NotifyObservers(&Observer::OnAfterFormsSeen);
+    NotifyObservers(&Observer::OnAfterFormsSeen,
+                    GetFormGlobalIds(parsed_forms));
     return;
   }
   DCHECK(base::FeatureList::IsEnabled(features::kAutofillParseAsync));
@@ -333,7 +371,8 @@
                                const std::vector<FormData>& parsed_forms) {
     if (!parsed_forms.empty())
       self.OnFormsParsed(parsed_forms);
-    self.NotifyObservers(&Observer::OnAfterFormsSeen);
+    self.NotifyObservers(&Observer::OnAfterFormsSeen,
+                         GetFormGlobalIds(parsed_forms));
   };
   ParseFormsAsync(updated_forms, base::BindOnce(ProcessParsedForms));
 }
@@ -403,17 +442,21 @@
   if (!IsValidFormData(form) || !IsValidFormFieldData(field))
     return;
 
-  NotifyObservers(&Observer::OnBeforeTextFieldDidChange);
+  NotifyObservers(&Observer::OnBeforeTextFieldDidChange, form.global_id(),
+                  field.global_id());
   NotifyObservers(&Observer::OnTextFieldDidChange);
   if (!base::FeatureList::IsEnabled(features::kAutofillParseAsync)) {
     OnTextFieldDidChangeImpl(form, field, bounding_box, timestamp);
-    NotifyObservers(&Observer::OnAfterTextFieldDidChange);
+    NotifyObservers(&Observer::OnAfterTextFieldDidChange, form.global_id(),
+                    field.global_id());
     return;
   }
-  ParseFormAsync(form,
-                 ParsingCallback(&AutofillManager::OnTextFieldDidChangeImpl,
-                                 &Observer::OnAfterTextFieldDidChange, field,
-                                 bounding_box, timestamp));
+  ParseFormAsync(
+      form,
+      ParsingCallback(&AutofillManager::OnTextFieldDidChangeImpl, field,
+                      bounding_box, timestamp)
+          .Then(NotifyObserversCallback(&Observer::OnAfterTextFieldDidChange,
+                                        form.global_id(), field.global_id())));
 }
 
 void AutofillManager::OnTextFieldDidScroll(const FormData& form,
@@ -427,9 +470,10 @@
     OnTextFieldDidScrollImpl(form, field, bounding_box);
     return;
   }
-  ParseFormAsync(form,
-                 ParsingCallback(&AutofillManager::OnTextFieldDidScrollImpl,
-                                 /*after_event=*/nullptr, field, bounding_box));
+  ParseFormAsync(
+      form, ParsingCallback(&AutofillManager::OnTextFieldDidScrollImpl, field,
+                            bounding_box)
+                .Then(NotifyNoObserversCallback()));
 }
 
 void AutofillManager::OnSelectControlDidChange(const FormData& form,
@@ -445,7 +489,8 @@
   }
   ParseFormAsync(form,
                  ParsingCallback(&AutofillManager::OnSelectControlDidChangeImpl,
-                                 /*after_event=*/nullptr, field, bounding_box));
+                                 field, bounding_box)
+                     .Then(NotifyNoObserversCallback()));
 }
 
 void AutofillManager::OnAskForValuesToFill(
@@ -457,7 +502,8 @@
   if (!IsValidFormData(form) || !IsValidFormFieldData(field))
     return;
 
-  NotifyObservers(&Observer::OnBeforeAskForValuesToFill);
+  NotifyObservers(&Observer::OnBeforeAskForValuesToFill, form.global_id(),
+                  field.global_id());
   if (!base::FeatureList::IsEnabled(features::kAutofillParseAsync)
 #if BUILDFLAG(IS_ANDROID)
       // TODO(crbug.com/1379149) Asynchronous parsing breaks Touch To Fill's
@@ -470,14 +516,17 @@
     OnAskForValuesToFillImpl(form, field, bounding_box,
                              autoselect_first_suggestion,
                              form_element_was_clicked);
-    NotifyObservers(&Observer::OnAfterAskForValuesToFill);
+    NotifyObservers(&Observer::OnAfterAskForValuesToFill, form.global_id(),
+                    field.global_id());
     return;
   }
   ParseFormAsync(
       form,
-      ParsingCallback(&AutofillManager::OnAskForValuesToFillImpl,
-                      &Observer::OnAfterAskForValuesToFill, field, bounding_box,
-                      autoselect_first_suggestion, form_element_was_clicked));
+      ParsingCallback(&AutofillManager::OnAskForValuesToFillImpl, field,
+                      bounding_box, autoselect_first_suggestion,
+                      form_element_was_clicked)
+          .Then(NotifyObserversCallback(&Observer::OnAfterAskForValuesToFill,
+                                        form.global_id(), field.global_id())));
 }
 
 void AutofillManager::OnFocusOnFormField(const FormData& form,
@@ -490,9 +539,9 @@
     OnFocusOnFormFieldImpl(form, field, bounding_box);
     return;
   }
-  ParseFormAsync(form,
-                 ParsingCallback(&AutofillManager::OnFocusOnFormFieldImpl,
-                                 /*after_event=*/nullptr, field, bounding_box));
+  ParseFormAsync(form, ParsingCallback(&AutofillManager::OnFocusOnFormFieldImpl,
+                                       field, bounding_box)
+                           .Then(NotifyNoObserversCallback()));
 }
 
 void AutofillManager::OnFocusNoLongerOnForm(bool had_interacted_form) {
@@ -520,8 +569,8 @@
     return;
   }
   ParseFormAsync(
-      form, ParsingCallback(&AutofillManager::OnSelectFieldOptionsDidChangeImpl,
-                            /*after_event=*/nullptr));
+      form, ParsingCallback(&AutofillManager::OnSelectFieldOptionsDidChangeImpl)
+                .Then(NotifyNoObserversCallback()));
 }
 
 void AutofillManager::OnJavaScriptChangedAutofilledValue(
@@ -531,17 +580,21 @@
   if (!IsValidFormData(form))
     return;
 
-  NotifyObservers(&Observer::OnBeforeJavaScriptChangedAutofilledValue);
+  NotifyObservers(&Observer::OnBeforeJavaScriptChangedAutofilledValue,
+                  form.global_id(), field.global_id());
   if (!base::FeatureList::IsEnabled(features::kAutofillParseAsync)) {
     OnJavaScriptChangedAutofilledValueImpl(form, field, old_value);
-    NotifyObservers(&Observer::OnAfterJavaScriptChangedAutofilledValue);
+    NotifyObservers(&Observer::OnAfterJavaScriptChangedAutofilledValue,
+                    form.global_id(), field.global_id());
     return;
   }
   ParseFormAsync(
       form,
       ParsingCallback(&AutofillManager::OnJavaScriptChangedAutofilledValueImpl,
-                      &Observer::OnAfterJavaScriptChangedAutofilledValue, field,
-                      old_value));
+                      field, old_value)
+          .Then(NotifyObserversCallback(
+              &Observer::OnAfterJavaScriptChangedAutofilledValue,
+              form.global_id(), field.global_id())));
 }
 
 // Returns true if |live_form| does not match |cached_form|.
diff --git a/components/autofill/core/browser/autofill_manager.h b/components/autofill/core/browser/autofill_manager.h
index 398368c..a85cea3c 100644
--- a/components/autofill/core/browser/autofill_manager.h
+++ b/components/autofill/core/browser/autofill_manager.h
@@ -10,6 +10,7 @@
 #include <string>
 #include <vector>
 
+#include "base/containers/span.h"
 #include "base/functional/bind.h"
 #include "base/memory/raw_ptr.h"
 #include "base/memory/weak_ptr.h"
@@ -71,40 +72,60 @@
   // needed.
   class Observer : public base::CheckedObserver {
    public:
-    virtual void OnAutofillManagerDestroyed() {}
-    virtual void OnAutofillManagerReset() {}
+    virtual void OnAutofillManagerDestroyed(AutofillManager& manager) {}
+    virtual void OnAutofillManagerReset(AutofillManager& manager) {}
 
-    virtual void OnBeforeLanguageDetermined() {}
-    virtual void OnAfterLanguageDetermined() {}
+    virtual void OnBeforeLanguageDetermined(AutofillManager& manager) {}
+    virtual void OnAfterLanguageDetermined(AutofillManager& manager) {}
 
-    virtual void OnBeforeFormsSeen() {}
-    virtual void OnAfterFormsSeen() {}
+    virtual void OnBeforeFormsSeen(AutofillManager& manager,
+                                   base::span<const FormGlobalId> forms) {}
+    virtual void OnAfterFormsSeen(AutofillManager& manager,
+                                  base::span<const FormGlobalId> forms) {}
 
-    virtual void OnBeforeTextFieldDidChange() {}
-    virtual void OnAfterTextFieldDidChange() {}
+    virtual void OnBeforeTextFieldDidChange(AutofillManager& manager,
+                                            FormGlobalId form,
+                                            FieldGlobalId field) {}
+    virtual void OnAfterTextFieldDidChange(AutofillManager& manager,
+                                           FormGlobalId form,
+                                           FieldGlobalId field) {}
 
-    virtual void OnBeforeDidFillAutofillFormData() {}
-    virtual void OnAfterDidFillAutofillFormData() {}
+    virtual void OnBeforeDidFillAutofillFormData(AutofillManager& manager,
+                                                 FormGlobalId form) {}
+    virtual void OnAfterDidFillAutofillFormData(AutofillManager& manager,
+                                                FormGlobalId form) {}
 
-    virtual void OnBeforeAskForValuesToFill() {}
-    virtual void OnAfterAskForValuesToFill() {}
+    virtual void OnBeforeAskForValuesToFill(AutofillManager& manager,
+                                            FormGlobalId form,
+                                            FieldGlobalId field) {}
+    virtual void OnAfterAskForValuesToFill(AutofillManager& manager,
+                                           FormGlobalId form,
+                                           FieldGlobalId field) {}
 
-    virtual void OnBeforeJavaScriptChangedAutofilledValue() {}
-    virtual void OnAfterJavaScriptChangedAutofilledValue() {}
+    virtual void OnBeforeJavaScriptChangedAutofilledValue(
+        AutofillManager& manager,
+        FormGlobalId form,
+        FieldGlobalId field) {}
+    virtual void OnAfterJavaScriptChangedAutofilledValue(
+        AutofillManager& manager,
+        FormGlobalId form,
+        FieldGlobalId field) {}
 
-    virtual void OnBeforeFormSubmitted() {}
-    virtual void OnAfterFormSubmitted() {}
+    virtual void OnBeforeFormSubmitted(AutofillManager& manager,
+                                       FormGlobalId form) {}
+    virtual void OnAfterFormSubmitted(AutofillManager& manager,
+                                      FormGlobalId form) {}
 
-    virtual void OnBeforeLoadedServerPredictions() {}
-    virtual void OnAfterLoadedServerPredictions() {}
+    virtual void OnBeforeLoadedServerPredictions(AutofillManager& manager) {}
+    virtual void OnAfterLoadedServerPredictions(AutofillManager& manager) {}
 
     // TODO(crbug.com/1330105): Clean up API: delete the events that don't
     // follow the OnBeforeFoo() / OnAfterFoo() pattern.
-    virtual void OnFormParsed() {}
-    virtual void OnTextFieldDidChange() {}
-    virtual void OnTextFieldDidScroll() {}
-    virtual void OnSelectControlDidChange() {}
-    virtual void OnFormSubmitted() {}
+    virtual void OnFormParsed(AutofillManager& manager) {}
+    virtual void OnTextFieldDidChange(AutofillManager& manager) {}
+    virtual void OnTextFieldDidScroll(AutofillManager& manager) {}
+    virtual void OnSelectControlDidChange(AutofillManager& manager) {}
+    virtual void OnFormSubmitted(AutofillManager& manager) {}
   };
 
   // TODO(crbug.com/1151542): Move to anonymous namespace once
@@ -301,9 +322,11 @@
     observers_.RemoveObserver(observer);
   }
 
-  void NotifyObservers(void (Observer::*event)()) {
-    for (Observer& observer : observers_)
-      base::invoke(event, observer);
+  template <typename Functor, typename... Args>
+  void NotifyObservers(const Functor& functor, const Args&... args) {
+    for (Observer& observer : observers_) {
+      base::invoke(functor, observer, *this, args...);
+    }
   }
 
   // Returns the present form structures seen by Autofill handler.
diff --git a/components/autofill/core/browser/autofill_manager_unittest.cc b/components/autofill/core/browser/autofill_manager_unittest.cc
index 898b1e01a..4cc6f3f0 100644
--- a/components/autofill/core/browser/autofill_manager_unittest.cc
+++ b/components/autofill/core/browser/autofill_manager_unittest.cc
@@ -202,19 +202,25 @@
   MockAutofillObserver& operator=(const MockAutofillObserver&) = delete;
   ~MockAutofillObserver() override = default;
 
-  MOCK_METHOD(void, OnFormParsed, (), (override));
+  MOCK_METHOD(void, OnFormParsed, (AutofillManager&), (override));
 
-  MOCK_METHOD(void, OnTextFieldDidChange, (), (override));
+  MOCK_METHOD(void, OnTextFieldDidChange, (AutofillManager&), (override));
 
-  MOCK_METHOD(void, OnTextFieldDidScroll, (), (override));
+  MOCK_METHOD(void, OnTextFieldDidScroll, (AutofillManager&), (override));
 
-  MOCK_METHOD(void, OnSelectControlDidChange, (), (override));
+  MOCK_METHOD(void, OnSelectControlDidChange, (AutofillManager&), (override));
 
-  MOCK_METHOD(void, OnFormSubmitted, (), (override));
+  MOCK_METHOD(void, OnFormSubmitted, (AutofillManager&), (override));
 
-  MOCK_METHOD(void, OnBeforeLoadedServerPredictions, (), (override));
+  MOCK_METHOD(void,
+              OnBeforeLoadedServerPredictions,
+              (AutofillManager&),
+              (override));
 
-  MOCK_METHOD(void, OnAfterLoadedServerPredictions, (), (override));
+  MOCK_METHOD(void,
+              OnAfterLoadedServerPredictions,
+              (AutofillManager&),
+              (override));
 };
 
 // Creates a vector of test forms which differ in their FormGlobalIds
@@ -261,12 +267,11 @@
   size_t num =
       std::min(updated_forms.size(), kAutofillManagerMaxFormCacheSize -
                                          manager.form_structures().size());
-  EXPECT_CALL(manager, ShouldParseForms(_)).Times(1).WillOnce(Return(true));
+  EXPECT_CALL(manager, ShouldParseForms).Times(1).WillOnce(Return(true));
   EXPECT_CALL(manager, OnBeforeProcessParsedForms()).Times(num > 0);
-  EXPECT_CALL(manager, OnFormProcessed(_, _)).Times(num);
-  EXPECT_CALL(manager, OnAfterProcessParsedForms(_)).Times(num > 0);
-  TestAutofillManagerWaiter waiter(
-      manager, {&AutofillManager::Observer::OnAfterFormsSeen});
+  EXPECT_CALL(manager, OnFormProcessed).Times(num);
+  EXPECT_CALL(manager, OnAfterProcessParsedForms).Times(num > 0);
+  TestAutofillManagerWaiter waiter(manager, {AutofillManagerEvent::kFormsSeen});
   manager.OnFormsSeen(updated_forms, removed_forms);
   ASSERT_TRUE(waiter.Wait());
   EXPECT_THAT(manager.form_structures(), HaveSameFormIdsAs(expectation));
@@ -387,26 +392,27 @@
   // Reset the manager, the observers should stick around.
   manager_->Reset();
 
-  EXPECT_CALL(observer, OnTextFieldDidChange()).Times(1);
+  EXPECT_CALL(observer, OnTextFieldDidChange(testing::Address(manager_.get())));
   manager_->OnTextFieldDidChange(form, field, bounds, time);
-  EXPECT_CALL(observer, OnTextFieldDidChange()).Times(0);
+  EXPECT_CALL(observer, OnTextFieldDidChange).Times(0);
 
-  EXPECT_CALL(observer, OnTextFieldDidScroll()).Times(1);
+  EXPECT_CALL(observer, OnTextFieldDidScroll(testing::Address(manager_.get())));
   manager_->OnTextFieldDidScroll(form, field, bounds);
-  EXPECT_CALL(observer, OnTextFieldDidScroll()).Times(0);
+  EXPECT_CALL(observer, OnTextFieldDidScroll).Times(0);
 
-  EXPECT_CALL(observer, OnSelectControlDidChange()).Times(1);
+  EXPECT_CALL(observer,
+              OnSelectControlDidChange(testing::Address(manager_.get())));
   manager_->OnSelectControlDidChange(form, field, bounds);
-  EXPECT_CALL(observer, OnSelectControlDidChange()).Times(0);
+  EXPECT_CALL(observer, OnSelectControlDidChange).Times(0);
 
-  EXPECT_CALL(observer, OnFormSubmitted()).Times(1);
+  EXPECT_CALL(observer, OnFormSubmitted(testing::Address(manager_.get())));
   manager_->OnFormSubmitted(form, true,
                             mojom::SubmissionSource::FORM_SUBMISSION);
-  EXPECT_CALL(observer, OnFormSubmitted()).Times(0);
+  EXPECT_CALL(observer, OnFormSubmitted).Times(0);
 
   // Remove observer from manager, the observer should no longer receive pings.
   manager_->RemoveObserver(&observer);
-  EXPECT_CALL(observer, OnTextFieldDidChange()).Times(0);
+  EXPECT_CALL(observer, OnTextFieldDidChange).Times(0);
   manager_->OnTextFieldDidChange(form, field, bounds, time);
 }
 
@@ -431,8 +437,9 @@
   SetUpObserverAndDownloadManager(/*successful_request=*/true);
 
   std::vector<FormData> forms = CreateTestForms(1);
-  EXPECT_CALL(observer_, OnBeforeLoadedServerPredictions());
-  EXPECT_CALL(observer_, OnAfterLoadedServerPredictions()).Times(0);
+  EXPECT_CALL(observer_, OnBeforeLoadedServerPredictions(
+                             testing::Address(manager_.get())));
+  EXPECT_CALL(observer_, OnAfterLoadedServerPredictions).Times(0);
   OnFormsSeenWithExpectations(*manager_, forms, {}, forms);
   task_environment_.RunUntilIdle();
 
@@ -445,8 +452,10 @@
   SetUpObserverAndDownloadManager(/*successful_request=*/false);
 
   std::vector<FormData> forms = CreateTestForms(1);
-  EXPECT_CALL(observer_, OnBeforeLoadedServerPredictions());
-  EXPECT_CALL(observer_, OnAfterLoadedServerPredictions());
+  EXPECT_CALL(observer_, OnBeforeLoadedServerPredictions(
+                             testing::Address(manager_.get())));
+  EXPECT_CALL(observer_,
+              OnAfterLoadedServerPredictions(testing::Address(manager_.get())));
   OnFormsSeenWithExpectations(*manager_, forms, {}, forms);
   task_environment_.RunUntilIdle();
 
@@ -459,11 +468,13 @@
   SetUpObserverAndDownloadManager(/*successful_request=*/true);
 
   std::vector<FormData> forms = CreateTestForms(1);
-  EXPECT_CALL(observer_, OnBeforeLoadedServerPredictions());
+  EXPECT_CALL(observer_, OnBeforeLoadedServerPredictions(
+                             testing::Address(manager_.get())));
   OnFormsSeenWithExpectations(*manager_, forms, {}, forms);
   task_environment_.RunUntilIdle();
 
-  EXPECT_CALL(observer_, OnAfterLoadedServerPredictions());
+  EXPECT_CALL(observer_,
+              OnAfterLoadedServerPredictions(testing::Address(manager_.get())));
   manager_->OnLoadedServerPredictionsForTest("", {});
 
   manager_->RemoveObserver(&observer_);
@@ -475,11 +486,13 @@
   SetUpObserverAndDownloadManager(/*successful_request=*/true);
 
   std::vector<FormData> forms = CreateTestForms(1);
-  EXPECT_CALL(observer_, OnBeforeLoadedServerPredictions());
+  EXPECT_CALL(observer_, OnBeforeLoadedServerPredictions(
+                             testing::Address(manager_.get())));
   OnFormsSeenWithExpectations(*manager_, forms, {}, forms);
   task_environment_.RunUntilIdle();
 
-  EXPECT_CALL(observer_, OnAfterLoadedServerPredictions());
+  EXPECT_CALL(observer_,
+              OnAfterLoadedServerPredictions(testing::Address(manager_.get())));
   manager_->OnLoadedServerPredictionsForTest(
       "",
       {manager_->FindCachedFormById(forms[0].global_id())->form_signature()});
diff --git a/components/autofill/core/browser/metrics/payments/wallet_usage_data_metrics.cc b/components/autofill/core/browser/metrics/payments/wallet_usage_data_metrics.cc
new file mode 100644
index 0000000..7e238fa
--- /dev/null
+++ b/components/autofill/core/browser/metrics/payments/wallet_usage_data_metrics.cc
@@ -0,0 +1,22 @@
+// 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/metrics/payments/wallet_usage_data_metrics.h"
+
+#include "base/metrics/histogram_functions.h"
+#include "base/metrics/histogram_macros.h"
+
+namespace autofill::autofill_metrics {
+
+void LogStoredVirtualCardUsageCount(const size_t usage_data_size) {
+  base::UmaHistogramCounts1000(
+      "Autofill.VirtualCardUsageData.StoredUsageDataCount", usage_data_size);
+}
+
+void LogSyncedVirtualCardUsageDataBeingValid(bool valid) {
+  base::UmaHistogramBoolean(
+      "Autofill.VirtualCardUsageData.SyncedUsageDataBeingValid", valid);
+}
+
+}  // namespace autofill::autofill_metrics
\ No newline at end of file
diff --git a/components/autofill/core/browser/metrics/payments/wallet_usage_data_metrics.h b/components/autofill/core/browser/metrics/payments/wallet_usage_data_metrics.h
new file mode 100644
index 0000000..9e9b5db7b
--- /dev/null
+++ b/components/autofill/core/browser/metrics/payments/wallet_usage_data_metrics.h
@@ -0,0 +1,25 @@
+// 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_METRICS_PAYMENTS_WALLET_USAGE_DATA_METRICS_H_
+#define COMPONENTS_AUTOFILL_CORE_BROWSER_METRICS_PAYMENTS_WALLET_USAGE_DATA_METRICS_H_
+
+#include "components/autofill/core/browser/data_model/autofill_wallet_usage_data.h"
+#include "components/autofill/core/browser/metrics/autofill_metrics.h"
+
+class VirtualCardUsageData;
+
+namespace autofill::autofill_metrics {
+
+// Logs metrics about the virtual card usage data associated with a Chrome
+// profile. This should be called each time a Chrome profile is launched.
+void LogStoredVirtualCardUsageCount(size_t usage_data_size);
+
+// Logs whether a synced virtual card usage data is valid. Checked for every
+// synced virtual card usage data.
+void LogSyncedVirtualCardUsageDataBeingValid(bool invalid);
+
+}  // namespace autofill::autofill_metrics
+
+#endif  // COMPONENTS_AUTOFILL_CORE_BROWSER_METRICS_PAYMENTS_WALLET_USAGE_DATA_METRICS_H_
\ No newline at end of file
diff --git a/components/autofill/core/browser/metrics/payments/wallet_usage_data_metrics_unittest.cc b/components/autofill/core/browser/metrics/payments/wallet_usage_data_metrics_unittest.cc
new file mode 100644
index 0000000..a73033d8
--- /dev/null
+++ b/components/autofill/core/browser/metrics/payments/wallet_usage_data_metrics_unittest.cc
@@ -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.
+
+#include "components/autofill/core/browser/metrics/payments/wallet_usage_data_metrics.h"
+#include "base/test/metrics/histogram_tester.h"
+#include "components/autofill/core/browser/autofill_test_utils.h"
+#include "components/autofill/core/browser/data_model/autofill_wallet_usage_data.h"
+#include "components/autofill/core/browser/metrics/autofill_metrics_test_base.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace autofill::autofill_metrics {
+
+class WalletUsageDataMetricsTest : public metrics::AutofillMetricsBaseTest,
+                                   public testing::Test {
+ public:
+  void SetUp() override { SetUpHelper(); }
+
+  void TearDown() override { TearDownHelper(); }
+};
+
+// Tests that we correctly log the number of stored virtual card usage data.
+TEST_F(WalletUsageDataMetricsTest, LogStoredVirtualCardUsageMetrics) {
+  std::vector<std::unique_ptr<VirtualCardUsageData>> virtual_card_usage_data;
+  VirtualCardUsageData virtual_card_usage_data1 =
+      test::GetVirtualCardUsageData1();
+  VirtualCardUsageData virtual_card_usage_data2 =
+      test::GetVirtualCardUsageData2();
+  virtual_card_usage_data.push_back(
+      std::make_unique<VirtualCardUsageData>(virtual_card_usage_data1));
+  virtual_card_usage_data.push_back(
+      std::make_unique<VirtualCardUsageData>(virtual_card_usage_data2));
+
+  base::HistogramTester histogram_tester;
+  autofill_metrics::LogStoredVirtualCardUsageCount(
+      virtual_card_usage_data.size());
+
+  // Validate the count metrics.
+  histogram_tester.ExpectBucketCount(
+      "Autofill.VirtualCardUsageData.StoredUsageDataCount", 2, 1);
+}
+
+}  // namespace autofill::autofill_metrics
\ No newline at end of file
diff --git a/components/autofill/core/browser/personal_data_manager.cc b/components/autofill/core/browser/personal_data_manager.cc
index cba2393..dad9b34 100644
--- a/components/autofill/core/browser/personal_data_manager.cc
+++ b/components/autofill/core/browser/personal_data_manager.cc
@@ -48,6 +48,7 @@
 #include "components/autofill/core/browser/metrics/autofill_metrics.h"
 #include "components/autofill/core/browser/metrics/payments/iban_metrics.h"
 #include "components/autofill/core/browser/metrics/payments/offers_metrics.h"
+#include "components/autofill/core/browser/metrics/payments/wallet_usage_data_metrics.h"
 #include "components/autofill/core/browser/metrics/stored_profile_metrics.h"
 #include "components/autofill/core/browser/personal_data_manager_observer.h"
 #include "components/autofill/core/browser/strike_databases/autofill_profile_migration_strike_database.h"
@@ -2151,6 +2152,16 @@
   }
 }
 
+void PersonalDataManager::LogStoredVirtualCardUsageMetrics() const {
+  if (!has_logged_stored_virtual_card_usage_metrics_) {
+    autofill_metrics::LogStoredVirtualCardUsageCount(
+        autofill_virtual_card_usage_data_.size());
+
+    // Only log this info once per chrome user profile load.
+    has_logged_stored_virtual_card_usage_metrics_ = true;
+  }
+}
+
 std::string PersonalDataManager::MostCommonCountryCodeFromProfiles() const {
   if (!IsAutofillEnabled())
     return std::string();
diff --git a/components/autofill/core/browser/personal_data_manager.h b/components/autofill/core/browser/personal_data_manager.h
index c4259aa..adba601 100644
--- a/components/autofill/core/browser/personal_data_manager.h
+++ b/components/autofill/core/browser/personal_data_manager.h
@@ -713,6 +713,10 @@
   // offer data. On subsequent calls, does nothing.
   void LogStoredOfferMetrics() const;
 
+  // The first time this is called, logs UMA metrics about the users's autofill
+  // virtual card usage data. On subsequent calls, does nothing.
+  void LogStoredVirtualCardUsageMetrics() const;
+
   // Whether the server cards are enabled and should be suggested to the user.
   virtual bool ShouldSuggestServerCards() const;
 
@@ -974,6 +978,10 @@
   // Whether we have already logged the stored offer metrics this session.
   mutable bool has_logged_stored_offer_metrics_ = false;
 
+  // Whether we have already logged the stored virtual card usage metrics this
+  // session.
+  mutable bool has_logged_stored_virtual_card_usage_metrics_ = false;
+
   // An observer to listen for changes to prefs::kAutofillCreditCardEnabled.
   std::unique_ptr<BooleanPrefMember> credit_card_enabled_pref_;
 
diff --git a/components/autofill/core/browser/personal_data_manager_cleaner.cc b/components/autofill/core/browser/personal_data_manager_cleaner.cc
index 35f8fa06..50e0315 100644
--- a/components/autofill/core/browser/personal_data_manager_cleaner.cc
+++ b/components/autofill/core/browser/personal_data_manager_cleaner.cc
@@ -65,11 +65,12 @@
   if (!personal_data_manager_->IsSyncEnabledFor(syncer::AUTOFILL_WALLET_DATA))
     ApplyCardFixesAndCleanups();
 
-  // Log address, credit card and offer startup metrics.
+  // Log address, credit card, offer, and usage data startup metrics.
   personal_data_manager_->LogStoredProfileMetrics();
   personal_data_manager_->LogStoredCreditCardMetrics();
   personal_data_manager_->LogStoredIbanMetrics();
   personal_data_manager_->LogStoredOfferMetrics();
+  personal_data_manager_->LogStoredVirtualCardUsageMetrics();
 
   personal_data_manager_->NotifyPersonalDataObserver();
 }
diff --git a/components/autofill/core/browser/test_autofill_manager_waiter.cc b/components/autofill/core/browser/test_autofill_manager_waiter.cc
index 99b7910..6dfce1d 100644
--- a/components/autofill/core/browser/test_autofill_manager_waiter.cc
+++ b/components/autofill/core/browser/test_autofill_manager_waiter.cc
@@ -18,116 +18,152 @@
 TestAutofillManagerWaiter::State::~State() = default;
 
 TestAutofillManagerWaiter::EventCount* TestAutofillManagerWaiter::State::Get(
-    AfterEvent event) {
-  auto it = base::ranges::find(events, event, &EventCount::event);
-  return it != events.end() ? &*it : nullptr;
+    Event event) {
+  auto it = events.find(event);
+  return it != events.end() ? &it->second : nullptr;
 }
 
 TestAutofillManagerWaiter::EventCount&
-TestAutofillManagerWaiter::State::GetOrCreate(AfterEvent event,
+TestAutofillManagerWaiter::State::GetOrCreate(Event event,
                                               base::Location location) {
-  if (EventCount* e = Get(event))
+  if (EventCount* e = Get(event)) {
     return *e;
-  return *events.insert(events.end(), {event, location});
+  }
+  EventCount& e = events[event];
+  e = EventCount{.location = location};
+  return e;
 }
 
 size_t TestAutofillManagerWaiter::State::num_pending_calls() const {
   size_t pending_calls = 0;
-  for (const EventCount& e : events)
-    pending_calls += e.num_pending_calls;
+  for (const auto& [_, event_count] : events) {
+    pending_calls += event_count.num_pending_calls;
+  }
   return pending_calls;
 }
 
 size_t TestAutofillManagerWaiter::State::num_total_calls() const {
   size_t total_calls = 0;
-  for (const EventCount& e : events)
-    total_calls += e.num_total_calls;
+  for (const auto& [_, event_count] : events) {
+    total_calls += event_count.num_total_calls;
+  }
   return total_calls;
 }
 
 std::string TestAutofillManagerWaiter::State::Describe() const {
   std::vector<std::string> strings;
-  for (const auto& e : events) {
-    strings.push_back(base::StringPrintf(
-        "[event=%s, pending=%zu, total=%zu]", e.location.function_name(),
-        e.num_pending_calls, e.num_total_calls));
+  for (const auto& [_, event_count] : events) {
+    strings.push_back(base::StringPrintf("[event=%s, pending=%zu, total=%zu]",
+                                         event_count.location.function_name(),
+                                         event_count.num_pending_calls,
+                                         event_count.num_total_calls));
   }
   return base::JoinString(strings, ", ");
 }
 
 TestAutofillManagerWaiter::TestAutofillManagerWaiter(
     AutofillManager& manager,
-    std::initializer_list<AfterEvent> relevant_events)
+    std::initializer_list<Event> relevant_events)
     : relevant_events_(relevant_events) {
   observation_.Observe(&manager);
 }
 
 TestAutofillManagerWaiter::~TestAutofillManagerWaiter() = default;
 
-void TestAutofillManagerWaiter::OnAutofillManagerDestroyed() {
+void TestAutofillManagerWaiter::OnAutofillManagerDestroyed(
+    AutofillManager& manager) {
   observation_.Reset();
 }
 
-void TestAutofillManagerWaiter::OnAutofillManagerReset() {
+void TestAutofillManagerWaiter::OnAutofillManagerReset(
+    AutofillManager& manager) {
   Reset();
 }
 
-void TestAutofillManagerWaiter::OnBeforeLanguageDetermined() {
-  Increment(&AutofillManager::Observer::OnAfterLanguageDetermined);
+void TestAutofillManagerWaiter::OnBeforeLanguageDetermined(
+    AutofillManager& manager) {
+  Increment(Event::kLanguageDetermined);
 }
 
-void TestAutofillManagerWaiter::OnAfterLanguageDetermined() {
-  Decrement(&AutofillManager::Observer::OnAfterLanguageDetermined);
+void TestAutofillManagerWaiter::OnAfterLanguageDetermined(
+    AutofillManager& manager) {
+  Decrement(Event::kLanguageDetermined);
 }
 
-void TestAutofillManagerWaiter::OnBeforeFormsSeen() {
-  Increment(&AutofillManager::Observer::OnAfterFormsSeen);
+void TestAutofillManagerWaiter::OnBeforeFormsSeen(
+    AutofillManager& manager,
+    base::span<const FormGlobalId> forms) {
+  Increment(Event::kFormsSeen);
 }
 
-void TestAutofillManagerWaiter::OnAfterFormsSeen() {
-  Decrement(&AutofillManager::Observer::OnAfterFormsSeen);
+void TestAutofillManagerWaiter::OnAfterFormsSeen(
+    AutofillManager& manager,
+    base::span<const FormGlobalId> forms) {
+  Decrement(Event::kFormsSeen);
 }
 
-void TestAutofillManagerWaiter::OnBeforeTextFieldDidChange() {
-  Increment(&AutofillManager::Observer::OnAfterTextFieldDidChange);
+void TestAutofillManagerWaiter::OnBeforeTextFieldDidChange(
+    AutofillManager& manager,
+    FormGlobalId form,
+    FieldGlobalId field) {
+  Increment(Event::kTextFieldDidChange);
 }
 
-void TestAutofillManagerWaiter::OnAfterTextFieldDidChange() {
-  Decrement(&AutofillManager::Observer::OnAfterTextFieldDidChange);
+void TestAutofillManagerWaiter::OnAfterTextFieldDidChange(
+    AutofillManager& manager,
+    FormGlobalId form,
+    FieldGlobalId field) {
+  Decrement(Event::kTextFieldDidChange);
 }
 
-void TestAutofillManagerWaiter::OnBeforeAskForValuesToFill() {
-  Increment(&AutofillManager::Observer::OnAfterAskForValuesToFill);
+void TestAutofillManagerWaiter::OnBeforeAskForValuesToFill(
+    AutofillManager& manager,
+    FormGlobalId form,
+    FieldGlobalId field) {
+  Increment(Event::kAskForValuesToFill);
 }
 
-void TestAutofillManagerWaiter::OnAfterAskForValuesToFill() {
-  Decrement(&AutofillManager::Observer::OnAfterAskForValuesToFill);
+void TestAutofillManagerWaiter::OnAfterAskForValuesToFill(
+    AutofillManager& manager,
+    FormGlobalId form,
+    FieldGlobalId field) {
+  Decrement(Event::kAskForValuesToFill);
 }
 
-void TestAutofillManagerWaiter::OnBeforeDidFillAutofillFormData() {
-  Increment(&AutofillManager::Observer::OnAfterDidFillAutofillFormData);
+void TestAutofillManagerWaiter::OnBeforeDidFillAutofillFormData(
+    AutofillManager& manager,
+    FormGlobalId form) {
+  Increment(Event::kDidFillAutofillFormData);
 }
 
-void TestAutofillManagerWaiter::OnAfterDidFillAutofillFormData() {
-  Decrement(&AutofillManager::Observer::OnAfterDidFillAutofillFormData);
+void TestAutofillManagerWaiter::OnAfterDidFillAutofillFormData(
+    AutofillManager& manager,
+    FormGlobalId form) {
+  Decrement(Event::kDidFillAutofillFormData);
 }
 
-void TestAutofillManagerWaiter::OnBeforeJavaScriptChangedAutofilledValue() {
-  Increment(
-      &AutofillManager::Observer::OnAfterJavaScriptChangedAutofilledValue);
+void TestAutofillManagerWaiter::OnBeforeJavaScriptChangedAutofilledValue(
+    AutofillManager& manager,
+    FormGlobalId form,
+    FieldGlobalId field) {
+  Increment(Event::kJavaScriptChangedAutofilledValue);
 }
 
-void TestAutofillManagerWaiter::OnAfterJavaScriptChangedAutofilledValue() {
-  Decrement(
-      &AutofillManager::Observer::OnAfterJavaScriptChangedAutofilledValue);
+void TestAutofillManagerWaiter::OnAfterJavaScriptChangedAutofilledValue(
+    AutofillManager& manager,
+    FormGlobalId form,
+    FieldGlobalId field) {
+  Decrement(Event::kJavaScriptChangedAutofilledValue);
 }
 
-void TestAutofillManagerWaiter::OnBeforeFormSubmitted() {
-  Increment(&AutofillManager::Observer::OnAfterFormSubmitted);
+void TestAutofillManagerWaiter::OnBeforeFormSubmitted(AutofillManager& manager,
+                                                      FormGlobalId form) {
+  Increment(Event::kFormSubmitted);
 }
 
-void TestAutofillManagerWaiter::OnAfterFormSubmitted() {
-  Decrement(&AutofillManager::Observer::OnAfterFormSubmitted);
+void TestAutofillManagerWaiter::OnAfterFormSubmitted(AutofillManager& manager,
+                                                     FormGlobalId form) {
+  Decrement(Event::kFormSubmitted);
 }
 
 void TestAutofillManagerWaiter::Reset() {
@@ -141,11 +177,11 @@
   swap(state_, state);
 }
 
-bool TestAutofillManagerWaiter::IsRelevant(AfterEvent event) const {
+bool TestAutofillManagerWaiter::IsRelevant(Event event) const {
   return relevant_events_.empty() || base::Contains(relevant_events_, event);
 }
 
-void TestAutofillManagerWaiter::Increment(AfterEvent event,
+void TestAutofillManagerWaiter::Increment(Event event,
                                           base::Location location) {
   base::AutoLock lock(state_->lock);
   if (!IsRelevant(event)) {
@@ -165,7 +201,7 @@
   ++e.num_pending_calls;
 }
 
-void TestAutofillManagerWaiter::Decrement(AfterEvent event,
+void TestAutofillManagerWaiter::Decrement(Event event,
                                           base::Location location) {
   base::AutoLock lock(state_->lock);
   if (!IsRelevant(event)) {
@@ -251,17 +287,21 @@
     }
 
    private:
-    void OnAutofillManagerDestroyed() override {
+    void OnAutofillManagerDestroyed(AutofillManager& manager) override {
+      DCHECK_EQ(&manager, &manager_);
       run_loop_.Quit();
       observation_.Reset();
     }
 
-    void OnAutofillManagerReset() override {
+    void OnAutofillManagerReset(AutofillManager& manager) override {
+      DCHECK_EQ(&manager, &manager_);
       run_loop_.Quit();
       observation_.Reset();
     }
 
-    void OnAfterFormsSeen() override {
+    void OnAfterFormsSeen(AutofillManager& manager,
+                          base::span<const FormGlobalId> forms) override {
+      DCHECK_EQ(&manager, &manager_);
       if (const auto* form = FindForm()) {
         matching_form_ = form;
         run_loop_.Quit();
diff --git a/components/autofill/core/browser/test_autofill_manager_waiter.h b/components/autofill/core/browser/test_autofill_manager_waiter.h
index 233f68cd..d9dc6876 100644
--- a/components/autofill/core/browser/test_autofill_manager_waiter.h
+++ b/components/autofill/core/browser/test_autofill_manager_waiter.h
@@ -5,19 +5,34 @@
 #ifndef COMPONENTS_AUTOFILL_CORE_BROWSER_TEST_AUTOFILL_MANAGER_WAITER_H_
 #define COMPONENTS_AUTOFILL_CORE_BROWSER_TEST_AUTOFILL_MANAGER_WAITER_H_
 
-#include <list>
+#include <map>
 #include <memory>
 #include <vector>
 
+#include "base/containers/span.h"
 #include "base/run_loop.h"
 #include "base/scoped_observation.h"
 #include "base/synchronization/lock.h"
 #include "base/time/time.h"
+#include "base/types/cxx23_to_underlying.h"
 #include "components/autofill/core/browser/autofill_manager.h"
+#include "components/autofill/core/common/unique_ids.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
 namespace autofill {
 
+// One constant `kFoo` for each event
+// `AutofillManager::Observer::On{Before,After}Foo()`.
+enum class AutofillManagerEvent {
+  kLanguageDetermined,
+  kFormsSeen,
+  kTextFieldDidChange,
+  kAskForValuesToFill,
+  kDidFillAutofillFormData,
+  kJavaScriptChangedAutofilledValue,
+  kFormSubmitted,
+};
+
 // Records AutofillManager::Observer::OnBeforeFoo() events and blocks until the
 // corresponding OnAfterFoo() events have happened.
 //
@@ -43,8 +58,8 @@
 // Typical usage is as follows:
 //
 //   TestAutofillManagerWaiter waiter(manager,
-//                                    {&AutofillManager::Observer::OnAfterFoo,
-//                                     &AutofillManager::Observer::OnAfterBar,
+//                                    {AutofillManagerEvent::kFoo,
+//                                     AutofillManagerEvent::kBar,
 //                                     ...});
 //   ... trigger events ...
 //   ASSERT_TRUE(waiter.Wait());  // Blocks.
@@ -53,7 +68,7 @@
 // say, 1 event because triggering events is asynchronous due to Mojo:
 //
 //   TestAutofillManagerWaiter waiter(manager,
-//                                    {&AutofillManager::Observer::OnAfterFoo,
+//                                    {AutofillManagerEvent::kFoo,
 //                                     ...});
 //   ... trigger asynchronous OnFoo event ...
 //   ASSERT_TRUE(waiter.Wait(1));  // Blocks until at least one OnFoo() event
@@ -64,13 +79,11 @@
 // OnAfterFoo() calls.
 class TestAutofillManagerWaiter : public AutofillManager::Observer {
  public:
-  // An OnFooAfter() event. As a convention, throughout this class we use the
-  // OnAfterFoo() events to identify the pair of OnAfterFoo() / OnBeforeFoo().
-  using AfterEvent = void (AutofillManager::Observer::*)();
+  using Event = AutofillManagerEvent;
 
   explicit TestAutofillManagerWaiter(
       AutofillManager& manager,
-      std::initializer_list<AfterEvent> relevant_events = {});
+      std::initializer_list<Event> relevant_events = {});
   TestAutofillManagerWaiter(const TestAutofillManagerWaiter&) = delete;
   TestAutofillManagerWaiter& operator=(const TestAutofillManagerWaiter&) =
       delete;
@@ -91,8 +104,6 @@
 
  private:
   struct EventCount {
-    // An AutofillManager::Observer::OnAfterFoo() event.
-    AfterEvent event;
     // The OnBeforeFoo() function. Used for meaningful error messages.
     base::Location location;
     // The total number of recorded OnBeforeFoo() events.
@@ -109,18 +120,17 @@
     State& operator=(State&) = delete;
     ~State();
 
-    EventCount& GetOrCreate(AfterEvent event, base::Location location);
-    EventCount* Get(AfterEvent event);
+    EventCount& GetOrCreate(Event event, base::Location location);
+    EventCount* Get(Event event);
 
     size_t num_total_calls() const;
     size_t num_pending_calls() const;
 
     std::string Describe() const;
 
-    // Effectively a map from `AfterEvent` to its count. Since `AfterEvent` is
-    // only equality-comparable, we use a list. (The list, rather than a vector,
-    // avoids invalidation of the references returned by GetOrCreate().)
-    std::list<EventCount> events;
+    // The std::map guarantees that references aren't invalidated by
+    // GetOrCreate().
+    std::map<Event, EventCount> events;
     // Decrement() unblocks Wait() when the number of awaited calls reaches 0.
     size_t num_awaiting_total_calls = std::numeric_limits<size_t>::max();
     // Running iff there are no awaited and no pending calls.
@@ -131,37 +141,55 @@
     base::Lock lock;
   };
 
-  bool IsRelevant(AfterEvent event) const;
-  void Increment(AfterEvent event,
+  bool IsRelevant(Event event) const;
+  void Increment(Event event,
                  base::Location location = base::Location::Current());
-  void Decrement(AfterEvent event,
+  void Decrement(Event event,
                  base::Location location = base::Location::Current());
 
-  void OnAutofillManagerDestroyed() override;
-  void OnAutofillManagerReset() override;
+  void OnAutofillManagerDestroyed(AutofillManager& manager) override;
+  void OnAutofillManagerReset(AutofillManager& manager) override;
 
-  void OnBeforeLanguageDetermined() override;
-  void OnAfterLanguageDetermined() override;
+  void OnBeforeLanguageDetermined(AutofillManager& manager) override;
+  void OnAfterLanguageDetermined(AutofillManager& manager) override;
 
-  void OnBeforeFormsSeen() override;
-  void OnAfterFormsSeen() override;
+  void OnBeforeFormsSeen(AutofillManager& manager,
+                         base::span<const FormGlobalId> forms) override;
+  void OnAfterFormsSeen(AutofillManager& manager,
+                        base::span<const FormGlobalId> forms) override;
 
-  void OnBeforeTextFieldDidChange() override;
-  void OnAfterTextFieldDidChange() override;
+  void OnBeforeTextFieldDidChange(AutofillManager& manager,
+                                  FormGlobalId form,
+                                  FieldGlobalId field) override;
+  void OnAfterTextFieldDidChange(AutofillManager& manager,
+                                 FormGlobalId form,
+                                 FieldGlobalId field) override;
 
-  void OnBeforeAskForValuesToFill() override;
-  void OnAfterAskForValuesToFill() override;
+  void OnBeforeAskForValuesToFill(AutofillManager& manager,
+                                  FormGlobalId form,
+                                  FieldGlobalId field) override;
+  void OnAfterAskForValuesToFill(AutofillManager& manager,
+                                 FormGlobalId form,
+                                 FieldGlobalId field) override;
 
-  void OnBeforeDidFillAutofillFormData() override;
-  void OnAfterDidFillAutofillFormData() override;
+  void OnBeforeDidFillAutofillFormData(AutofillManager& manager,
+                                       FormGlobalId form) override;
+  void OnAfterDidFillAutofillFormData(AutofillManager& manager,
+                                      FormGlobalId form) override;
 
-  void OnBeforeJavaScriptChangedAutofilledValue() override;
-  void OnAfterJavaScriptChangedAutofilledValue() override;
+  void OnBeforeJavaScriptChangedAutofilledValue(AutofillManager& manager,
+                                                FormGlobalId form,
+                                                FieldGlobalId field) override;
+  void OnAfterJavaScriptChangedAutofilledValue(AutofillManager& manager,
+                                               FormGlobalId form,
+                                               FieldGlobalId field) override;
 
-  void OnBeforeFormSubmitted() override;
-  void OnAfterFormSubmitted() override;
+  void OnBeforeFormSubmitted(AutofillManager& manager,
+                             FormGlobalId form) override;
+  void OnAfterFormSubmitted(AutofillManager& manager,
+                            FormGlobalId form) override;
 
-  std::vector<AfterEvent> relevant_events_;
+  std::vector<Event> relevant_events_;
   std::unique_ptr<State> state_ = std::make_unique<State>();
   base::TimeDelta timeout_ = base::Seconds(30);
   base::ScopedObservation<AutofillManager, AutofillManager::Observer>
diff --git a/components/autofill/core/browser/test_browser_autofill_manager.cc b/components/autofill/core/browser/test_browser_autofill_manager.cc
index d9d92856..1077e422 100644
--- a/components/autofill/core/browser/test_browser_autofill_manager.cc
+++ b/components/autofill/core/browser/test_browser_autofill_manager.cc
@@ -42,8 +42,8 @@
 
 void TestBrowserAutofillManager::OnLanguageDetermined(
     const translate::LanguageDetectionDetails& details) {
-  TestAutofillManagerWaiter waiter(
-      *this, {&AutofillManager::Observer::OnAfterLanguageDetermined});
+  TestAutofillManagerWaiter waiter(*this,
+                                   {AutofillManagerEvent::kLanguageDetermined});
   AutofillManager::OnLanguageDetermined(details);
   ASSERT_TRUE(waiter.Wait());
 }
@@ -51,7 +51,7 @@
 void TestBrowserAutofillManager::OnFormsSeen(
     const std::vector<FormData>& updated_forms,
     const std::vector<FormGlobalId>& removed_forms) {
-  TestAutofillManagerWaiter waiter(*this, {&Observer::OnAfterFormsSeen});
+  TestAutofillManagerWaiter waiter(*this, {AutofillManagerEvent::kFormsSeen});
   AutofillManager::OnFormsSeen(updated_forms, removed_forms);
   ASSERT_TRUE(waiter.Wait());
 }
@@ -62,7 +62,7 @@
     const gfx::RectF& bounding_box,
     const base::TimeTicks timestamp) {
   TestAutofillManagerWaiter waiter(*this,
-                                   {&Observer::OnAfterTextFieldDidChange});
+                                   {AutofillManagerEvent::kTextFieldDidChange});
   AutofillManager::OnTextFieldDidChange(form, field, bounding_box, timestamp);
   ASSERT_TRUE(waiter.Wait());
 }
@@ -70,8 +70,8 @@
 void TestBrowserAutofillManager::OnDidFillAutofillFormData(
     const FormData& form,
     const base::TimeTicks timestamp) {
-  TestAutofillManagerWaiter waiter(*this,
-                                   {&Observer::OnAfterDidFillAutofillFormData});
+  TestAutofillManagerWaiter waiter(
+      *this, {AutofillManagerEvent::kDidFillAutofillFormData});
   AutofillManager::OnDidFillAutofillFormData(form, timestamp);
   ASSERT_TRUE(waiter.Wait());
 }
@@ -83,7 +83,7 @@
     AutoselectFirstSuggestion autoselect_first_suggestion,
     FormElementWasClicked form_element_was_clicked) {
   TestAutofillManagerWaiter waiter(*this,
-                                   {&Observer::OnAfterAskForValuesToFill});
+                                   {AutofillManagerEvent::kAskForValuesToFill});
   AutofillManager::OnAskForValuesToFill(form, field, bounding_box,
                                         autoselect_first_suggestion,
                                         form_element_was_clicked);
@@ -95,7 +95,7 @@
     const FormFieldData& field,
     const std::u16string& old_value) {
   TestAutofillManagerWaiter waiter(
-      *this, {&Observer::OnAfterJavaScriptChangedAutofilledValue});
+      *this, {AutofillManagerEvent::kJavaScriptChangedAutofilledValue});
   AutofillManager::OnJavaScriptChangedAutofilledValue(form, field, old_value);
   ASSERT_TRUE(waiter.Wait());
 }
@@ -104,7 +104,7 @@
     const FormData& form,
     const bool known_success,
     const mojom::SubmissionSource source) {
-  TestAutofillManagerWaiter waiter(*this, {&Observer::OnAfterFormsSeen});
+  TestAutofillManagerWaiter waiter(*this, {AutofillManagerEvent::kFormsSeen});
   AutofillManager::OnFormSubmitted(form, known_success, source);
   ASSERT_TRUE(waiter.Wait());
 }
@@ -256,8 +256,8 @@
     const gfx::RectF& bounding_box,
     AutoselectFirstSuggestion autoselect_first_suggestion,
     FormElementWasClicked form_element_was_clicked) {
-  TestAutofillManagerWaiter waiter(
-      *this, {&AutofillManager::Observer::OnAfterAskForValuesToFill});
+  TestAutofillManagerWaiter waiter(*this,
+                                   {AutofillManagerEvent::kAskForValuesToFill});
   BrowserAutofillManager::OnAskForValuesToFill(form, field, bounding_box,
                                                autoselect_first_suggestion,
                                                form_element_was_clicked);
diff --git a/components/autofill/core/browser/webdata/autofill_wallet_usage_data_sync_bridge.cc b/components/autofill/core/browser/webdata/autofill_wallet_usage_data_sync_bridge.cc
index 42fa4b03..7d779f8 100644
--- a/components/autofill/core/browser/webdata/autofill_wallet_usage_data_sync_bridge.cc
+++ b/components/autofill/core/browser/webdata/autofill_wallet_usage_data_sync_bridge.cc
@@ -9,6 +9,7 @@
 #include "base/ranges/algorithm.h"
 #include "base/strings/string_util.h"
 #include "components/autofill/core/browser/data_model/autofill_wallet_usage_data.h"
+#include "components/autofill/core/browser/metrics/payments/wallet_usage_data_metrics.h"
 #include "components/autofill/core/browser/webdata/autofill_sync_bridge_util.h"
 #include "components/autofill/core/browser/webdata/autofill_table.h"
 #include "components/autofill/core/browser/webdata/autofill_webdata_backend.h"
@@ -103,6 +104,14 @@
         // TODO(crbug.com/1412207): AddOrUpdate VirtualCardUsageData method for
         // Autofill Table
         DCHECK(IsEntityDataValid(change->data()));
+        bool valid_data = IsVirtualCardUsageDataSpecificsValid(
+            change->data()
+                .specifics.autofill_wallet_usage()
+                .virtual_card_usage_data());
+        autofill_metrics::LogSyncedVirtualCardUsageDataBeingValid(valid_data);
+        if (!valid_data) {
+          continue;
+        }
         VirtualCardUsageData remote = VirtualCardUsageDataFromUsageSpecifics(
             change->data().specifics.autofill_wallet_usage());
         if (table && table->GetVirtualCardUsageData(change->storage_key())) {
@@ -117,7 +126,6 @@
                 FROM_HERE, "Failed to add virtual card usage data in table.");
           }
         }
-        break;
       }
     }
   }
diff --git a/components/autofill/core/browser/webdata/autofill_wallet_usage_data_sync_bridge_unittest.cc b/components/autofill/core/browser/webdata/autofill_wallet_usage_data_sync_bridge_unittest.cc
index dfdff125..d10070f3 100644
--- a/components/autofill/core/browser/webdata/autofill_wallet_usage_data_sync_bridge_unittest.cc
+++ b/components/autofill/core/browser/webdata/autofill_wallet_usage_data_sync_bridge_unittest.cc
@@ -13,6 +13,7 @@
 #include "base/strings/strcat.h"
 #include "base/strings/string_number_conversions.h"
 #include "base/test/bind.h"
+#include "base/test/metrics/histogram_tester.h"
 #include "base/test/task_environment.h"
 #include "components/autofill/core/browser/autofill_test_utils.h"
 #include "components/autofill/core/browser/geo/country_names.h"
@@ -306,4 +307,31 @@
   EXPECT_FALSE(GetVirtualCardUsageDataFromTable().empty());
 }
 
+// Test to ensure whether the data being valid is logged correctly.
+TEST_F(AutofillWalletUsageDataSyncBridgeTest, ApplySyncData_LogDataValidity) {
+  VirtualCardUsageData virtual_card_usage_data1 =
+      test::GetVirtualCardUsageData1();
+
+  // AutofillWalletUsageSpecifics with missing fields.
+  AutofillWalletUsageSpecifics specifics;
+  specifics.set_guid("guid");
+  specifics.mutable_virtual_card_usage_data()->set_instrument_id(1234);
+
+  syncer::EntityChangeList entity_change_list;
+  entity_change_list.push_back(syncer::EntityChange::CreateAdd(
+      *virtual_card_usage_data1.usage_data_id(),
+      VirtualCardUsageDataToEntity(virtual_card_usage_data1)));
+  entity_change_list.push_back(syncer::EntityChange::CreateAdd(
+      specifics.guid(), SpecificsToEntity(specifics)));
+
+  EXPECT_CALL(backend(), CommitChanges());
+  base::HistogramTester histogram_tester;
+  bridge()->ApplySyncChanges(bridge()->CreateMetadataChangeList(),
+                             std::move(entity_change_list));
+  histogram_tester.ExpectBucketCount(
+      "Autofill.VirtualCardUsageData.SyncedUsageDataBeingValid", true, 1);
+  histogram_tester.ExpectBucketCount(
+      "Autofill.VirtualCardUsageData.SyncedUsageDataBeingValid", false, 1);
+}
+
 }  // namespace autofill
diff --git a/components/commerce/core/mock_shopping_service.cc b/components/commerce/core/mock_shopping_service.cc
index 5828438..4c8c6c2b 100644
--- a/components/commerce/core/mock_shopping_service.cc
+++ b/components/commerce/core/mock_shopping_service.cc
@@ -31,6 +31,8 @@
   SetResponseForGetMerchantInfoForUrl(absl::nullopt);
   SetSubscribeCallbackValue(true);
   SetUnsubscribeCallbackValue(true);
+  SetIsSubscribedCallbackValue(true);
+  SetGetAllSubscriptionsCallbackValue(std::vector<CommerceSubscription>());
   SetIsShoppingListEligible(true);
   SetIsClusterIdTrackedByUserResponse(true);
   SetIsMerchantViewerEnabled(true);
@@ -108,6 +110,29 @@
           });
 }
 
+void MockShoppingService::SetIsSubscribedCallbackValue(bool is_subscribed) {
+  ON_CALL(*this, IsSubscribed)
+      .WillByDefault([is_subscribed](CommerceSubscription subscription,
+                                     base::OnceCallback<void(bool)> callback) {
+        base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
+            FROM_HERE, base::BindOnce(std::move(callback), is_subscribed));
+      });
+  ON_CALL(*this, IsSubscribedFromCache)
+      .WillByDefault(testing::Return(is_subscribed));
+}
+
+void MockShoppingService::SetGetAllSubscriptionsCallbackValue(
+    std::vector<CommerceSubscription> subscriptions) {
+  ON_CALL(*this, GetAllSubscriptions)
+      .WillByDefault([subs = std::move(subscriptions)](
+                         SubscriptionType type,
+                         base::OnceCallback<void(
+                             std::vector<CommerceSubscription>)> callback) {
+        base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
+            FROM_HERE, base::BindOnce(std::move(callback), std::move(subs)));
+      });
+}
+
 void MockShoppingService::SetIsShoppingListEligible(bool eligible) {
   ON_CALL(*this, IsShoppingListEligible)
       .WillByDefault(testing::Return(eligible));
diff --git a/components/commerce/core/mock_shopping_service.h b/components/commerce/core/mock_shopping_service.h
index 152724c..c2f50852 100644
--- a/components/commerce/core/mock_shopping_service.h
+++ b/components/commerce/core/mock_shopping_service.h
@@ -12,7 +12,6 @@
 #include "components/commerce/core/subscriptions/commerce_subscription.h"
 #include "services/network/public/cpp/shared_url_loader_factory.h"
 #include "testing/gmock/include/gmock/gmock.h"
-#include "testing/gtest/include/gtest/gtest.h"
 
 namespace commerce {
 
@@ -52,6 +51,21 @@
               (std::unique_ptr<std::vector<CommerceSubscription>> subscriptions,
                base::OnceCallback<void(bool)> callback),
               (override));
+  MOCK_METHOD(
+      void,
+      GetAllSubscriptions,
+      (SubscriptionType type,
+       base::OnceCallback<void(std::vector<CommerceSubscription>)> callback),
+      (override));
+  MOCK_METHOD(void,
+              IsSubscribed,
+              (CommerceSubscription subscription,
+               base::OnceCallback<void(bool)> callback),
+              (override));
+  MOCK_METHOD(bool,
+              IsSubscribedFromCache,
+              (const CommerceSubscription& subscription),
+              (override));
   MOCK_METHOD(void, ScheduleSavedProductUpdate, (), (override));
   MOCK_METHOD(bool, IsShoppingListEligible, (), (override));
   MOCK_METHOD(void,
@@ -68,6 +82,9 @@
       absl::optional<commerce::MerchantInfo> merchant_info);
   void SetSubscribeCallbackValue(bool subscribe_should_succeed);
   void SetUnsubscribeCallbackValue(bool unsubscribe_should_succeed);
+  void SetIsSubscribedCallbackValue(bool is_subscribed);
+  void SetGetAllSubscriptionsCallbackValue(
+      std::vector<CommerceSubscription> subscriptions);
   void SetIsShoppingListEligible(bool enabled);
   void SetIsClusterIdTrackedByUserResponse(bool is_tracked);
   void SetIsMerchantViewerEnabled(bool is_enabled);
diff --git a/components/commerce/core/shopping_service.cc b/components/commerce/core/shopping_service.cc
index 8d9c26db..fa1723ce 100644
--- a/components/commerce/core/shopping_service.cc
+++ b/components/commerce/core/shopping_service.cc
@@ -721,6 +721,16 @@
   }
 }
 
+void ShoppingService::GetAllSubscriptions(
+    SubscriptionType type,
+    base::OnceCallback<void(std::vector<CommerceSubscription>)> callback) {
+  if (subscriptions_manager_) {
+    subscriptions_manager_->GetAllSubscriptions(type, std::move(callback));
+  } else {
+    CHECK_IS_TEST();
+  }
+}
+
 void ShoppingService::IsSubscribed(CommerceSubscription subscription,
                                    base::OnceCallback<void(bool)> callback) {
   if (subscriptions_manager_) {
@@ -731,7 +741,7 @@
   }
 }
 
-bool ShoppingService::IsSubscriptedFromCache(
+bool ShoppingService::IsSubscribedFromCache(
     const CommerceSubscription& subscription) {
   if (subscriptions_manager_) {
     return subscriptions_manager_->IsSubscribedFromCache(subscription);
diff --git a/components/commerce/core/shopping_service.h b/components/commerce/core/shopping_service.h
index fc7eb75e..db0893e0 100644
--- a/components/commerce/core/shopping_service.h
+++ b/components/commerce/core/shopping_service.h
@@ -21,6 +21,7 @@
 #include "base/supports_user_data.h"
 #include "components/commerce/core/account_checker.h"
 #include "components/commerce/core/proto/commerce_subscription_db_content.pb.h"
+#include "components/commerce/core/subscriptions/commerce_subscription.h"
 #include "components/keyed_service/core/keyed_service.h"
 #include "components/optimization_guide/core/optimization_guide_decision.h"
 #include "services/data_decoder/public/cpp/data_decoder.h"
@@ -216,6 +217,12 @@
       std::unique_ptr<std::vector<CommerceSubscription>> subscriptions,
       base::OnceCallback<void(bool)> callback);
 
+  // Gets all subscriptions for the specified type. The list of subscriptions
+  // will be provided as input to the |callback| passed to this function.
+  virtual void GetAllSubscriptions(
+      SubscriptionType type,
+      base::OnceCallback<void(std::vector<CommerceSubscription>)> callback);
+
   // Methods to register or remove SubscriptionsObserver, which will be notified
   // when a (un)subscribe request has finished.
   void AddSubscriptionsObserver(SubscriptionsObserver* observer);
@@ -228,7 +235,7 @@
   // Checks if a subscription exists from the in-memory cache. Use of the the
   // callback-based version |IsSubscribed| is preferred. Information provided
   // by this API is not guaranteed to be correct.
-  virtual bool IsSubscriptedFromCache(const CommerceSubscription& subscription);
+  virtual bool IsSubscribedFromCache(const CommerceSubscription& subscription);
 
   // Fetch users' pref from server on whether to receive price tracking emails.
   void FetchPriceEmailPref();
diff --git a/components/history_clusters/core/history_clusters_service.cc b/components/history_clusters/core/history_clusters_service.cc
index 4ac56b1..b22ccaf 100644
--- a/components/history_clusters/core/history_clusters_service.cc
+++ b/components/history_clusters/core/history_clusters_service.cc
@@ -40,6 +40,15 @@
 
 namespace history_clusters {
 
+namespace {
+
+void RecordUpdateClustersLatencyHistogram(const std::string& histogram_name,
+                                          base::ElapsedTimer elapsed_timer) {
+  base::UmaHistogramMediumTimes(histogram_name, elapsed_timer.Elapsed());
+}
+
+}  // namespace
+
 VisitDeletionObserver::VisitDeletionObserver(
     HistoryClustersService* history_clusters_service)
     : history_clusters_service_(history_clusters_service) {}
@@ -262,13 +271,19 @@
     update_clusters_task_ =
         std::make_unique<HistoryClustersServiceTaskUpdateClusterTriggerability>(
             weak_ptr_factory_.GetWeakPtr(), backend_.get(), history_service_,
-            base::DoNothing());
+            base::BindOnce(
+                &RecordUpdateClustersLatencyHistogram,
+                "History.Clusters.Backend.UpdateClusterTriggerability.Total",
+                base::ElapsedTimer()));
   } else {
     update_clusters_task_ =
         std::make_unique<HistoryClustersServiceTaskUpdateClusters>(
             weak_ptr_factory_.GetWeakPtr(),
             incomplete_visit_context_annotations_, backend_.get(),
-            history_service_, base::DoNothing());
+            history_service_,
+            base::BindOnce(&RecordUpdateClustersLatencyHistogram,
+                           "History.Clusters.Backend.UpdateClusters.Total",
+                           base::ElapsedTimer()));
   }
 }
 
diff --git a/components/image_service/BUILD.gn b/components/image_service/BUILD.gn
index 5e67915b..645ae4d 100644
--- a/components/image_service/BUILD.gn
+++ b/components/image_service/BUILD.gn
@@ -5,10 +5,13 @@
 component("image_service") {
   defines = [ "IS_IMAGE_SERVICE_IMPL" ]
   sources = [
+    "features.cc",
+    "features.h",
     "image_service.cc",
     "image_service.h",
   ]
   deps = [
+    "mojom:mojo_bindings",
     "//base",
     "//components/google/core/common",
     "//components/keyed_service/core",
diff --git a/components/image_service/features.cc b/components/image_service/features.cc
new file mode 100644
index 0000000..045270eb
--- /dev/null
+++ b/components/image_service/features.cc
@@ -0,0 +1,22 @@
+// 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/image_service/features.h"
+
+namespace image_service {
+
+// Enabled by default because we are only using this as a killswitch.
+BASE_FEATURE(kImageService, "ImageService", base::FEATURE_ENABLED_BY_DEFAULT);
+
+// Disabled by default because the usage of this is still not approved.
+BASE_FEATURE(kImageServiceSuggestPoweredImages,
+             "ImageServiceSuggestPoweredImages",
+             base::FEATURE_DISABLED_BY_DEFAULT);
+
+// Disabled by default, usage is approved but we still want to control rollout.
+BASE_FEATURE(kImageServiceOptimizationGuideSalientImages,
+             "ImageServiceOptimizationGuideSalientImages",
+             base::FEATURE_DISABLED_BY_DEFAULT);
+
+}  // namespace image_service
diff --git a/components/image_service/features.h b/components/image_service/features.h
new file mode 100644
index 0000000..9959b360
--- /dev/null
+++ b/components/image_service/features.h
@@ -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.
+
+#ifndef COMPONENTS_IMAGE_SERVICE_FEATURES_H_
+#define COMPONENTS_IMAGE_SERVICE_FEATURES_H_
+
+#include "base/feature_list.h"
+
+namespace image_service {
+
+BASE_DECLARE_FEATURE(kImageService);
+BASE_DECLARE_FEATURE(kImageServiceSuggestPoweredImages);
+BASE_DECLARE_FEATURE(kImageServiceOptimizationGuideSalientImages);
+
+}  // namespace image_service
+
+#endif  // COMPONENTS_IMAGE_SERVICE_FEATURES_H_
diff --git a/components/image_service/image_service.cc b/components/image_service/image_service.cc
index 258cf06..1c7774d 100644
--- a/components/image_service/image_service.cc
+++ b/components/image_service/image_service.cc
@@ -4,11 +4,13 @@
 
 #include "components/image_service/image_service.h"
 
+#include "base/feature_list.h"
 #include "base/functional/bind.h"
 #include "base/functional/callback.h"
 #include "base/i18n/case_conversion.h"
 #include "base/memory/raw_ptr.h"
 #include "base/memory/weak_ptr.h"
+#include "components/image_service/features.h"
 #include "components/omnibox/browser/remote_suggestions_service.h"
 #include "components/omnibox/browser/search_suggestion_parser.h"
 #include "components/search_engines/search_engine_type.h"
@@ -23,6 +25,7 @@
 // A one-time use object that uses Suggest to get an image URL corresponding
 // to `search_query` and `entity_id`. This is a hacky temporary implementation,
 // ideally this should be replaced by persisted Suggest-provided entities.
+// TODO(tommycli): Move this to its own separate file with unit tests.
 class ImageService::SuggestEntityImageURLFetcher {
  public:
   SuggestEntityImageURLFetcher(
@@ -150,7 +153,51 @@
   return weak_factory_.GetWeakPtr();
 }
 
-bool ImageService::FetchImageFor(const std::u16string& search_query,
+void ImageService::FetchImageFor(mojom::ClientId client_id,
+                                 const GURL& page_url,
+                                 const mojom::Options& options,
+                                 ResultCallback callback) {
+  if (!base::FeatureList::IsEnabled(kImageService)) {
+    // In general this should never happen, because each UI should have its own
+    // feature gate, but this is just so we have a whole-service killswitch.
+    return std::move(callback).Run(GURL());
+  }
+
+  // TODO(b/244507194): This one only checks Sync consent, we probably need to
+  // delegate consent checking to the UI layer entirely, since Bookmarks needs
+  // to use a Bookmarks-specific Sync permission checker.
+  DCHECK(url_consent_helper_ && url_consent_helper_->IsEnabled());
+
+  if (options.suggest_images &&
+      base::FeatureList::IsEnabled(kImageServiceSuggestPoweredImages)) {
+    // TODO(b/244507194): Get our "own" TemplateURLService.
+    if (auto* template_url_service =
+            autocomplete_provider_client_->GetTemplateURLService()) {
+      auto search_metadata =
+          template_url_service->ExtractSearchMetadata(page_url);
+      // Fetch entity-keyed images for Google SRP visits only, because only
+      // Google SRP visits can expect to have a reasonable entity from Google
+      // Suggest.
+      if (search_metadata && search_metadata->template_url &&
+          search_metadata->template_url->GetEngineType(
+              template_url_service->search_terms_data()) ==
+              SEARCH_ENGINE_GOOGLE) {
+        return FetchImageFor(/*search_query=*/search_metadata->search_terms,
+                             /*entity_id=*/"", std::move(callback));
+      }
+    }
+  }
+
+  if (options.optimization_guide_images &&
+      base::FeatureList::IsEnabled(
+          kImageServiceOptimizationGuideSalientImages)) {
+    // TODO(b/248367751): Insert OptimizationGuide Salient Image call here.
+  }
+
+  std::move(callback).Run(GURL());
+}
+
+void ImageService::FetchImageFor(const std::u16string& search_query,
                                  const std::string& entity_id,
                                  ResultCallback callback) {
   DCHECK(url_consent_helper_ && url_consent_helper_->IsEnabled());
@@ -164,7 +211,6 @@
   fetcher_raw_ptr->Start(
       base::BindOnce(&ImageService::OnImageFetched, weak_factory_.GetWeakPtr(),
                      std::move(fetcher), std::move(callback)));
-  return true;
 }
 
 void ImageService::OnImageFetched(
diff --git a/components/image_service/image_service.h b/components/image_service/image_service.h
index 3cf561a..a7d826d 100644
--- a/components/image_service/image_service.h
+++ b/components/image_service/image_service.h
@@ -11,6 +11,7 @@
 #include "base/component_export.h"
 #include "base/functional/callback_forward.h"
 #include "base/memory/weak_ptr.h"
+#include "components/image_service/mojom/image_service.mojom.h"
 #include "components/keyed_service/core/keyed_service.h"
 #include "components/omnibox/browser/autocomplete_provider_client.h"
 #include "components/sync/driver/sync_service.h"
@@ -36,16 +37,23 @@
   // object whose lifetime might exceed the service.
   base::WeakPtr<ImageService> GetWeakPtr();
 
-  // Fetches an image appropriate for `search_query` and `entity_id`, returning
-  // the result asynchronously to `callback`. Returns false if we can't do it
-  // for configuration or privacy reasons.
-  bool FetchImageFor(const std::u16string& search_query,
-                     const std::string& entity_id,
+  // Fetches an image appropriate for `page_url`, returning the result
+  // asynchronously to `callback`. The callback is always invoked. If there are
+  // no images available, it is invoked with an empty GURL result.
+  void FetchImageFor(mojom::ClientId client_id,
+                     const GURL& page_url,
+                     const mojom::Options& options,
                      ResultCallback callback);
 
  private:
   class SuggestEntityImageURLFetcher;
 
+  // Fetches an image appropriate for `search_query` and `entity_id`, returning
+  // the result asynchronously to `callback`.
+  void FetchImageFor(const std::u16string& search_query,
+                     const std::string& entity_id,
+                     ResultCallback callback);
+
   // Callback for `FetchImageFor`.
   void OnImageFetched(std::unique_ptr<SuggestEntityImageURLFetcher> fetcher,
                       ResultCallback callback,
diff --git a/components/image_service/mojom/BUILD.gn b/components/image_service/mojom/BUILD.gn
new file mode 100644
index 0000000..e7ebe25d
--- /dev/null
+++ b/components/image_service/mojom/BUILD.gn
@@ -0,0 +1,16 @@
+# 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("//mojo/public/tools/bindings/mojom.gni")
+
+mojom("mojo_bindings") {
+  sources = [ "image_service.mojom" ]
+  public_deps = [
+    "//mojo/public/mojom/base",
+    "//url/mojom:url_mojom_gurl",
+  ]
+
+  webui_module_path = ""
+  use_typescript_sources = true
+}
diff --git a/components/image_service/mojom/OWNERS b/components/image_service/mojom/OWNERS
new file mode 100644
index 0000000..9c3b66c8
--- /dev/null
+++ b/components/image_service/mojom/OWNERS
@@ -0,0 +1,6 @@
+file://components/history_clusters/OWNERS
+
+per-file *.mojom=set noparent
+per-file *.mojom=file://ipc/SECURITY_OWNERS
+per-file *_mojom_traits*.*=set noparent
+per-file *_mojom_traits*.*=file://ipc/SECURITY_OWNERS
diff --git a/components/image_service/mojom/image_service.mojom b/components/image_service/mojom/image_service.mojom
new file mode 100644
index 0000000..e024ad8
--- /dev/null
+++ b/components/image_service/mojom/image_service.mojom
@@ -0,0 +1,24 @@
+// 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.
+
+module image_service.mojom;
+
+// The UI client that's calling the service. Used to track usage.
+enum ClientId {
+  // Approved as part of launch/4224996.
+  Journeys,
+  JourneysSidePanel,
+  NtpRealbox,
+  NtpQuests,
+
+  // Tracked at launch/4225832.
+  Bookmarks,
+};
+
+// Options for image fetching.
+struct Options {
+  // Turn these off if any UI client wants to disable a source.
+  bool suggest_images = true;
+  bool optimization_guide_images = true;
+};
diff --git a/components/ownership/owner_settings_service.cc b/components/ownership/owner_settings_service.cc
index c54e70a..5768a0c7 100644
--- a/components/ownership/owner_settings_service.cc
+++ b/components/ownership/owner_settings_service.cc
@@ -84,7 +84,7 @@
 
 BASE_FEATURE(kChromeSideOwnerKeyGeneration,
              "ChromeSideOwnerKeyGeneration",
-             base::FeatureState::FEATURE_ENABLED_BY_DEFAULT);
+             base::FeatureState::FEATURE_DISABLED_BY_DEFAULT);
 
 OwnerSettingsService::OwnerSettingsService(
     const scoped_refptr<ownership::OwnerKeyUtil>& owner_key_util)
diff --git a/components/password_manager/core/browser/password_manager_constants.cc b/components/password_manager/core/browser/password_manager_constants.cc
index 6bc9b65..e313f86f 100644
--- a/components/password_manager/core/browser/password_manager_constants.cc
+++ b/components/password_manager/core/browser/password_manager_constants.cc
@@ -26,7 +26,4 @@
 
 const char kReferrerURL[] = "https://passwords.google/";
 
-const char kTestingReferrerURL[] =
-    "https://xl-password-manager-staging.uc.r.appspot.com/";
-
 }  // namespace password_manager
diff --git a/components/password_manager/core/browser/password_manager_constants.h b/components/password_manager/core/browser/password_manager_constants.h
index 327af0f..8da603c8 100644
--- a/components/password_manager/core/browser/password_manager_constants.h
+++ b/components/password_manager/core/browser/password_manager_constants.h
@@ -29,11 +29,6 @@
 // URL from which native Password Manager UI can be opened.
 extern const char kReferrerURL[];
 
-// URL for a testing website from which native Password Manager UI can be
-// opened.
-// TODO(crbug.com/1329165): remove when the main website is launched.
-extern const char kTestingReferrerURL[];
-
 }  // namespace password_manager
 
 #endif  // COMPONENTS_PASSWORD_MANAGER_CORE_BROWSER_PASSWORD_MANAGER_CONSTANTS_H_
diff --git a/components/policy/android/java/src/org/chromium/components/policy/PolicyCache.java b/components/policy/android/java/src/org/chromium/components/policy/PolicyCache.java
index a191ecb0..b1e6419 100644
--- a/components/policy/android/java/src/org/chromium/components/policy/PolicyCache.java
+++ b/components/policy/android/java/src/org/chromium/components/policy/PolicyCache.java
@@ -238,6 +238,13 @@
         enableWriteOnlyMode();
     }
 
+    /** Delete all entries from the cache. */
+    public void reset() {
+        SharedPreferences.Editor sharedPreferencesEditor = getSharedPreferencesEditor();
+        sharedPreferencesEditor.clear();
+        sharedPreferencesEditor.apply();
+    }
+
     private void enableWriteOnlyMode() {
         mSharedPreferences = null;
         mReadable = false;
diff --git a/components/policy/android/javatests/src/org/chromium/components/policy/test/PolicyCacheUpdaterTestSupporter.java b/components/policy/android/javatests/src/org/chromium/components/policy/test/PolicyCacheUpdaterTestSupporter.java
index f348c84d..fcf7b08 100644
--- a/components/policy/android/javatests/src/org/chromium/components/policy/test/PolicyCacheUpdaterTestSupporter.java
+++ b/components/policy/android/javatests/src/org/chromium/components/policy/test/PolicyCacheUpdaterTestSupporter.java
@@ -23,16 +23,30 @@
     @CalledByNative
     private PolicyCacheUpdaterTestSupporter() {}
 
+    /** Checks value for {@code policy} is not cached. */
     @CalledByNative
-    private void verifyPolicyCacheIntValue(String policy, boolean hasValue, int expectedValue) {
+    private void verifyIntPolicyNotCached(String policy) {
+        PolicyCache policyCache = PolicyCache.get();
+        policyCache.setReadableForTesting(true);
+        Assert.assertNull(policyCache.getIntValue(policy));
+    }
+
+    /**
+     * Checks value for {@code policy} is cached and the corresponding value is {@code
+     * expectedValue}.
+     */
+    @CalledByNative
+    private void verifyIntPolicyHasValue(String policy, int expectedValue) {
         PolicyCache policyCache = PolicyCache.get();
         policyCache.setReadableForTesting(true);
         Integer actualValue = policyCache.getIntValue(policy);
-        if (hasValue) {
-            Assert.assertNotNull(actualValue);
-            Assert.assertEquals(expectedValue, actualValue.intValue());
-        } else {
-            Assert.assertNull(actualValue);
-        }
+        Assert.assertNotNull(actualValue);
+        Assert.assertEquals(expectedValue, actualValue.intValue());
+    }
+
+    /** Deletes all entries from the policy cache. */
+    @CalledByNative
+    private void resetPolicyCache() {
+        PolicyCache.get().reset();
     }
 }
diff --git a/components/policy/core/browser/android/policy_cache_updater_android_unittest.cc b/components/policy/core/browser/android/policy_cache_updater_android_unittest.cc
index 917bb910..94499d8 100644
--- a/components/policy/core/browser/android/policy_cache_updater_android_unittest.cc
+++ b/components/policy/core/browser/android/policy_cache_updater_android_unittest.cc
@@ -89,6 +89,10 @@
   }
   ~PolicyCacheUpdaterAndroidTest() override = default;
 
+  void TearDown() override {
+    Java_PolicyCacheUpdaterTestSupporter_resetPolicyCache(env_, j_support_);
+  }
+
   void SetPolicy(const std::string& policy, int policy_value) {
     policy_map_.Set(policy, PolicyLevel::POLICY_LEVEL_MANDATORY,
                     PolicyScope::POLICY_SCOPE_MACHINE,
@@ -99,12 +103,15 @@
 
   void UpdatePolicy() { policy_provider_.UpdateChromePolicy(policy_map_); }
 
-  void VerifyPolicyName(const std::string& policy,
-                        bool has_value,
-                        int expected_value) {
-    Java_PolicyCacheUpdaterTestSupporter_verifyPolicyCacheIntValue(
+  void VerifyIntPolicyNotCached(const std::string& policy) {
+    Java_PolicyCacheUpdaterTestSupporter_verifyIntPolicyNotCached(
+        env_, j_support_, base::android::ConvertUTF8ToJavaString(env_, policy));
+  }
+
+  void VerifyIntPolicyHasValue(const std::string& policy, int expected_value) {
+    Java_PolicyCacheUpdaterTestSupporter_verifyIntPolicyHasValue(
         env_, j_support_, base::android::ConvertUTF8ToJavaString(env_, policy),
-        has_value, expected_value);
+        expected_value);
   }
 
   ConfigurationPolicyHandlerList* policy_handler_list() {
@@ -132,7 +139,7 @@
   PolicyCacheUpdater updater(policy_service(), policy_handler_list());
   SetPolicy(kPolicyName, kPolicyValue);
   UpdatePolicy();
-  VerifyPolicyName(kPolicyName, /*has_value=*/true, kPolicyValue);
+  VerifyIntPolicyHasValue(kPolicyName, kPolicyValue);
 }
 
 TEST_F(PolicyCacheUpdaterAndroidTest, TestPolicyNotExist) {
@@ -141,7 +148,7 @@
 
   PolicyCacheUpdater updater(policy_service(), policy_handler_list());
   UpdatePolicy();
-  VerifyPolicyName(kPolicyName, /*has_value=*/false, kPolicyValue);
+  VerifyIntPolicyNotCached(kPolicyName);
 }
 
 TEST_F(PolicyCacheUpdaterAndroidTest, TestPolicyErrorPolicy) {
@@ -151,7 +158,7 @@
   PolicyCacheUpdater updater(policy_service(), policy_handler_list());
   SetPolicy(kPolicyName, kPolicyValue);
   UpdatePolicy();
-  VerifyPolicyName(kPolicyName, /*has_value=*/false, kPolicyValue);
+  VerifyIntPolicyNotCached(kPolicyName);
 }
 
 TEST_F(PolicyCacheUpdaterAndroidTest, TestPolicyMapIgnoredPolicy) {
@@ -162,7 +169,7 @@
   SetPolicy(kPolicyName, kPolicyValue);
   policy_map()->GetMutable(kPolicyName)->SetIgnored();
   UpdatePolicy();
-  VerifyPolicyName(kPolicyName, /*has_value=*/false, kPolicyValue);
+  VerifyIntPolicyNotCached(kPolicyName);
 }
 
 TEST_F(PolicyCacheUpdaterAndroidTest, TestPolicyMapErrorMessagePolicy) {
@@ -175,7 +182,7 @@
       ->GetMutable(kPolicyName)
       ->AddMessage(PolicyMap::MessageType::kError, IDS_POLICY_BLOCKED);
   UpdatePolicy();
-  VerifyPolicyName(kPolicyName, /*has_value=*/false, kPolicyValue);
+  VerifyIntPolicyNotCached(kPolicyName);
 }
 
 TEST_F(PolicyCacheUpdaterAndroidTest, TestPolicyMapWarningMessagePolicy) {
@@ -188,7 +195,7 @@
       ->GetMutable(kPolicyName)
       ->AddMessage(PolicyMap::MessageType::kWarning, IDS_POLICY_BLOCKED);
   UpdatePolicy();
-  VerifyPolicyName(kPolicyName, /*has_value=*/true, kPolicyValue);
+  VerifyIntPolicyHasValue(kPolicyName, kPolicyValue);
 }
 
 TEST_F(PolicyCacheUpdaterAndroidTest, TestPolicyUpdatedBeforeUpdaterCreated) {
@@ -197,9 +204,9 @@
 
   SetPolicy(kPolicyName, kPolicyValue);
   UpdatePolicy();
-  VerifyPolicyName(kPolicyName, /*has_value=*/false, kPolicyValue);
+  VerifyIntPolicyNotCached(kPolicyName);
   PolicyCacheUpdater updater(policy_service(), policy_handler_list());
-  VerifyPolicyName(kPolicyName, /*has_value=*/true, kPolicyValue);
+  VerifyIntPolicyHasValue(kPolicyName, kPolicyValue);
 }
 
 TEST_F(PolicyCacheUpdaterAndroidTest,
@@ -210,7 +217,7 @@
   PolicyCacheUpdater updater(policy_service(), policy_handler_list());
   SetPolicy(kPolicyName, kPolicyValue);
   UpdatePolicy();
-  VerifyPolicyName(kPolicyName, /*has_value=*/false, kPolicyValue);
+  VerifyIntPolicyNotCached(kPolicyName);
 }
 
 TEST_F(PolicyCacheUpdaterAndroidTest, TestWithWarningError_PolicyHasValue) {
@@ -220,7 +227,7 @@
   PolicyCacheUpdater updater(policy_service(), policy_handler_list());
   SetPolicy(kPolicyName, kPolicyValue);
   UpdatePolicy();
-  VerifyPolicyName(kPolicyName, /*has_value=*/true, kPolicyValue);
+  VerifyIntPolicyHasValue(kPolicyName, kPolicyValue);
 }
 
 TEST_F(PolicyCacheUpdaterAndroidTest, TestWithInfoError_PolicyHasValue) {
@@ -230,7 +237,7 @@
   PolicyCacheUpdater updater(policy_service(), policy_handler_list());
   SetPolicy(kPolicyName, kPolicyValue);
   UpdatePolicy();
-  VerifyPolicyName(kPolicyName, /*has_value=*/true, kPolicyValue);
+  VerifyIntPolicyHasValue(kPolicyName, kPolicyValue);
 }
 
 }  // namespace android
diff --git a/components/policy/resources/templates/policies.yaml b/components/policy/resources/templates/policies.yaml
index e09ef96..2f7d462 100644
--- a/components/policy/resources/templates/policies.yaml
+++ b/components/policy/resources/templates/policies.yaml
@@ -1077,6 +1077,7 @@
   1076: DeviceActivityHeartbeatCollectionRateMs
   1077: WallpaperGooglePhotosIntegrationEnabled
   1078: WebRtcTextLogCollectionAllowed
+  1079: EnforceLocalAnchorConstraintsEnabled
 atomic_groups:
   1: Homepage
   2: RemoteAccess
diff --git a/components/policy/resources/templates/policy_definitions/Miscellaneous/EnforceLocalAnchorConstraintsEnabled.yaml b/components/policy/resources/templates/policy_definitions/Miscellaneous/EnforceLocalAnchorConstraintsEnabled.yaml
new file mode 100644
index 0000000..5eb229e
--- /dev/null
+++ b/components/policy/resources/templates/policy_definitions/Miscellaneous/EnforceLocalAnchorConstraintsEnabled.yaml
@@ -0,0 +1,56 @@
+caption: Determines whether the built-in certificate verifier
+  will enforce constraints encoded into trust anchors loaded from the platform
+  trust store.
+default: true
+desc: |-
+  X.509 certificates may encode constraints, such as Name Constraints,
+  in extensions in the certificate. RFC 5280 specifies that enforcing such
+  constraints on trust anchor certificates is optional. Starting in
+  <ph name="PRODUCT_NAME">$1<ex>Google Chrome</ex></ph> 112, such constraints
+  in certificates loaded from the platform certificate store will now be
+  enforced.
+
+  This policy exists as a temporary opt-out in case an enterprise encounters
+  issues with the constraints encoded in their private roots. In that case this
+  policy may be used to temporarily disable enforcement of the constraints
+  while correcting the certificate issues.
+
+  When this policy is not set, or is set to enabled,
+  <ph name="PRODUCT_NAME">$1<ex>Google Chrome</ex></ph> will enforce
+  constraints encoded into trust anchors loaded from the platform trust store.
+
+  When this policy is set to disabled,
+  <ph name="PRODUCT_NAME">$1<ex>Google Chrome</ex></ph> will not enforce
+  constraints encoded into trust anchors loaded from the platform trust store.
+
+  This policy has no effect if the
+  <ph name="CHROME_ROOT_STORE_ENABLED_POLICY_NAME">ChromeRootStoreEnabled</ph>
+  policy is disabled.
+
+  This policy is planned to be removed in
+  <ph name="PRODUCT_NAME">$1<ex>Google Chrome</ex></ph> version 115.
+
+example_value: false
+features:
+  dynamic_refresh: true
+  per_profile: false
+items:
+- caption: Enforce constraints in locally added trust anchors
+  value: true
+- caption: Do not enforce constraints in locally added trust anchors
+  value: false
+owners:
+- mattm@chromium.org
+- file://net/cert/OWNERS
+schema:
+  type: boolean
+supported_on:
+- chrome.win:112-
+- chrome.mac:112-
+- chrome.linux:112-
+- chrome_os:112-
+future_on:
+- android
+tags: []
+type: main
+
diff --git a/components/reading_list/core/dual_reading_list_model.cc b/components/reading_list/core/dual_reading_list_model.cc
index 8ef06fba..cad6a35 100644
--- a/components/reading_list/core/dual_reading_list_model.cc
+++ b/components/reading_list/core/dual_reading_list_model.cc
@@ -9,14 +9,15 @@
 #include "base/notreached.h"
 #include "base/stl_util.h"
 #include "components/reading_list/core/reading_list_entry.h"
+#include "components/reading_list/core/reading_list_model_impl.h"
 #include "components/reading_list/features/reading_list_switches.h"
 #include "url/gurl.h"
 
 namespace reading_list {
 
 DualReadingListModel::DualReadingListModel(
-    std::unique_ptr<ReadingListModel> local_or_syncable_model,
-    std::unique_ptr<ReadingListModel> account_model)
+    std::unique_ptr<ReadingListModelImpl> local_or_syncable_model,
+    std::unique_ptr<ReadingListModelImpl> account_model)
     : local_or_syncable_model_(std::move(local_or_syncable_model)),
       account_model_(std::move(account_model)) {
   DCHECK(local_or_syncable_model_);
@@ -154,6 +155,17 @@
   return local_or_syncable_model_->IsUrlSupported(url);
 }
 
+bool DualReadingListModel::NeedsExplicitUploadToSyncServer(
+    const GURL& url) const {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  DCHECK(!local_or_syncable_model_->IsTrackingSyncMetadata() ||
+         !account_model_->IsTrackingSyncMetadata());
+
+  return account_model_->IsTrackingSyncMetadata() &&
+         local_or_syncable_model_->GetEntryByURL(url) != nullptr &&
+         account_model_->GetEntryByURL(url) == nullptr;
+}
+
 const ReadingListEntry& DualReadingListModel::AddOrReplaceEntry(
     const GURL& url,
     const std::string& title,
diff --git a/components/reading_list/core/dual_reading_list_model.h b/components/reading_list/core/dual_reading_list_model.h
index d174723..7bd970f 100644
--- a/components/reading_list/core/dual_reading_list_model.h
+++ b/components/reading_list/core/dual_reading_list_model.h
@@ -14,6 +14,7 @@
 #include "base/sequence_checker.h"
 #include "components/reading_list/core/reading_list_entry.h"
 #include "components/reading_list/core/reading_list_model.h"
+#include "components/reading_list/core/reading_list_model_impl.h"
 #include "components/reading_list/core/reading_list_model_observer.h"
 #include "url/gurl.h"
 
@@ -38,8 +39,8 @@
   };
 
   DualReadingListModel(
-      std::unique_ptr<ReadingListModel> local_or_syncable_model,
-      std::unique_ptr<ReadingListModel> account_model);
+      std::unique_ptr<ReadingListModelImpl> local_or_syncable_model,
+      std::unique_ptr<ReadingListModelImpl> account_model);
   ~DualReadingListModel() override;
 
   // KeyedService implementation.
@@ -62,6 +63,7 @@
   scoped_refptr<const ReadingListEntry> GetEntryByURL(
       const GURL& gurl) const override;
   bool IsUrlSupported(const GURL& url) override;
+  bool NeedsExplicitUploadToSyncServer(const GURL& url) const override;
   const ReadingListEntry& AddOrReplaceEntry(
       const GURL& url,
       const std::string& title,
@@ -114,8 +116,8 @@
   void NotifyObserversWithDidRemoveEntry(const GURL& url);
   void NotifyObserversWithDidApplyChanges();
 
-  const std::unique_ptr<ReadingListModel> local_or_syncable_model_;
-  const std::unique_ptr<ReadingListModel> account_model_;
+  const std::unique_ptr<ReadingListModelImpl> local_or_syncable_model_;
+  const std::unique_ptr<ReadingListModelImpl> account_model_;
 
   // Indicates whether a ReadingListModelImpl::RemoveEntryByURL is currently
   // performing on `local_or_syncable_model_` and `account_model_`.
diff --git a/components/reading_list/core/dual_reading_list_model_unittest.cc b/components/reading_list/core/dual_reading_list_model_unittest.cc
index c3f07d5..fa0244b 100644
--- a/components/reading_list/core/dual_reading_list_model_unittest.cc
+++ b/components/reading_list/core/dual_reading_list_model_unittest.cc
@@ -55,7 +55,7 @@
         std::make_unique<FakeReadingListModelStorage>();
     account_model_storage_ptr_ = account_model_storage->AsWeakPtr();
     auto account_model = std::make_unique<ReadingListModelImpl>(
-        std::move(account_model_storage), syncer::StorageType::kUnspecified,
+        std::move(account_model_storage), syncer::StorageType::kAccount,
         &clock_);
     account_model_ptr_ = account_model.get();
 
@@ -65,56 +65,60 @@
   }
 
   bool ResetStorageAndTriggerLoadCompletion(
-      std::vector<scoped_refptr<ReadingListEntry>> initial_local_entries,
-      std::vector<scoped_refptr<ReadingListEntry>> initial_account_entries) {
+      std::vector<scoped_refptr<ReadingListEntry>>
+          initial_local_or_syncable_entries = {},
+      std::vector<scoped_refptr<ReadingListEntry>> initial_account_entries =
+          {}) {
     ResetStorage();
     return local_or_syncable_model_storage_ptr_->TriggerLoadCompletion(
-               std::move(initial_local_entries)) &&
+               std::move(initial_local_or_syncable_entries)) &&
            account_model_storage_ptr_->TriggerLoadCompletion(
                std::move(initial_account_entries));
   }
 
-  bool ResetStorageAndTriggerLoadCompletion(
-      const std::vector<GURL>& initial_local_urls = {},
-      const std::vector<GURL>& initial_account_urls = {}) {
-    std::vector<scoped_refptr<ReadingListEntry>> initial_local_entries;
-    for (const auto& url : initial_local_urls) {
-      initial_local_entries.push_back(base::MakeRefCounted<ReadingListEntry>(
-          url, "Title for " + url.spec(), clock_.Now()));
-    }
-
-    std::vector<scoped_refptr<ReadingListEntry>> initial_account_entries;
-    for (const auto& url : initial_account_urls) {
-      initial_account_entries.push_back(base::MakeRefCounted<ReadingListEntry>(
-          url, "Title for " + url.spec(), clock_.Now()));
-    }
-
+  bool ResetStorageAndMimicSignedOut(
+      std::vector<scoped_refptr<ReadingListEntry>> initial_local_entries = {}) {
     return ResetStorageAndTriggerLoadCompletion(
-        std::move(initial_local_entries), std::move(initial_account_entries));
+        std::move(initial_local_entries), /*initial_account_entries=*/{});
   }
 
-  size_t UnreadSize() {
-    size_t size = 0;
-    for (const auto& url : dual_model_->GetKeys()) {
-      scoped_refptr<const ReadingListEntry> entry =
-          dual_model_->GetEntryByURL(url);
-      if (!entry->IsRead()) {
-        size++;
-      }
-    }
-    return size;
+  bool ResetStorageAndMimicSignedInSyncDisabled(
+      std::vector<scoped_refptr<ReadingListEntry>> initial_local_entries = {},
+      std::vector<scoped_refptr<ReadingListEntry>> initial_account_entries =
+          {}) {
+    ResetStorage();
+    auto metadata_batch = std::make_unique<syncer::MetadataBatch>();
+    sync_pb::ModelTypeState state;
+    state.set_initial_sync_done(true);
+    metadata_batch->SetModelTypeState(state);
+    return local_or_syncable_model_storage_ptr_->TriggerLoadCompletion(
+               std::move(initial_local_entries)) &&
+           account_model_storage_ptr_->TriggerLoadCompletion(
+               std::move(initial_account_entries), std::move(metadata_batch));
   }
 
-  size_t ReadSize() {
-    size_t size = 0;
-    for (const auto& url : dual_model_->GetKeys()) {
-      scoped_refptr<const ReadingListEntry> entry =
-          dual_model_->GetEntryByURL(url);
-      if (entry->IsRead()) {
-        size++;
-      }
+  bool ResetStorageAndMimicSyncEnabled(
+      std::vector<scoped_refptr<ReadingListEntry>> initial_syncable_entries =
+          {}) {
+    ResetStorage();
+    auto metadata_batch = std::make_unique<syncer::MetadataBatch>();
+    sync_pb::ModelTypeState state;
+    state.set_initial_sync_done(true);
+    metadata_batch->SetModelTypeState(state);
+    return local_or_syncable_model_storage_ptr_->TriggerLoadCompletion(
+               std::move(initial_syncable_entries),
+               std::move(metadata_batch)) &&
+           account_model_storage_ptr_->TriggerLoadCompletion();
+  }
+
+  std::vector<scoped_refptr<ReadingListEntry>> MakeTestEntriesForURLs(
+      const std::vector<GURL>& urls) {
+    std::vector<scoped_refptr<ReadingListEntry>> entries;
+    for (const auto& url : urls) {
+      entries.push_back(base::MakeRefCounted<ReadingListEntry>(
+          url, "Title for " + url.spec(), clock_.Now()));
     }
-    return size;
+    return entries;
   }
 
  protected:
@@ -140,8 +144,6 @@
   EXPECT_CALL(observer_, ReadingListModelLoaded(dual_model_.get()));
   ASSERT_TRUE(account_model_storage_ptr_->TriggerLoadCompletion());
   EXPECT_TRUE(dual_model_->loaded());
-  EXPECT_EQ(0ul, UnreadSize());
-  EXPECT_EQ(0ul, ReadSize());
 }
 
 // Tests errors during load model.
@@ -156,7 +158,8 @@
 
 TEST_F(DualReadingListModelTest, ReturnAccountModelSize) {
   ASSERT_TRUE(ResetStorageAndTriggerLoadCompletion(
-      /*initial_local_urls=*/{}, {GURL("https://url.com")}));
+      /*initial_local_or_syncable_entries=*/{},
+      MakeTestEntriesForURLs({GURL("https://url.com")})));
   ASSERT_EQ(0ul, local_or_syncable_model_ptr_->size());
   ASSERT_EQ(1ul, account_model_ptr_->size());
   EXPECT_EQ(1ul, dual_model_->size());
@@ -164,15 +167,17 @@
 
 TEST_F(DualReadingListModelTest, ReturnLocalModelSize) {
   ASSERT_TRUE(ResetStorageAndTriggerLoadCompletion(
-      {GURL("https://url.com")}, /*initial_account_urls=*/{}));
+      MakeTestEntriesForURLs({GURL("https://url.com")}),
+      /*initial_account_entries=*/{}));
   ASSERT_EQ(1ul, local_or_syncable_model_ptr_->size());
   ASSERT_EQ(0ul, account_model_ptr_->size());
   EXPECT_EQ(1ul, dual_model_->size());
 }
 
 TEST_F(DualReadingListModelTest, ReturnKeysSize) {
-  ASSERT_TRUE(ResetStorageAndTriggerLoadCompletion({GURL("https://url1.com")},
-                                                   {GURL("https://url2.com")}));
+  ASSERT_TRUE(ResetStorageAndTriggerLoadCompletion(
+      MakeTestEntriesForURLs({GURL("https://url1.com")}),
+      MakeTestEntriesForURLs({GURL("https://url2.com")})));
   ASSERT_EQ(1ul, local_or_syncable_model_ptr_->size());
   ASSERT_EQ(1ul, account_model_ptr_->size());
   EXPECT_EQ(2ul, dual_model_->size());
@@ -246,27 +251,75 @@
             ReadingListEntry::DISTILLATION_ERROR);
 }
 
-TEST_F(DualReadingListModelTest, RemoveNonExistingEntryByUrl) {
-  ResetStorageAndTriggerLoadCompletion();
-  const GURL kUrl = GURL("http://url.com/");
+TEST_F(DualReadingListModelTest, NeedsExplicitUploadToSyncServerWhenSignedOut) {
+  const GURL kLocalUrl("http://local_url.com/");
+  ASSERT_TRUE(
+      ResetStorageAndMimicSignedOut(MakeTestEntriesForURLs({kLocalUrl})));
+  ASSERT_EQ(dual_model_->GetStorageStateForURLForTesting(kLocalUrl),
+            StorageStateForTesting::kExistsInLocalOrSyncableModelOnly);
 
-  ASSERT_EQ(dual_model_->GetStorageStateForURLForTesting(kUrl),
+  EXPECT_FALSE(dual_model_->NeedsExplicitUploadToSyncServer(kLocalUrl));
+  EXPECT_FALSE(dual_model_->NeedsExplicitUploadToSyncServer(
+      GURL("http://non_existing_url.com/")));
+}
+
+TEST_F(DualReadingListModelTest,
+       NeedsExplicitUploadToSyncServerWhenSignedInSyncDisabled) {
+  const GURL kLocalUrl("http://local_url.com/");
+  const GURL kAccountUrl("http://account_url.com/");
+  const GURL kCommonUrl("http://common_url.com/");
+  ASSERT_TRUE(ResetStorageAndMimicSignedInSyncDisabled(
+      MakeTestEntriesForURLs({kLocalUrl, kCommonUrl}),
+      MakeTestEntriesForURLs({kAccountUrl, kCommonUrl})));
+  ASSERT_EQ(dual_model_->GetStorageStateForURLForTesting(kLocalUrl),
+            StorageStateForTesting::kExistsInLocalOrSyncableModelOnly);
+  ASSERT_EQ(dual_model_->GetStorageStateForURLForTesting(kAccountUrl),
+            StorageStateForTesting::kExistsInAccountModelOnly);
+  ASSERT_EQ(dual_model_->GetStorageStateForURLForTesting(kCommonUrl),
+            StorageStateForTesting::kExistsInBothModels);
+
+  EXPECT_TRUE(dual_model_->NeedsExplicitUploadToSyncServer(kLocalUrl));
+  EXPECT_FALSE(dual_model_->NeedsExplicitUploadToSyncServer(kAccountUrl));
+  EXPECT_FALSE(dual_model_->NeedsExplicitUploadToSyncServer(kCommonUrl));
+  EXPECT_FALSE(dual_model_->NeedsExplicitUploadToSyncServer(
+      GURL("http://non_existing_url.com/")));
+}
+
+TEST_F(DualReadingListModelTest,
+       NeedsExplicitUploadToSyncServerWhenSyncEnabled) {
+  const GURL kSyncableUrl("http://syncable_url.com/");
+  ASSERT_TRUE(
+      ResetStorageAndMimicSyncEnabled(MakeTestEntriesForURLs({kSyncableUrl})));
+  ASSERT_EQ(dual_model_->GetStorageStateForURLForTesting(kSyncableUrl),
+            StorageStateForTesting::kExistsInLocalOrSyncableModelOnly);
+
+  EXPECT_FALSE(dual_model_->NeedsExplicitUploadToSyncServer(kSyncableUrl));
+  EXPECT_FALSE(dual_model_->NeedsExplicitUploadToSyncServer(
+      GURL("http://non_existing_url.com/")));
+}
+
+TEST_F(DualReadingListModelTest, RemoveNonExistingEntryByUrl) {
+  ASSERT_TRUE(ResetStorageAndTriggerLoadCompletion());
+  const GURL kNonExistingURL("http://non_existing_url.com/");
+
+  ASSERT_EQ(dual_model_->GetStorageStateForURLForTesting(kNonExistingURL),
             StorageStateForTesting::kNotFound);
-  ASSERT_THAT(dual_model_->GetEntryByURL(kUrl), IsNull());
+  ASSERT_THAT(dual_model_->GetEntryByURL(kNonExistingURL), IsNull());
 
   EXPECT_CALL(observer_, ReadingListWillRemoveEntry).Times(0);
   EXPECT_CALL(observer_, ReadingListDidRemoveEntry).Times(0);
   EXPECT_CALL(observer_, ReadingListDidApplyChanges).Times(0);
 
-  dual_model_->RemoveEntryByURL(kUrl);
+  dual_model_->RemoveEntryByURL(kNonExistingURL);
 
-  EXPECT_THAT(dual_model_->GetEntryByURL(kUrl), IsNull());
+  EXPECT_THAT(dual_model_->GetEntryByURL(kNonExistingURL), IsNull());
 }
 
 TEST_F(DualReadingListModelTest, RemoveLocalEntryByUrl) {
-  const GURL kLocalUrl = GURL("http://local_url.com/");
-  ResetStorageAndTriggerLoadCompletion({kLocalUrl},
-                                       /*initial_account_urls=*/{});
+  const GURL kLocalUrl("http://local_url.com/");
+  ASSERT_TRUE(
+      ResetStorageAndTriggerLoadCompletion(MakeTestEntriesForURLs({kLocalUrl}),
+                                           /*initial_account_entries=*/{}));
 
   ASSERT_EQ(dual_model_->GetStorageStateForURLForTesting(kLocalUrl),
             StorageStateForTesting::kExistsInLocalOrSyncableModelOnly);
@@ -285,9 +338,10 @@
 }
 
 TEST_F(DualReadingListModelTest, RemoveAccountEntryByUrl) {
-  const GURL kAccountUrl = GURL("http://account_url.com/");
-  ResetStorageAndTriggerLoadCompletion(/*initial_local_urls=*/{},
-                                       {kAccountUrl});
+  const GURL kAccountUrl("http://account_url.com/");
+  ASSERT_TRUE(ResetStorageAndTriggerLoadCompletion(
+      /*initial_local_or_syncable_entries=*/{},
+      MakeTestEntriesForURLs({kAccountUrl})));
 
   ASSERT_EQ(dual_model_->GetStorageStateForURLForTesting(kAccountUrl),
             StorageStateForTesting::kExistsInAccountModelOnly);
@@ -306,8 +360,10 @@
 }
 
 TEST_F(DualReadingListModelTest, RemoveCommonEntryByUrl) {
-  const GURL kCommonUrl = GURL("http://Common_url.com/");
-  ResetStorageAndTriggerLoadCompletion({kCommonUrl}, {kCommonUrl});
+  const GURL kCommonUrl("http://common_url.com/");
+  ASSERT_TRUE(ResetStorageAndTriggerLoadCompletion(
+      MakeTestEntriesForURLs({kCommonUrl}),
+      MakeTestEntriesForURLs({kCommonUrl})));
 
   ASSERT_EQ(dual_model_->GetStorageStateForURLForTesting(kCommonUrl),
             StorageStateForTesting::kExistsInBothModels);
@@ -326,9 +382,10 @@
 }
 
 TEST_F(DualReadingListModelTest, RemoveLocalEntryByUrlFromSync) {
-  const GURL kLocalUrl = GURL("http://local_url.com/");
-  ResetStorageAndTriggerLoadCompletion({kLocalUrl},
-                                       /*initial_account_urls=*/{});
+  const GURL kLocalUrl("http://local_url.com/");
+  ASSERT_TRUE(
+      ResetStorageAndTriggerLoadCompletion(MakeTestEntriesForURLs({kLocalUrl}),
+                                           /*initial_account_entries=*/{}));
   // DCHECKs verify that sync updates are issued as batch updates.
   auto token = local_or_syncable_model_ptr_->BeginBatchUpdates();
 
@@ -349,9 +406,10 @@
 }
 
 TEST_F(DualReadingListModelTest, RemoveAccountEntryByUrlFromSync) {
-  const GURL kAccountUrl = GURL("http://account_url.com/");
-  ResetStorageAndTriggerLoadCompletion(/*initial_local_urls=*/{},
-                                       {kAccountUrl});
+  const GURL kAccountUrl("http://account_url.com/");
+  ASSERT_TRUE(ResetStorageAndTriggerLoadCompletion(
+      /*initial_local_or_syncable_entries=*/{},
+      MakeTestEntriesForURLs({kAccountUrl})));
   // DCHECKs verify that sync updates are issued as batch updates.
   auto token = account_model_ptr_->BeginBatchUpdates();
 
@@ -372,8 +430,10 @@
 }
 
 TEST_F(DualReadingListModelTest, RemoveCommonEntryByUrlFromSync) {
-  const GURL kCommonUrl = GURL("http://Common_url.com/");
-  ResetStorageAndTriggerLoadCompletion({kCommonUrl}, {kCommonUrl});
+  const GURL kCommonUrl("http://common_url.com/");
+  ASSERT_TRUE(ResetStorageAndTriggerLoadCompletion(
+      MakeTestEntriesForURLs({kCommonUrl}),
+      MakeTestEntriesForURLs({kCommonUrl})));
   // DCHECKs verify that sync updates are issued as batch updates.
   auto token = account_model_ptr_->BeginBatchUpdates();
 
diff --git a/components/reading_list/core/fake_reading_list_model_storage.cc b/components/reading_list/core/fake_reading_list_model_storage.cc
index a1ffa41..c4a7135b 100644
--- a/components/reading_list/core/fake_reading_list_model_storage.cc
+++ b/components/reading_list/core/fake_reading_list_model_storage.cc
@@ -4,6 +4,8 @@
 
 #include "components/reading_list/core/fake_reading_list_model_storage.h"
 
+#include <memory>
+
 #include "base/memory/scoped_refptr.h"
 #include "components/sync/model/metadata_batch.h"
 
@@ -52,9 +54,10 @@
 }
 
 bool FakeReadingListModelStorage::TriggerLoadCompletion(
-    std::vector<scoped_refptr<ReadingListEntry>> entries) {
+    std::vector<scoped_refptr<ReadingListEntry>> entries,
+    std::unique_ptr<syncer::MetadataBatch> metadata_batch) {
   LoadResult result;
-  result.second = std::make_unique<syncer::MetadataBatch>();
+  result.second = std::move(metadata_batch);
   for (auto& entry : entries) {
     GURL url = entry->URL();
     result.first.emplace(entry->URL(), std::move(entry));
diff --git a/components/reading_list/core/fake_reading_list_model_storage.h b/components/reading_list/core/fake_reading_list_model_storage.h
index 848c2b4b..993fd8cf 100644
--- a/components/reading_list/core/fake_reading_list_model_storage.h
+++ b/components/reading_list/core/fake_reading_list_model_storage.h
@@ -5,6 +5,7 @@
 #ifndef COMPONENTS_READING_LIST_CORE_FAKE_READING_LIST_MODEL_STORAGE_H_
 #define COMPONENTS_READING_LIST_CORE_FAKE_READING_LIST_MODEL_STORAGE_H_
 
+#include <memory>
 #include <vector>
 
 #include "base/memory/raw_ptr.h"
@@ -13,6 +14,7 @@
 #include "components/reading_list/core/reading_list_entry.h"
 #include "components/reading_list/core/reading_list_model_storage.h"
 #include "components/sync/model/dummy_metadata_change_list.h"
+#include "components/sync/model/metadata_batch.h"
 
 // Test-only implementation of ReadingListModelStorage that doesn't do any
 // actual I/O but allows populating the initial list of entries. It also
@@ -59,7 +61,9 @@
   // Convenience overload that uses sensible defaults (empty store) for success
   // case.
   bool TriggerLoadCompletion(
-      std::vector<scoped_refptr<ReadingListEntry>> entries = {});
+      std::vector<scoped_refptr<ReadingListEntry>> entries = {},
+      std::unique_ptr<syncer::MetadataBatch> metadata_batch =
+          std::make_unique<syncer::MetadataBatch>());
 
   // ReadingListModelStorage implementation.
   void Load(base::Clock* clock, LoadCallback load_cb) override;
diff --git a/components/reading_list/core/reading_list_model.h b/components/reading_list/core/reading_list_model.h
index e11e10b..4b0f88e 100644
--- a/components/reading_list/core/reading_list_model.h
+++ b/components/reading_list/core/reading_list_model.h
@@ -89,6 +89,10 @@
   // Returns true if |url| can be added to the reading list.
   virtual bool IsUrlSupported(const GURL& url) = 0;
 
+  // Returns true if the entry with `url` requires explicit user action to
+  // upload to sync servers.
+  virtual bool NeedsExplicitUploadToSyncServer(const GURL& url) const = 0;
+
   // Adds |url| at the top of the unread entries, and removes entries with the
   // same |url| from everywhere else if they exist. The entry title will be a
   // trimmed copy of |title|. |time_to_read_minutes| is the estimated time to
diff --git a/components/reading_list/core/reading_list_model_impl.cc b/components/reading_list/core/reading_list_model_impl.cc
index 008f64e1..a8200039 100644
--- a/components/reading_list/core/reading_list_model_impl.cc
+++ b/components/reading_list/core/reading_list_model_impl.cc
@@ -11,6 +11,7 @@
 #include "base/memory/ptr_util.h"
 #include "base/memory/scoped_refptr.h"
 #include "base/metrics/histogram_functions.h"
+#include "base/notreached.h"
 #include "base/observer_list.h"
 #include "base/strings/string_util.h"
 #include "base/time/clock.h"
@@ -324,6 +325,15 @@
   return url.SchemeIsHTTPOrHTTPS();
 }
 
+bool ReadingListModelImpl::NeedsExplicitUploadToSyncServer(
+    const GURL& url) const {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+  // Returning true only makes sense for an implementation that maintains a
+  // separate set of local and account entries (DualReadingListModel).
+  return false;
+}
+
 const ReadingListEntry& ReadingListModelImpl::AddOrReplaceEntry(
     const GURL& url,
     const std::string& title,
@@ -544,6 +554,11 @@
   return token;
 }
 
+bool ReadingListModelImpl::IsTrackingSyncMetadata() const {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  return sync_bridge_.change_processor()->IsTrackingMetadata();
+}
+
 // static
 std::unique_ptr<ReadingListModelImpl> ReadingListModelImpl::BuildNewForTest(
     std::unique_ptr<ReadingListModelStorage> storage_layer,
diff --git a/components/reading_list/core/reading_list_model_impl.h b/components/reading_list/core/reading_list_model_impl.h
index fa47219..1fcb8bc 100644
--- a/components/reading_list/core/reading_list_model_impl.h
+++ b/components/reading_list/core/reading_list_model_impl.h
@@ -60,6 +60,7 @@
   scoped_refptr<const ReadingListEntry> GetEntryByURL(
       const GURL& gurl) const override;
   bool IsUrlSupported(const GURL& url) override;
+  bool NeedsExplicitUploadToSyncServer(const GURL& url) const override;
   const ReadingListEntry& AddOrReplaceEntry(
       const GURL& url,
       const std::string& title,
@@ -112,6 +113,10 @@
   std::unique_ptr<ScopedReadingListBatchUpdateImpl>
   BeginBatchUpdatesWithSyncMetadata();
 
+  // Returns true if the model is sync-ing with the server and the initial
+  // download of data and corresponding merge has completed.
+  bool IsTrackingSyncMetadata() const;
+
   // Test-only factory function to inject an arbitrary change processor.
   static std::unique_ptr<ReadingListModelImpl> BuildNewForTest(
       std::unique_ptr<ReadingListModelStorage> storage_layer,
diff --git a/components/search_engines/template_url_service.cc b/components/search_engines/template_url_service.cc
index c8142d9..2bd1ce0 100644
--- a/components/search_engines/template_url_service.cc
+++ b/components/search_engines/template_url_service.cc
@@ -846,7 +846,7 @@
     return absl::nullopt;
   }
 
-  return SearchMetadata{normalized_url, normalized_search_terms};
+  return SearchMetadata{template_url, normalized_url, normalized_search_terms};
 }
 
 bool TemplateURLService::IsSideSearchSupportedForDefaultSearchProvider() const {
diff --git a/components/search_engines/template_url_service.h b/components/search_engines/template_url_service.h
index dd735e5..e61f640 100644
--- a/components/search_engines/template_url_service.h
+++ b/components/search_engines/template_url_service.h
@@ -105,6 +105,7 @@
 
   // Search metadata that's often used to persist into History.
   struct SearchMetadata {
+    const TemplateURL* template_url;
     GURL normalized_url;
     std::u16string search_terms;
   };
diff --git a/components/signin/internal/identity_manager/DEPS b/components/signin/internal/identity_manager/DEPS
index b2b6d8c..4374378 100644
--- a/components/signin/internal/identity_manager/DEPS
+++ b/components/signin/internal/identity_manager/DEPS
@@ -5,6 +5,5 @@
   "+components/signin/public/webdata",
   "+components/signin/public/android/jni_headers",
   "+components/signin/public/android/test_support_jni_headers",
-  "+google_apis",
   "+mojo/public",
 ]
diff --git a/components/signin/internal/identity_manager/gaia_cookie_manager_service.cc b/components/signin/internal/identity_manager/gaia_cookie_manager_service.cc
index a4ad94e1..fffb8451 100644
--- a/components/signin/internal/identity_manager/gaia_cookie_manager_service.cc
+++ b/components/signin/internal/identity_manager/gaia_cookie_manager_service.cc
@@ -31,7 +31,6 @@
 #include "components/signin/public/base/signin_metrics.h"
 #include "components/signin/public/base/signin_pref_names.h"
 #include "components/signin/public/identity_manager/set_accounts_in_cookie_result.h"
-#include "google_apis/credentials_mode.h"
 #include "google_apis/gaia/gaia_constants.h"
 #include "google_apis/gaia/gaia_urls.h"
 #include "net/base/load_flags.h"
@@ -348,8 +347,7 @@
 
   auto request = std::make_unique<network::ResourceRequest>();
   request->url = url;
-  request->credentials_mode =
-      google_apis::GetOmitCredentialsModeForGaiaRequests();
+  request->credentials_mode = network::mojom::CredentialsMode::kOmit;
 
   std::unique_ptr<network::SimpleURLLoader> loader =
       network::SimpleURLLoader::Create(std::move(request), traffic_annotation);
diff --git a/components/sync/driver/sync_stopped_reporter.cc b/components/sync/driver/sync_stopped_reporter.cc
index cebc3f35..e388680 100644
--- a/components/sync/driver/sync_stopped_reporter.cc
+++ b/components/sync/driver/sync_stopped_reporter.cc
@@ -12,7 +12,6 @@
 #include "base/metrics/histogram_functions.h"
 #include "base/strings/stringprintf.h"
 #include "components/sync/protocol/sync.pb.h"
-#include "google_apis/credentials_mode.h"
 #include "net/base/load_flags.h"
 #include "net/http/http_status_code.h"
 #include "net/traffic_annotation/network_traffic_annotation.h"
@@ -111,8 +110,7 @@
   resource_request->url = sync_event_url_;
   resource_request->load_flags =
       net::LOAD_BYPASS_CACHE | net::LOAD_DISABLE_CACHE;
-  resource_request->credentials_mode =
-      google_apis::GetOmitCredentialsModeForGaiaRequests();
+  resource_request->credentials_mode = network::mojom::CredentialsMode::kOmit;
   resource_request->method = "POST";
   resource_request->headers.SetHeader(
       net::HttpRequestHeaders::kAuthorization,
diff --git a/components/sync/engine/net/http_bridge.cc b/components/sync/engine/net/http_bridge.cc
index 387aa93..8e5c296a 100644
--- a/components/sync/engine/net/http_bridge.cc
+++ b/components/sync/engine/net/http_bridge.cc
@@ -17,7 +17,6 @@
 #include "base/task/thread_pool.h"
 #include "base/threading/thread_restrictions.h"
 #include "components/variations/net/variations_http_headers.h"
-#include "google_apis/credentials_mode.h"
 #include "net/base/load_flags.h"
 #include "net/base/net_errors.h"
 #include "net/http/http_request_headers.h"
@@ -243,8 +242,7 @@
   resource_request->method = "POST";
   resource_request->load_flags =
       net::LOAD_BYPASS_CACHE | net::LOAD_DISABLE_CACHE;
-  resource_request->credentials_mode =
-      google_apis::GetOmitCredentialsModeForGaiaRequests();
+  resource_request->credentials_mode = network::mojom::CredentialsMode::kOmit;
 
   if (!extra_headers_.empty())
     resource_request->headers.AddHeadersFromString(extra_headers_);
diff --git a/components/sync/trusted_vault/trusted_vault_request.cc b/components/sync/trusted_vault/trusted_vault_request.cc
index 8ea66bb..6deec60a 100644
--- a/components/sync/trusted_vault/trusted_vault_request.cc
+++ b/components/sync/trusted_vault/trusted_vault_request.cc
@@ -12,7 +12,6 @@
 #include "components/sync/driver/trusted_vault_histograms.h"
 #include "components/sync/trusted_vault/trusted_vault_access_token_fetcher.h"
 #include "components/sync/trusted_vault/trusted_vault_server_constants.h"
-#include "google_apis/credentials_mode.h"
 #include "net/base/url_util.h"
 #include "net/http/http_request_headers.h"
 #include "net/http/http_status_code.h"
@@ -179,8 +178,7 @@
       net::AppendQueryParameter(request_url_, kQueryParameterAlternateOutputKey,
                                 kQueryParameterAlternateOutputProto);
   request->method = GetHttpMethodString(http_method_);
-  request->credentials_mode =
-      google_apis::GetOmitCredentialsModeForGaiaRequests();
+  request->credentials_mode = network::mojom::CredentialsMode::kOmit;
   request->headers.SetHeader(
       kAuthorizationHeader,
       /*value=*/base::StringPrintf("Bearer %s", access_token.c_str()));
diff --git a/components/translate/content/android/translate_message.cc b/components/translate/content/android/translate_message.cc
index bdb47105..92813964 100644
--- a/components/translate/content/android/translate_message.cc
+++ b/components/translate/content/android/translate_message.cc
@@ -182,10 +182,8 @@
     RecordCompactInfobarEvent(InfobarEvent::INFOBAR_IMPRESSION);
   }
 
-  if (ui_delegate_->GetSourceLanguageCode() != source_language)
-    ui_delegate_->UpdateSourceLanguage(source_language);
-  if (ui_delegate_->GetTargetLanguageCode() != target_language)
-    ui_delegate_->UpdateTargetLanguage(target_language);
+  ui_delegate_->UpdateSourceLanguage(source_language);
+  ui_delegate_->UpdateTargetLanguage(target_language);
 
   if (step == TRANSLATE_STEP_TRANSLATE_ERROR) {
     // Prevent auto-always-translate from triggering if an error occurs.
diff --git a/components/translate/core/browser/translate_ui_delegate.cc b/components/translate/core/browser/translate_ui_delegate.cc
index 64e01f7..3746bac 100644
--- a/components/translate/core/browser/translate_ui_delegate.cc
+++ b/components/translate/core/browser/translate_ui_delegate.cc
@@ -228,6 +228,9 @@
 
 void TranslateUIDelegate::UpdateSourceLanguage(
     const std::string& language_code) {
+  if (GetSourceLanguageCode() == language_code) {
+    return;
+  }
   for (size_t i = 0; i < languages_.size(); ++i) {
     if (languages_[i].first.compare(language_code) == 0) {
       UpdateSourceLanguageIndex(i);
@@ -257,6 +260,9 @@
 
 void TranslateUIDelegate::UpdateTargetLanguage(
     const std::string& language_code) {
+  if (GetTargetLanguageCode() == language_code) {
+    return;
+  }
   for (size_t i = 0; i < languages_.size(); ++i) {
     if (languages_[i].first.compare(language_code) == 0) {
       UpdateTargetLanguageIndex(i);
diff --git a/components/translate/core/browser/translate_ui_delegate.h b/components/translate/core/browser/translate_ui_delegate.h
index 793339b..ff42bca 100644
--- a/components/translate/core/browser/translate_ui_delegate.h
+++ b/components/translate/core/browser/translate_ui_delegate.h
@@ -69,9 +69,12 @@
   // Returns the source language code.
   std::string GetSourceLanguageCode() const;
 
-  // Updates the source language index.
+  // Updates the source language index if |language_index| is different from the
+  // current index.
   void UpdateSourceLanguageIndex(size_t language_index);
 
+  // Updates the source language and saves the change for logging if the provided 
+  // |language_code| is valid.
   void UpdateSourceLanguage(const std::string& language_code);
 
   // Returns the target language index.
@@ -80,9 +83,12 @@
   // Returns the target language code.
   std::string GetTargetLanguageCode() const;
 
-  // Updates the target language index.
+  // Updates the target language index if |language_index| is different from the
+  // current index.
   void UpdateTargetLanguageIndex(size_t language_index);
 
+  // Updates the target language and saves the change for logging if the provided 
+  // |language_code| is valid.
   void UpdateTargetLanguage(const std::string& language_code);
 
   // Returns the ISO code for the language at |index|.
diff --git a/components/translate/core/browser/translate_ui_delegate_unittest.cc b/components/translate/core/browser/translate_ui_delegate_unittest.cc
index c292ed996..279644f 100644
--- a/components/translate/core/browser/translate_ui_delegate_unittest.cc
+++ b/components/translate/core/browser/translate_ui_delegate_unittest.cc
@@ -263,6 +263,40 @@
   EXPECT_EQ("fr", delegate_->GetTargetLanguageCode());
 }
 
+TEST_F(TranslateUIDelegateTest, UpdateSourceLanguageTranslateEvent) {
+  // Test source language and corresponding TranslateEvent field.
+  EXPECT_EQ("ar", delegate_->GetSourceLanguageCode());
+  EXPECT_FALSE(
+      manager_->mutable_translate_event()->has_modified_source_language());
+
+  // Test that updating with current language does not update TranslateEvent.
+  delegate_->UpdateSourceLanguage("ar");
+  EXPECT_FALSE(
+      manager_->mutable_translate_event()->has_modified_source_language());
+
+  // Test that updating with different language does update TranslateEvent.
+  delegate_->UpdateSourceLanguage("es");
+  EXPECT_TRUE(
+      manager_->mutable_translate_event()->has_modified_source_language());
+}
+
+TEST_F(TranslateUIDelegateTest, UpdateTargetLanguageTranslateEvent) {
+  // Test target language and corresponding TranslateEvent field.
+  EXPECT_EQ("fr", delegate_->GetTargetLanguageCode());
+  EXPECT_FALSE(
+      manager_->mutable_translate_event()->has_modified_target_language());
+
+  // Test that updating with current language does not update TranslateEvent.
+  delegate_->UpdateTargetLanguage("fr");
+  EXPECT_FALSE(
+      manager_->mutable_translate_event()->has_modified_target_language());
+
+  // Test that updating with different language does update TranslateEvent.
+  delegate_->UpdateTargetLanguage("es");
+  EXPECT_TRUE(
+      manager_->mutable_translate_event()->has_modified_target_language());
+}
+
 TEST_F(TranslateUIDelegateTest, ContentLanguagesWhenPrefChangeObserverEnabled) {
   testContentLanguages(/*disableObservers=*/false);
 }
diff --git a/content/browser/BUILD.gn b/content/browser/BUILD.gn
index e14ae9fa..89c0f02 100644
--- a/content/browser/BUILD.gn
+++ b/content/browser/BUILD.gn
@@ -1875,6 +1875,8 @@
     "renderer_host/should_swap_browsing_instance.h",
     "renderer_host/stored_page.cc",
     "renderer_host/stored_page.h",
+    "renderer_host/subframe_history_navigation_throttle.cc",
+    "renderer_host/subframe_history_navigation_throttle.h",
     "renderer_host/text_input_manager.cc",
     "renderer_host/text_input_manager.h",
     "renderer_host/transient_allow_popup.cc",
@@ -2993,6 +2995,8 @@
       "android/web_contents_observer_proxy.h",
       "attribution_reporting/attribution_input_event_tracker_android.cc",
       "attribution_reporting/attribution_input_event_tracker_android.h",
+      "attribution_reporting/attribution_os_level_manager_android.cc",
+      "attribution_reporting/attribution_os_level_manager_android.h",
       "child_process_launcher_helper_android.cc",
       "contacts/contacts_provider_android.cc",
       "contacts/contacts_provider_android.h",
diff --git a/content/browser/accessibility/accessibility_ipc_error_browsertest.cc b/content/browser/accessibility/accessibility_ipc_error_browsertest.cc
index 6923f1ad..625e1bb 100644
--- a/content/browser/accessibility/accessibility_ipc_error_browsertest.cc
+++ b/content/browser/accessibility/accessibility_ipc_error_browsertest.cc
@@ -80,7 +80,7 @@
     // the first event.
     AccessibilityNotificationWaiter waiter(shell()->web_contents(),
                                            ui::kAXModeComplete,
-                                           ax::mojom::Event::kLayoutComplete);
+                                           ax::mojom::Event::kLoadComplete);
     ASSERT_TRUE(waiter.WaitForNotification());
   }
 
@@ -176,7 +176,7 @@
     // the first event.
     AccessibilityNotificationWaiter waiter(shell()->web_contents(),
                                            ui::kAXModeComplete,
-                                           ax::mojom::Event::kLayoutComplete);
+                                           ax::mojom::Event::kLoadComplete);
     ASSERT_TRUE(waiter.WaitForNotification());
   }
 
diff --git a/content/browser/attribution_reporting/attribution_interop_unittest.cc b/content/browser/attribution_reporting/attribution_interop_unittest.cc
index b7725fe..395ac507 100644
--- a/content/browser/attribution_reporting/attribution_interop_unittest.cc
+++ b/content/browser/attribution_reporting/attribution_interop_unittest.cc
@@ -12,6 +12,7 @@
 #include "base/files/file_util.h"
 #include "base/path_service.h"
 #include "base/ranges/algorithm.h"
+#include "base/strings/string_piece.h"
 #include "base/test/scoped_feature_list.h"
 #include "base/test/values_test_util.h"
 #include "base/types/expected.h"
@@ -63,18 +64,16 @@
   return input_paths;
 }
 
-bool UnorderedMatch(base::Value::List* a, base::Value::List* b) {
-  if (!a) {
-    return !b || b->empty();
+void ProcessReports(base::Value::Dict& dict, base::StringPiece key) {
+  base::Value::List* list = dict.FindList(key);
+  if (!list) {
+    return;
   }
-
-  if (!b) {
-    return !a || a->empty();
+  if (list->empty()) {
+    dict.Remove(key);
+    return;
   }
-
-  base::ranges::sort(*a);
-  base::ranges::sort(*b);
-  return *a == *b;
+  base::ranges::sort(*list);
 }
 
 class AttributionInteropTest : public ::testing::TestWithParam<base::FilePath> {
@@ -129,14 +128,14 @@
 
   base::Value::Dict& expected_output_dict = expected_output->GetDict();
 
-  for (const char* field :
-       {kEventLevelResultsKey, kDebugEventLevelResultsKey,
-        kAggregatableResultsKey, kDebugAggregatableResultsKey,
-        kVerboseDebugReportsKey}) {
-    EXPECT_TRUE(UnorderedMatch(actual_output->FindList(field),
-                               expected_output_dict.FindList(field)))
-        << field;
+  for (const char* key : {kEventLevelResultsKey, kDebugEventLevelResultsKey,
+                          kAggregatableResultsKey, kDebugAggregatableResultsKey,
+                          kVerboseDebugReportsKey}) {
+    ProcessReports(*actual_output, key);
+    ProcessReports(expected_output_dict, key);
   }
+
+  EXPECT_THAT(*actual_output, base::test::IsJson(expected_output_dict));
 }
 
 INSTANTIATE_TEST_SUITE_P(
diff --git a/content/browser/attribution_reporting/attribution_manager_impl.cc b/content/browser/attribution_reporting/attribution_manager_impl.cc
index 480e18ab..03c7e68 100644
--- a/content/browser/attribution_reporting/attribution_manager_impl.cc
+++ b/content/browser/attribution_reporting/attribution_manager_impl.cc
@@ -29,6 +29,7 @@
 #include "base/threading/sequence_bound.h"
 #include "base/threading/thread_restrictions.h"
 #include "base/time/time.h"
+#include "build/build_config.h"
 #include "components/attribution_reporting/os_support.mojom.h"
 #include "components/attribution_reporting/source_registration_error.mojom.h"
 #include "components/attribution_reporting/suitable_origin.h"
@@ -72,6 +73,10 @@
 #include "third_party/blink/public/common/storage_key/storage_key.h"
 #include "url/gurl.h"
 
+#if BUILDFLAG(IS_ANDROID)
+#include "content/browser/attribution_reporting/attribution_os_level_manager_android.h"
+#endif
+
 namespace content {
 
 namespace {
@@ -479,6 +484,11 @@
   DCHECK(storage_task_runner_);
   DCHECK(cookie_checker_);
   DCHECK(report_sender_);
+
+#if BUILDFLAG(IS_ANDROID)
+  attribution_os_level_manager_ =
+      std::make_unique<AttributionOsLevelManagerAndroid>();
+#endif
 }
 
 AttributionManagerImpl::~AttributionManagerImpl() {
diff --git a/content/browser/attribution_reporting/attribution_manager_impl.h b/content/browser/attribution_reporting/attribution_manager_impl.h
index 4faae779..a51127f9 100644
--- a/content/browser/attribution_reporting/attribution_manager_impl.h
+++ b/content/browser/attribution_reporting/attribution_manager_impl.h
@@ -18,6 +18,7 @@
 #include "base/memory/weak_ptr.h"
 #include "base/observer_list.h"
 #include "base/threading/sequence_bound.h"
+#include "build/build_config.h"
 #include "components/attribution_reporting/os_support.mojom.h"
 #include "components/attribution_reporting/source_registration_error.mojom-forward.h"
 #include "content/browser/aggregation_service/aggregation_service.h"
@@ -58,6 +59,10 @@
 class StoragePartitionImpl;
 class StoredSource;
 
+#if BUILDFLAG(IS_ANDROID)
+class AttributionOsLevelManagerAndroid;
+#endif
+
 struct GlobalRenderFrameHostId;
 struct SendResult;
 
@@ -311,6 +316,11 @@
 
   base::ObserverList<AttributionObserver> observers_;
 
+#if BUILDFLAG(IS_ANDROID)
+  std::unique_ptr<AttributionOsLevelManagerAndroid>
+      attribution_os_level_manager_;
+#endif
+
   base::WeakPtrFactory<AttributionManagerImpl> weak_factory_{this};
 };
 
diff --git a/content/browser/attribution_reporting/attribution_os_level_manager_android.cc b/content/browser/attribution_reporting/attribution_os_level_manager_android.cc
new file mode 100644
index 0000000..4e8a29ed
--- /dev/null
+++ b/content/browser/attribution_reporting/attribution_os_level_manager_android.cc
@@ -0,0 +1,39 @@
+// 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 "content/browser/attribution_reporting/attribution_os_level_manager_android.h"
+
+#include "content/public/android/content_jni_headers/AttributionOsLevelManager_jni.h"
+#include "url/android/gurl_android.h"
+#include "url/origin.h"
+
+namespace content {
+
+AttributionOsLevelManagerAndroid::AttributionOsLevelManagerAndroid() = default;
+
+AttributionOsLevelManagerAndroid::~AttributionOsLevelManagerAndroid() {
+  if (jobj_) {
+    Java_AttributionOsLevelManager_nativeDestroyed(
+        base::android::AttachCurrentThread(), jobj_);
+  }
+}
+
+void AttributionOsLevelManagerAndroid::RegisterAttributionSource(
+    const GURL& registration_url,
+    const url::Origin& top_level_origin,
+    bool is_debug_key_allowed) {
+  JNIEnv* env = base::android::AttachCurrentThread();
+
+  if (!jobj_) {
+    jobj_ = Java_AttributionOsLevelManager_Constructor(
+        env, reinterpret_cast<intptr_t>(this));
+  }
+
+  Java_AttributionOsLevelManager_registerAttributionSource(
+      env, jobj_, url::GURLAndroid::FromNativeGURL(env, registration_url),
+      url::GURLAndroid::FromNativeGURL(env, top_level_origin.GetURL()),
+      is_debug_key_allowed);
+}
+
+}  // namespace content
diff --git a/content/browser/attribution_reporting/attribution_os_level_manager_android.h b/content/browser/attribution_reporting/attribution_os_level_manager_android.h
new file mode 100644
index 0000000..8ebd8b4
--- /dev/null
+++ b/content/browser/attribution_reporting/attribution_os_level_manager_android.h
@@ -0,0 +1,46 @@
+// 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 CONTENT_BROWSER_ATTRIBUTION_REPORTING_ATTRIBUTION_OS_LEVEL_MANAGER_ANDROID_H_
+#define CONTENT_BROWSER_ATTRIBUTION_REPORTING_ATTRIBUTION_OS_LEVEL_MANAGER_ANDROID_H_
+
+#include <jni.h>
+
+#include "base/android/jni_android.h"
+
+class GURL;
+
+namespace url {
+class Origin;
+}  // namespace url
+
+namespace content {
+
+// This class is responsible for communicating with java code to handle
+// registering events received on the web with Android.
+class AttributionOsLevelManagerAndroid {
+ public:
+  AttributionOsLevelManagerAndroid();
+  ~AttributionOsLevelManagerAndroid();
+
+  AttributionOsLevelManagerAndroid(const AttributionOsLevelManagerAndroid&) =
+      delete;
+  AttributionOsLevelManagerAndroid& operator=(
+      const AttributionOsLevelManagerAndroid&) = delete;
+
+  AttributionOsLevelManagerAndroid(AttributionOsLevelManagerAndroid&&) = delete;
+  AttributionOsLevelManagerAndroid& operator=(
+      AttributionOsLevelManagerAndroid&&) = delete;
+
+  void RegisterAttributionSource(const GURL& registration_url,
+                                 const url::Origin& top_level_origin,
+                                 bool is_debug_key_allowed);
+
+ private:
+  base::android::ScopedJavaGlobalRef<jobject> jobj_;
+};
+
+}  // namespace content
+
+#endif  // CONTENT_BROWSER_ATTRIBUTION_REPORTING_ATTRIBUTION_OS_LEVEL_MANAGER_ANDROID_H_
diff --git a/content/browser/devtools/protocol/storage_handler.cc b/content/browser/devtools/protocol/storage_handler.cc
index 6c469ad2..df66378 100644
--- a/content/browser/devtools/protocol/storage_handler.cc
+++ b/content/browser/devtools/protocol/storage_handler.cc
@@ -294,6 +294,11 @@
                                         owner_origin, params);
   }
 
+  void OnUrnUuidGenerated(const GURL& urn_uuid) override {}
+
+  void OnConfigPopulated(
+      const absl::optional<FencedFrameConfig>& config) override {}
+
  private:
   raw_ptr<StorageHandler> const owner_;
   base::ScopedObservation<
diff --git a/content/browser/direct_sockets/direct_sockets_udp_browsertest.cc b/content/browser/direct_sockets/direct_sockets_udp_browsertest.cc
index f902c308..d799bd4 100644
--- a/content/browser/direct_sockets/direct_sockets_udp_browsertest.cc
+++ b/content/browser/direct_sockets/direct_sockets_udp_browsertest.cc
@@ -133,49 +133,6 @@
   EXPECT_EQ("closeUdp succeeded", EvalJs(shell(), script));
 }
 
-IN_PROC_BROWSER_TEST_F(DirectSocketsUdpBrowserTest, SendUdp) {
-  // We send datagrams with one byte, two bytes, three bytes, ...
-  const uint32_t kRequiredDatagrams = 35;
-  const uint32_t kRequiredBytes =
-      kRequiredDatagrams * (kRequiredDatagrams + 1) / 2;
-
-  // Any attempt to make this a class member results into
-  // "This caller requires a single-threaded context".
-  network::test::UDPSocketListenerImpl listener;
-  mojo::Receiver<network::mojom::UDPSocketListener> listener_receiver{
-      &listener};
-
-  auto [server_address, server_helper] =
-      CreateUDPServerSocket(listener_receiver.BindNewPipeAndPassRemote());
-
-  GetUDPServerSocket()->ReceiveMore(kRequiredDatagrams);
-
-  const std::string script =
-      JsReplace("sendUdp({ remoteAddress: $1, remotePort: $2 }, $3)",
-                server_address.ToStringWithoutPort(), server_address.port(),
-                static_cast<int>(kRequiredBytes));
-
-  EXPECT_EQ("send succeeded", EvalJs(shell(), script));
-
-  listener.WaitForReceivedResults(kRequiredDatagrams);
-  EXPECT_EQ(listener.results().size(), kRequiredDatagrams);
-
-  uint32_t bytes_received = 0, expected_data_size = 0;
-  for (const network::test::UDPSocketListenerImpl::ReceivedResult& result :
-       listener.results()) {
-    expected_data_size++;
-    EXPECT_EQ(result.net_error, net::OK);
-    EXPECT_TRUE(result.src_addr.has_value());
-    EXPECT_TRUE(result.data.has_value());
-    EXPECT_EQ(result.data->size(), expected_data_size);
-    for (uint8_t current : *result.data) {
-      EXPECT_EQ(current, bytes_received % 256);
-      ++bytes_received;
-    }
-  }
-  EXPECT_EQ(bytes_received, kRequiredBytes);
-}
-
 IN_PROC_BROWSER_TEST_F(DirectSocketsUdpBrowserTest, SendUdpAfterClose) {
   const int32_t kRequiredBytes = 1;
   const std::string script =
@@ -186,59 +143,6 @@
               ::testing::HasSubstr("Stream closed."));
 }
 
-IN_PROC_BROWSER_TEST_F(DirectSocketsUdpBrowserTest, ReadUdp) {
-  const uint32_t kRequiredDatagrams = 35;
-  const uint32_t kRequiredBytes =
-      kRequiredDatagrams * (kRequiredDatagrams + 1) / 2;
-
-  network::test::UDPSocketListenerImpl listener;
-  mojo::Receiver<network::mojom::UDPSocketListener> listener_receiver{
-      &listener};
-
-  auto [server_address, server_helper] =
-      CreateUDPServerSocket(listener_receiver.BindNewPipeAndPassRemote());
-
-  // Why so complicated? Turns out that in order to send udp datagrams from
-  // server to client we need to be aware what the client's local port is.
-  // It cannot be predefined, so the first step is to create a socket in the
-  // global scope and retrieve the assigned local port.
-  const std::string open_socket = JsReplace(
-      R"((async () => {
-        socket = new UDPSocket({ remoteAddress: $1, remotePort: $2 });
-        let { localPort } = await socket.opened;
-        return localPort;
-      })())",
-      server_address.ToStringWithoutPort(), server_address.port());
-
-  const uint16_t local_port = EvalJs(shell(), open_socket).ExtractInt();
-
-  const std::string async_read = content::test::WrapAsync(JsReplace(
-      R"(
-        let { readable } = await socket.opened;
-        let reader = readable.getReader();
-        return await readLoop(reader, $1);
-      )",
-      static_cast<int>(kRequiredBytes)));
-  auto future = GetAsyncJsRunner()->RunScript(async_read);
-
-  // With a client socket listening in the javascript code, we can finally start
-  // sending out data.
-  net::IPEndPoint client_addr(net::IPAddress::IPv4Localhost(), local_port);
-  uint32_t bytesSent = 0;
-  for (uint32_t i = 0; i < kRequiredDatagrams; i++) {
-    std::vector<uint8_t> message(i + 1);
-    for (uint8_t& byte : message) {
-      byte = bytesSent % 256;
-      bytesSent++;
-    }
-    EXPECT_EQ(net::OK, server_helper->SendToSync(client_addr, message));
-  }
-
-  // Blocks until script execution is complete and returns the resulting
-  // message.
-  ASSERT_EQ(future->Get(), "readLoop succeeded.");
-}
-
 IN_PROC_BROWSER_TEST_F(DirectSocketsUdpBrowserTest, ReadUdpAfterSocketClose) {
   network::test::UDPSocketListenerImpl listener;
   mojo::Receiver<network::mojom::UDPSocketListener> listener_receiver{
@@ -358,7 +262,7 @@
 }
 
 IN_PROC_BROWSER_TEST_F(DirectSocketsUdpBrowserTest, ExchangeUdp) {
-  ASSERT_THAT(EvalJs(shell(), "exchangeSingleUdpPacketBetweenClientAndServer()")
+  ASSERT_THAT(EvalJs(shell(), "exchangeUdpPacketsBetweenClientAndServer()")
                   .ExtractString(),
               testing::HasSubstr("succeeded"));
 }
diff --git a/content/browser/fenced_frame/fenced_frame_url_mapping.cc b/content/browser/fenced_frame/fenced_frame_url_mapping.cc
index b95e000..53b02f6d8 100644
--- a/content/browser/fenced_frame/fenced_frame_url_mapping.cc
+++ b/content/browser/fenced_frame/fenced_frame_url_mapping.cc
@@ -219,7 +219,8 @@
   it->second.erase(observer_it);
 }
 
-void FencedFrameURLMapping::OnSharedStorageURNMappingResultDetermined(
+absl::optional<FencedFrameConfig>
+FencedFrameURLMapping::OnSharedStorageURNMappingResultDetermined(
     const GURL& urn_uuid,
     const SharedStorageURNMappingResult& mapping_result) {
   auto pending_it = pending_urn_uuid_to_url_map_.find(urn_uuid);
@@ -254,6 +255,8 @@
   }
 
   pending_urn_uuid_to_url_map_.erase(pending_it);
+
+  return config;
 }
 
 SharedStorageBudgetMetadata*
diff --git a/content/browser/fenced_frame/fenced_frame_url_mapping.h b/content/browser/fenced_frame/fenced_frame_url_mapping.h
index 926cbfb..275ceb6 100644
--- a/content/browser/fenced_frame/fenced_frame_url_mapping.h
+++ b/content/browser/fenced_frame/fenced_frame_url_mapping.h
@@ -31,6 +31,9 @@
 // Keeps a mapping of fenced frames URN:UUID and URL. Also keeps a set of
 // pending mapped URN:UUIDs to support asynchronous mapping. See
 // https://github.com/WICG/fenced-frame/blob/master/explainer/opaque_src.md
+// TODO(crbug.com/1405477): Add methods for:
+// 1. generating the pending config.
+// 2. finalizing the pending config.
 class CONTENT_EXPORT FencedFrameURLMapping {
  public:
   // The runURLSelectionOperation's url mapping result. It contains the mapped
@@ -126,7 +129,12 @@
   // will trigger the observers' OnFencedFrameURLMappingComplete() method
   // associated with the `urn_uuid`, unregister those observers, and move the
   // `urn_uuid` from `pending_urn_uuid_to_url_map_` to `urn_uuid_to_url_map_`.
-  void OnSharedStorageURNMappingResultDetermined(
+  // If the resolved URL is fenced-frame-compatible, the return value is the
+  // populated fenced frame config. It is used to notify the observers in shared
+  // storage worklet host manager. Tests can then obtain the populated fenced
+  // frame configs from the observers.
+  // Otherwise this method returns an absl::nullopt.
+  absl::optional<FencedFrameConfig> OnSharedStorageURNMappingResultDetermined(
       const GURL& urn_uuid,
       const SharedStorageURNMappingResult& mapping_result);
 
diff --git a/content/browser/interest_group/auction_runner.cc b/content/browser/interest_group/auction_runner.cc
index 134901a7..4bd98af 100644
--- a/content/browser/interest_group/auction_runner.cc
+++ b/content/browser/interest_group/auction_runner.cc
@@ -158,6 +158,7 @@
 
 void AuctionRunner::ResolvedBuyerTimeoutsPromise(
     blink::mojom::AuctionAdConfigAuctionIdPtr auction_id,
+    blink::mojom::AuctionAdConfigBuyerTimeoutField field,
     const blink::AuctionConfig::BuyerTimeouts& buyer_timeouts) {
   if (state_ == State::kFailed) {
     return;
@@ -171,14 +172,24 @@
     return;
   }
 
-  if (!config->non_shared_params.buyer_timeouts.is_promise()) {
+  blink::AuctionConfig::MaybePromiseBuyerTimeouts* field_ptr = nullptr;
+  switch (field) {
+    case blink::mojom::AuctionAdConfigBuyerTimeoutField::kPerBuyerTimeouts:
+      field_ptr = &config->non_shared_params.buyer_timeouts;
+      break;
+    case blink::mojom::AuctionAdConfigBuyerTimeoutField::
+        kPerBuyerCumulativeTimeouts:
+      field_ptr = &config->non_shared_params.buyer_cumulative_timeouts;
+      break;
+  }
+
+  if (!field_ptr->is_promise()) {
     mojo::ReportBadMessage("ResolvedBuyerTimeoutsPromise updating non-promise");
     return;
   }
 
-  config->non_shared_params.buyer_timeouts =
-      blink::AuctionConfig::MaybePromiseBuyerTimeouts::FromValue(
-          buyer_timeouts);
+  *field_ptr = blink::AuctionConfig::MaybePromiseBuyerTimeouts::FromValue(
+      buyer_timeouts);
   NotifyPromiseResolved(auction_id.get(), config);
 }
 
diff --git a/content/browser/interest_group/auction_runner.h b/content/browser/interest_group/auction_runner.h
index 5a2c415d..399875b 100644
--- a/content/browser/interest_group/auction_runner.h
+++ b/content/browser/interest_group/auction_runner.h
@@ -168,6 +168,7 @@
           per_buyer_signals) override;
   void ResolvedBuyerTimeoutsPromise(
       blink::mojom::AuctionAdConfigAuctionIdPtr auction_id,
+      blink::mojom::AuctionAdConfigBuyerTimeoutField field,
       const blink::AuctionConfig::BuyerTimeouts& buyer_timeouts) override;
   void ResolvedDirectFromSellerSignalsPromise(
       blink::mojom::AuctionAdConfigAuctionIdPtr auction_id,
diff --git a/content/browser/interest_group/auction_runner_unittest.cc b/content/browser/interest_group/auction_runner_unittest.cc
index e366452..3e6bdf88 100644
--- a/content/browser/interest_group/auction_runner_unittest.cc
+++ b/content/browser/interest_group/auction_runner_unittest.cc
@@ -557,13 +557,16 @@
       if (signals1[auctionConfig.seller + 'Signals'] !== 'Ad PlatformSignals')
         throw new Error("Wrong perBuyerSignals in auctionConfig");
       if (typeof auctionConfig.perBuyerTimeouts['https://adplatform.com'] !==
-          "number") {
+              "number" ||
+          typeof auctionConfig.perBuyerTimeouts['*'] !== "number") {
         throw new Error("timeout in auctionConfig.perBuyerTimeouts is not a " +
                         "number. huh");
       }
-      if (typeof auctionConfig.perBuyerTimeouts['*'] !== "number") {
-        throw new Error("timeout in auctionConfig.perBuyerTimeouts is not a " +
-                        "number. huh");
+      if (auctionConfig.perBuyerCumulativeTimeouts['https://adplatform.com'] !==
+              12345 ||
+          auctionConfig.perBuyerCumulativeTimeouts['*'] !== 23456) {
+        throw new Error("timeout in auctionConfig.perBuyerCumulativeTimeouts " +
+                        "is the wrong value. huh");
       }
       if (auctionConfig.sellerSignals["url"] != decisionLogicUrl)
         throw new Error("Wrong sellerSignals");
@@ -1286,6 +1289,21 @@
     }
   }
 
+  blink::AuctionConfig::MaybePromiseBuyerTimeouts MakeBuyerCumulativeTimeouts(
+      bool use_promise) {
+    if (use_promise) {
+      return blink::AuctionConfig::MaybePromiseBuyerTimeouts::FromPromise();
+    } else {
+      blink::AuctionConfig::BuyerTimeouts buyer_cumulative_timeouts;
+      buyer_cumulative_timeouts.per_buyer_timeouts.emplace();
+      buyer_cumulative_timeouts.per_buyer_timeouts.value()[kBidder1] =
+          base::Milliseconds(12345);
+      buyer_cumulative_timeouts.all_buyers_timeout = base::Milliseconds(23456);
+      return blink::AuctionConfig::MaybePromiseBuyerTimeouts::FromValue(
+          std::move(buyer_cumulative_timeouts));
+    }
+  }
+
   // Helper to create an auction config with the specified values.
   blink::AuctionConfig CreateAuctionConfig(
       const GURL& seller_decision_logic_url,
@@ -1307,6 +1325,8 @@
         use_promise_for_per_buyer_signals_, auction_config.seller);
     auction_config.non_shared_params.buyer_timeouts =
         MakeBuyerTimeouts(use_promise_for_buyer_timeouts_);
+    auction_config.non_shared_params.buyer_cumulative_timeouts =
+        MakeBuyerCumulativeTimeouts(use_promise_for_buyer_cumulative_timeouts_);
     auction_config.non_shared_params.auction_signals = MakeAuctionSignals(
         use_promise_for_auction_signals_, auction_config.seller);
 
@@ -1886,6 +1906,7 @@
   bool use_promise_for_auction_signals_ = false;
   bool use_promise_for_per_buyer_signals_ = false;
   bool use_promise_for_buyer_timeouts_ = false;
+  bool use_promise_for_buyer_cumulative_timeouts_ = false;
 
   // Unlike others, this is only test with promises at this level.
   bool pass_promise_for_direct_from_seller_signals_ = false;
@@ -5065,10 +5086,12 @@
                          HasSubstr("Uncaught Error: wrong auctionSignals."))));
 }
 
-// An auction that passes perBuyerSignals and buyerTimeouts via promises.
+// An auction that passes perBuyerSignals, perBuyerTimeouts, and
+// perBuyerCumulativeTimeouts via promises.
 TEST_F(AuctionRunnerTest, PromiseSignals3) {
   use_promise_for_per_buyer_signals_ = true;
   use_promise_for_buyer_timeouts_ = true;
+  use_promise_for_buyer_cumulative_timeouts_ = true;
 
   auction_worklet::AddJavascriptResponse(
       &url_loader_factory_, kBidder1Url,
@@ -5107,11 +5130,20 @@
   task_environment()->RunUntilIdle();
   EXPECT_FALSE(auction_run_loop_->AnyQuitCalled());
 
-  // Feed in buyerTimeouts.
+  // Feed in perBuyerTimeouts.
   abortable_ad_auction_->ResolvedBuyerTimeoutsPromise(
       blink::mojom::AuctionAdConfigAuctionId::NewMainAuction(0),
+      blink::mojom::AuctionAdConfigBuyerTimeoutField::kPerBuyerTimeouts,
       MakeBuyerTimeouts(/*use_promise=*/false).value());
+  task_environment()->RunUntilIdle();
+  EXPECT_FALSE(auction_run_loop_->AnyQuitCalled());
 
+  // Feed in perBuyerCumulativeTimeouts.
+  abortable_ad_auction_->ResolvedBuyerTimeoutsPromise(
+      blink::mojom::AuctionAdConfigAuctionId::NewMainAuction(0),
+      blink::mojom::AuctionAdConfigBuyerTimeoutField::
+          kPerBuyerCumulativeTimeouts,
+      MakeBuyerCumulativeTimeouts(/*use_promise=*/false).value());
   auction_run_loop_->Run();
 
   EXPECT_EQ(InterestGroupKey(kBidder2, kBidder2Name), result_.winning_group_id);
@@ -5119,7 +5151,7 @@
   EXPECT_THAT(result_.errors, testing::ElementsAre());
 }
 
-// An auction that passes perBuyerSignals and buyerTimeouts via promises.
+// An auction that passes perBuyerSignals and perBuyerTimeouts via promises.
 // Empty values are provided, which causes the validation scripts to complain.
 TEST_F(AuctionRunnerTest, PromiseSignals4) {
   use_promise_for_per_buyer_signals_ = true;
@@ -5159,9 +5191,10 @@
   task_environment()->RunUntilIdle();
   EXPECT_FALSE(auction_run_loop_->AnyQuitCalled());
 
-  // Feed in buyerTimeouts.
+  // Feed in perBuyerTimeouts.
   abortable_ad_auction_->ResolvedBuyerTimeoutsPromise(
       blink::mojom::AuctionAdConfigAuctionId::NewMainAuction(0),
+      blink::mojom::AuctionAdConfigBuyerTimeoutField::kPerBuyerTimeouts,
       blink::AuctionConfig::BuyerTimeouts());
 
   auction_run_loop_->Run();
@@ -5591,10 +5624,11 @@
   task_environment()->RunUntilIdle();
   EXPECT_FALSE(auction_run_loop_->AnyQuitCalled());
 
-  // Feed in sellerSignals with wrong component ID.
+  // Feed in perBuyerSignals with wrong component ID.
   blink::AuctionConfig::BuyerTimeouts buyer_timeouts;
   abortable_ad_auction_->ResolvedBuyerTimeoutsPromise(
       blink::mojom::AuctionAdConfigAuctionId::NewComponentAuction(0),
+      blink::mojom::AuctionAdConfigBuyerTimeoutField::kPerBuyerTimeouts,
       buyer_timeouts);
   auction_run_loop_->RunUntilIdle();
   EXPECT_EQ("Invalid auction ID in ResolvedBuyerTimeoutsPromise",
@@ -5602,6 +5636,49 @@
 }
 
 TEST_F(AuctionRunnerTest, PromiseSignalsBadAuctionId4) {
+  use_promise_for_buyer_cumulative_timeouts_ = true;
+
+  auction_worklet::AddJavascriptResponse(
+      &url_loader_factory_, kBidder1Url,
+      MakeBidScript(kSeller, "1", "https://ad1.com/", /*num_ad_components=*/0,
+                    kBidder1, kBidder1Name));
+  auction_worklet::AddJavascriptResponse(
+      &url_loader_factory_, kBidder2Url,
+      MakeBidScript(kSeller, "2", "https://ad2.com/", /*num_ad_components=*/0,
+                    kBidder2, kBidder2Name));
+  auction_worklet::AddJavascriptResponse(&url_loader_factory_, kSellerUrl,
+                                         MakeAuctionScript());
+
+  std::vector<StorageInterestGroup> bidders;
+  bidders.emplace_back(MakeInterestGroup(
+      kBidder1, kBidder1Name, kBidder1Url,
+      /*trusted_bidding_signals_url=*/absl::nullopt,
+      /*trusted_bidding_signals_keys=*/{}, GURL("https://ad1.com"),
+      /*ad_component_urls=*/absl::nullopt));
+  bidders.emplace_back(MakeInterestGroup(
+      kBidder2, kBidder2Name, kBidder2Url,
+      /*trusted_bidding_signals_url=*/absl::nullopt,
+      /*trusted_bidding_signals_keys=*/{}, GURL("https://ad2.com"),
+      /*ad_component_urls=*/absl::nullopt));
+  StartAuction(kSellerUrl, std::move(bidders));
+
+  // Can't complete yet.
+  task_environment()->RunUntilIdle();
+  EXPECT_FALSE(auction_run_loop_->AnyQuitCalled());
+
+  // Feed in perBuyerSignals with wrong component ID.
+  blink::AuctionConfig::BuyerTimeouts buyer_cumulative_timeouts;
+  abortable_ad_auction_->ResolvedBuyerTimeoutsPromise(
+      blink::mojom::AuctionAdConfigAuctionId::NewComponentAuction(0),
+      blink::mojom::AuctionAdConfigBuyerTimeoutField::
+          kPerBuyerCumulativeTimeouts,
+      buyer_cumulative_timeouts);
+  auction_run_loop_->RunUntilIdle();
+  EXPECT_EQ("Invalid auction ID in ResolvedBuyerTimeoutsPromise",
+            TakeBadMessage());
+}
+
+TEST_F(AuctionRunnerTest, PromiseSignalsBadAuctionId5) {
   pass_promise_for_direct_from_seller_signals_ = true;
 
   auction_worklet::AddJavascriptResponse(
@@ -5938,16 +6015,70 @@
   // Feed in buyer timeouts twice.
   blink::AuctionConfig::BuyerTimeouts timeouts;
   abortable_ad_auction_->ResolvedBuyerTimeoutsPromise(
-      blink::mojom::AuctionAdConfigAuctionId::NewMainAuction(0), timeouts);
+      blink::mojom::AuctionAdConfigAuctionId::NewMainAuction(0),
+      blink::mojom::AuctionAdConfigBuyerTimeoutField::kPerBuyerTimeouts,
+      timeouts);
   abortable_ad_auction_->ResolvedBuyerTimeoutsPromise(
-      blink::mojom::AuctionAdConfigAuctionId::NewMainAuction(0), timeouts);
+      blink::mojom::AuctionAdConfigAuctionId::NewMainAuction(0),
+      blink::mojom::AuctionAdConfigBuyerTimeoutField::kPerBuyerTimeouts,
+      timeouts);
+  task_environment()->RunUntilIdle();
+  EXPECT_EQ("ResolvedBuyerTimeoutsPromise updating non-promise",
+            TakeBadMessage());
+}
+
+// Trying to update buyer cumulative timeouts twice.
+TEST_F(AuctionRunnerTest, PromiseSignalsUpdateNonPromise7) {
+  // Have two kind of promises so we don't just finish after first update.
+  use_promise_for_per_buyer_signals_ = true;
+  use_promise_for_buyer_cumulative_timeouts_ = true;
+
+  auction_worklet::AddJavascriptResponse(
+      &url_loader_factory_, kBidder1Url,
+      MakeBidScript(kSeller, "1", "https://ad1.com/", /*num_ad_components=*/0,
+                    kBidder1, kBidder1Name));
+  auction_worklet::AddJavascriptResponse(
+      &url_loader_factory_, kBidder2Url,
+      MakeBidScript(kSeller, "2", "https://ad2.com/", /*num_ad_components=*/0,
+                    kBidder2, kBidder2Name));
+  auction_worklet::AddJavascriptResponse(&url_loader_factory_, kSellerUrl,
+                                         MakeAuctionScript());
+
+  std::vector<StorageInterestGroup> bidders;
+  bidders.emplace_back(MakeInterestGroup(
+      kBidder1, kBidder1Name, kBidder1Url,
+      /*trusted_bidding_signals_url=*/absl::nullopt,
+      /*trusted_bidding_signals_keys=*/{}, GURL("https://ad1.com"),
+      /*ad_component_urls=*/absl::nullopt));
+  bidders.emplace_back(MakeInterestGroup(
+      kBidder2, kBidder2Name, kBidder2Url,
+      /*trusted_bidding_signals_url=*/absl::nullopt,
+      /*trusted_bidding_signals_keys=*/{}, GURL("https://ad2.com"),
+      /*ad_component_urls=*/absl::nullopt));
+  StartAuction(kSellerUrl, std::move(bidders));
+
+  // Can't complete yet.
+  task_environment()->RunUntilIdle();
+  EXPECT_FALSE(auction_run_loop_->AnyQuitCalled());
+
+  // Feed in buyer timeouts twice.
+  blink::AuctionConfig::BuyerTimeouts timeouts;
+  abortable_ad_auction_->ResolvedBuyerTimeoutsPromise(
+      blink::mojom::AuctionAdConfigAuctionId::NewMainAuction(0),
+      blink::mojom::AuctionAdConfigBuyerTimeoutField::kPerBuyerTimeouts,
+      timeouts);
+  abortable_ad_auction_->ResolvedBuyerTimeoutsPromise(
+      blink::mojom::AuctionAdConfigAuctionId::NewMainAuction(0),
+      blink::mojom::AuctionAdConfigBuyerTimeoutField::
+          kPerBuyerCumulativeTimeouts,
+      timeouts);
   task_environment()->RunUntilIdle();
   EXPECT_EQ("ResolvedBuyerTimeoutsPromise updating non-promise",
             TakeBadMessage());
 }
 
 // Trying to update direct from seller signals twice.
-TEST_F(AuctionRunnerTest, PromiseSignalsUpdateNonPromise7) {
+TEST_F(AuctionRunnerTest, PromiseSignalsUpdateNonPromise8) {
   // Have two kind of promises so we don't just finish after first update.
   use_promise_for_per_buyer_signals_ = true;
   pass_promise_for_direct_from_seller_signals_ = true;
diff --git a/content/browser/interest_group/interest_group_browsertest.cc b/content/browser/interest_group/interest_group_browsertest.cc
index a0cd698..97ed794 100644
--- a/content/browser/interest_group/interest_group_browsertest.cc
+++ b/content/browser/interest_group/interest_group_browsertest.cc
@@ -3619,7 +3619,7 @@
 }
 
 // Exercise error-handling path in the renderer for promise-delivered
-// perBuyerSignals.
+// perBuyerTimeouts.
 IN_PROC_BROWSER_TEST_F(InterestGroupBrowserTest,
                        RunAdAuctionResolvePromiseInvalidPerBuyerTimeouts) {
   GURL test_url = https_server_->GetURL("a.test", "/echo");
@@ -3681,6 +3681,111 @@
   WaitForAccessObserved({});
 }
 
+// Test rejection path in the renderer for promise-delivered
+// perBuyerCumulativeTimeouts.
+IN_PROC_BROWSER_TEST_F(InterestGroupBrowserTest,
+                       RunAdAuctionRejectPromisePerBuyerCumulativeTimeouts) {
+  GURL test_url = https_server_->GetURL("a.test", "/echo");
+  url::Origin test_origin = url::Origin::Create(test_url);
+  ASSERT_TRUE(NavigateToURL(shell(), test_url));
+  GURL ad_url = https_server_->GetURL("c.test", "/echo?render_cars");
+  GURL decision_url =
+      https_server_->GetURL("a.test", "/interest_group/decision_logic.js");
+  // Note: at present at least one bid must be made for promise checking to
+  // be guaranteed to happen; if the auction is (effectively) empty whether
+  // it happens or not is timing-dependent.
+  EXPECT_EQ(
+      kSuccess,
+      JoinInterestGroupAndVerify(
+          /*owner=*/test_origin,
+          /*name=*/"cars",
+          /*priority=*/0.0,
+          /*execution_mode=*/
+          blink::InterestGroup::ExecutionMode::kCompatibilityMode,
+          /*bidding_url=*/
+          https_server_->GetURL("a.test", "/interest_group/bidding_logic.js"),
+          /*ads=*/{{{ad_url, /*metadata=*/absl::nullopt}}}));
+
+  const char kAuctionConfigTemplate[] = R"({
+      seller: $1,
+      decisionLogicUrl: $2,
+      perBuyerCumulativeTimeouts: new Promise((resolve, reject) => { setTimeout(
+          () => { reject('boo'); }, 10) }),
+      interestGroupBuyers: [$1]
+  })";
+
+  EXPECT_EQ("Promise argument rejected or resolved to invalid value.",
+            RunAuctionAndWait(
+                JsReplace(kAuctionConfigTemplate, test_origin, decision_url)));
+  WaitForAccessObserved({});
+}
+
+// Exercise error-handling path in the renderer for promise-delivered
+// perBuyerCumulativeTimeouts.
+IN_PROC_BROWSER_TEST_F(
+    InterestGroupBrowserTest,
+    RunAdAuctionResolvePromiseInvalidPerBuyerCumulativeTimeouts) {
+  GURL test_url = https_server_->GetURL("a.test", "/echo");
+  url::Origin test_origin = url::Origin::Create(test_url);
+  ASSERT_TRUE(NavigateToURL(shell(), test_url));
+  GURL ad_url = https_server_->GetURL("c.test", "/echo?render_cars");
+  GURL decision_url =
+      https_server_->GetURL("a.test", "/interest_group/decision_logic.js");
+  // Note: at present at least one bid must be made for promise checking to
+  // be guaranteed to happen; if the auction is (effectively) empty whether
+  // it happens or not is timing-dependent.
+  EXPECT_EQ(
+      kSuccess,
+      JoinInterestGroupAndVerify(
+          /*owner=*/test_origin,
+          /*name=*/"cars",
+          /*priority=*/0.0,
+          /*execution_mode=*/
+          blink::InterestGroup::ExecutionMode::kCompatibilityMode,
+          /*bidding_url=*/
+          https_server_->GetURL("a.test", "/interest_group/bidding_logic.js"),
+          /*ads=*/{{{ad_url, /*metadata=*/absl::nullopt}}}));
+
+  const char kAuctionConfigTemplate[] = R"({
+      seller: $1,
+      decisionLogicUrl: $2,
+      perBuyerCumulativeTimeouts: new Promise((resolve, reject) => { setTimeout(
+          () => { resolve({'http://b.com': 52}); }, 10) }),
+      interestGroupBuyers: [$1]
+  })";
+
+  WebContentsConsoleObserver console_observer(shell()->web_contents());
+  console_observer.SetPattern(
+      "Uncaught (in promise) TypeError: Failed to execute 'runAdAuction' on "
+      "'NavigatorAuction': perBuyerCumulativeTimeouts buyer 'http://b.com' for "
+      "AuctionAdConfig with seller 'https://a.test:*' must be \"*\" (wildcard) "
+      "or a valid https origin.");
+  EXPECT_EQ("Promise argument rejected or resolved to invalid value.",
+            RunAuctionAndWait(
+                JsReplace(kAuctionConfigTemplate, test_origin, decision_url)));
+  EXPECT_TRUE(console_observer.Wait());
+  WaitForAccessObserved({});
+}
+
+IN_PROC_BROWSER_TEST_F(InterestGroupBrowserTest,
+                       RunAdAuctionInvalidPerBuyerCumulativeTimeoutsOrigin) {
+  ASSERT_TRUE(NavigateToURL(shell(), https_server_->GetURL("a.test", "/echo")));
+
+  EXPECT_EQ(
+      "TypeError: Failed to execute 'runAdAuction' on 'Navigator': "
+      "perBuyerCumulativeTimeouts buyer 'https://invalid^&' for "
+      "AuctionAdConfig "
+      "with seller 'https://test.com' must be \"*\" (wildcard) or a valid "
+      "https "
+      "origin.",
+      RunAuctionAndWait(R"({
+      seller: 'https://test.com',
+      decisionLogicUrl: 'https://test.com',
+      perBuyerCumulativeTimeouts: {'https://invalid^&': 100}
+  })"));
+  WaitForAccessObserved({});
+}
+
 IN_PROC_BROWSER_TEST_F(InterestGroupBrowserTest,
                        RunAdAuctionInvalidPerBuyerGroupLimitsValue) {
   ASSERT_TRUE(NavigateToURL(shell(), https_server_->GetURL("a.test", "/echo")));
@@ -7512,6 +7617,7 @@
     sellerTimeout: 200,
     perBuyerSignals: {$4: {signalsForBuyer: 1}, $5: {signalsForBuyer: 2}},
     perBuyerTimeouts: {$4: 110, $5: 120, '*': 150},
+    perBuyerCumulativeTimeouts: {$4: 130, $5: 140, '*': 160},
     perBuyerPrioritySignals: {$4: {foo: 1}, '*': {BaR: -2}}
   });
 })())",
@@ -7633,6 +7739,7 @@
     sellerTimeout: 200,
     perBuyerSignals: {$4: {signalsForBuyer: 1}, $5: {signalsForBuyer: 2}},
     perBuyerTimeouts: {$4: 110, $5: 120, '*': 150},
+    perBuyerCumulativeTimeouts: {$4: 130, $5: 140, '*': 160},
     perBuyerPrioritySignals: {$4: {foo: 1}, '*': {BaR: -2}}
   });
 })())",
@@ -7801,6 +7908,7 @@
     sellerTimeout: 300,
     perBuyerSignals: maybePromise({$8: ["top-level buyer signals"]}),
     perBuyerTimeouts: maybePromise({$8: 110, '*': 150}),
+    perBuyerCumulativeTimeouts: maybePromise({$8: 111, '*': 151}),
     perBuyerPrioritySignals: {'*': {foo: 3}},
     componentAuctions: [{
       seller: $5,
@@ -7813,6 +7921,7 @@
       sellerTimeout: 200,
       perBuyerSignals: maybePromise({$8: ["component buyer signals"]}),
       perBuyerTimeouts: maybePromise({$8: 200}),
+      perBuyerCumulativeTimeouts: maybePromise({$8: 201}),
       perBuyerPrioritySignals: {$8: {bar: 1}, '*': {BaZ: -2}},
     }],
   });
@@ -8409,8 +8518,10 @@
   EXPECT_EQ(GURL("https://example.com/render"), observer.mapped_url());
 }
 
-// Test for perBuyerTimeouts being passed to runAdAuction as promises.
-IN_PROC_BROWSER_TEST_F(InterestGroupBrowserTest, PromiseBuyerTimeouts) {
+// Test for perBuyerTimeouts and perBuyerCumulativeTimeouts being passed to
+// runAdAuction as promises.
+IN_PROC_BROWSER_TEST_F(InterestGroupBrowserTest,
+                       PromiseBuyerTimeoutsAndCumulativeBuyerTimeouts) {
   // These scripts are generated by this test.
   constexpr char kBiddingLogicPath[] =
       "/interest_group/test_generated_bidding_argument_validator.js";
@@ -8438,22 +8549,21 @@
 function scoreAd(
     adMetadata, bid, auctionConfig, unusedTrustedScoringSignals,
     unusedBrowserSignals) {
-  validateAuctionConfig(auctionConfig);
+  validatePerBuyerTimeouts(auctionConfig.perBuyerTimeouts);
+  validatePerBuyerCumulativeTimeouts(auctionConfig.perBuyerCumulativeTimeouts);
   return bid;
 }
 
-function validateAuctionConfig(auctionConfig) {
-  const perBuyerTimeoutsJSON = JSON.stringify(auctionConfig.perBuyerTimeouts);
+function validatePerBuyerTimeouts(perBuyerTimeouts) {
+  const perBuyerTimeoutsJSON = JSON.stringify(perBuyerTimeouts);
   let ok = 0;
-  for (key in auctionConfig.perBuyerTimeouts) {
-    if (key.startsWith("https://a.test") &&
-        auctionConfig.perBuyerTimeouts[key] === 50) {
+  for (key in perBuyerTimeouts) {
+    if (key.startsWith("https://a.test") && perBuyerTimeouts[key] === 50) {
       ++ok;
-    } else if (key.startsWith("https://b.test") &&
-        auctionConfig.perBuyerTimeouts[key] === 60) {
+    } else if (key === "https://b.test" && perBuyerTimeouts[key] === 60) {
       ++ok;
     } else if (key === '*' &&
-        auctionConfig.perBuyerTimeouts[key] === 56) {
+        perBuyerTimeouts[key] === 56) {
       ++ok;
     } else {
       throw 'Wrong key in perBuyerTimeouts ' + perBuyerTimeoutsJSON;
@@ -8463,6 +8573,30 @@
     throw 'Wrong perBuyerTimeouts ' + perBuyerTimeoutsJSON;
   }
 }
+
+function validatePerBuyerCumulativeTimeouts(perBuyerCumulativeTimeouts) {
+  const perBuyerCumulativeTimeoutsJSON =
+      JSON.stringify(perBuyerCumulativeTimeouts);
+  let ok = 0;
+  for (key in perBuyerCumulativeTimeouts) {
+    if (key.startsWith("https://a.test") &&
+        perBuyerCumulativeTimeouts[key] === 70) {
+      ++ok;
+    } else if (key === "https://c.test" &&
+        perBuyerCumulativeTimeouts[key] === 80) {
+      ++ok;
+    } else if (key === '*' &&
+        perBuyerCumulativeTimeouts[key] === 76) {
+      ++ok;
+    } else {
+      throw 'Wrong key in perCumulativeBuyerTimeouts ' +
+          perBuyerCumulativeTimeoutsJSON;
+    }
+  }
+  if (ok !== 3) {
+    throw 'Wrong perCumulativeBuyerTimeouts ' + perBuyerCumulativeTimeoutsJSON;
+  }
+}
 )";
 
   network_responder_->RegisterNetworkResponse(
@@ -8505,6 +8639,10 @@
     perBuyerTimeouts: new Promise((resolve, reject) => {
       setTimeout(
           () => { resolve({$1: 50, 'https://b.test': 60, '*': 56}); }, 1)
+    }),
+    perBuyerCumulativeTimeouts: new Promise((resolve, reject) => {
+      setTimeout(
+          () => { resolve({$1: 70, 'https://c.test': 80, '*': 76}); }, 1)
     })
   });
 })())",
diff --git a/content/browser/renderer_host/navigation_controller_impl.cc b/content/browser/renderer_host/navigation_controller_impl.cc
index 1ee80fd..8c0177e 100644
--- a/content/browser/renderer_host/navigation_controller_impl.cc
+++ b/content/browser/renderer_host/navigation_controller_impl.cc
@@ -3253,6 +3253,32 @@
   // function.
   std::unique_ptr<PendingEntryRef> pending_entry_ref = ReferencePendingEntry();
 
+  // If there is a main-frame same-document history navigation, we may defer
+  // the subframe history navigations in order to give JS in the main frame the
+  // opportunity to cancel the entire traverse via the navigate event. In that
+  // case, we need to stash the main frame request's navigation token on the
+  // subframes, so they can look up the main frame request and defer themselves
+  // until it completes.
+  if (!same_document_loads.empty() &&
+      same_document_loads.at(0)->frame_tree_node()->IsMainFrame()) {
+    NavigationRequest* main_frame_request = same_document_loads.at(0).get();
+    // The token will only be returned in cases where deferring the navigation
+    // is necessary.
+    if (auto main_frame_same_document_token =
+            main_frame_request->GetNavigationTokenForDeferringSubframes()) {
+      for (auto& item : same_document_loads) {
+        if (item.get() != main_frame_request) {
+          item->set_main_frame_same_document_history_token(
+              main_frame_same_document_token);
+        }
+      }
+      for (auto& item : different_document_loads) {
+        item->set_main_frame_same_document_history_token(
+            main_frame_same_document_token);
+      }
+    }
+  }
+
   // Send all the same document frame loads before the different document loads.
   for (auto& item : same_document_loads) {
     FrameTreeNode* frame = item->frame_tree_node();
diff --git a/content/browser/renderer_host/navigation_request.cc b/content/browser/renderer_host/navigation_request.cc
index c7279d9..af5da1f4 100644
--- a/content/browser/renderer_host/navigation_request.cc
+++ b/content/browser/renderer_host/navigation_request.cc
@@ -1971,6 +1971,15 @@
               *pending_navigation_api_key_,
               blink::mojom::TraverseCancelledReason::kAbortedBeforeCommit);
     }
+
+    // If subframe history navigations were deferred waiting for this request,
+    // the cancelation of this request should cancel them, too.
+    for (auto& throttle : subframe_history_navigation_throttles_) {
+      if (throttle) {
+        throttle->Cancel();
+      }
+    }
+    subframe_history_navigation_throttles_.clear();
   }
 
   // If this NavigationRequest is the last one referencing the pending
@@ -6567,6 +6576,8 @@
     frame_tree_node()->SetCollapsed(false);
   }
 
+  UnblockPendingSubframeNavigationRequestsIfNeeded();
+
   if (service_worker_handle_) {
     // Notify the service worker navigation handle that the navigation finished
     // committing.
@@ -8436,6 +8447,47 @@
   }
 }
 
+absl::optional<base::UnguessableToken>
+NavigationRequest::GetNavigationTokenForDeferringSubframes() {
+  DCHECK(IsInMainFrame());
+  if (!base::FeatureList::IsEnabled(
+          blink::features::kNavigateEventCancelableTraversals)) {
+    return absl::nullopt;
+  }
+  if (!IsSameDocument() ||
+      !NavigationTypeUtils::IsHistory(common_params_->navigation_type)) {
+    return absl::nullopt;
+  }
+  RenderFrameHostImpl* current_frame_host =
+      frame_tree_node_->current_frame_host();
+  if (!current_frame_host->has_navigate_event_handler()) {
+    return absl::nullopt;
+  }
+  if (commit_params_->is_browser_initiated &&
+      !current_frame_host->IsHistoryUserActivationActive()) {
+    return absl::nullopt;
+  }
+  return commit_params_->navigation_token;
+}
+
+void NavigationRequest::AddDeferredSubframeNavigationThrottle(
+    base::WeakPtr<SubframeHistoryNavigationThrottle> throttle) {
+  DCHECK(IsInMainFrame());
+  subframe_history_navigation_throttles_.push_back(throttle);
+}
+
+void NavigationRequest::UnblockPendingSubframeNavigationRequestsIfNeeded() {
+  // After a main frame same-document history navigation completes successfully,
+  // we can resume any corresponding subframe history navigations that were
+  // blocked on it.
+  for (auto& throttle : subframe_history_navigation_throttles_) {
+    if (throttle) {
+      throttle->Resume();
+    }
+  }
+  subframe_history_navigation_throttles_.clear();
+}
+
 bool NavigationRequest::IsServedFromBackForwardCache() const {
   return is_back_forward_cache_restore_;
 }
diff --git a/content/browser/renderer_host/navigation_request.h b/content/browser/renderer_host/navigation_request.h
index 9b5228864..1d1b3e3 100644
--- a/content/browser/renderer_host/navigation_request.h
+++ b/content/browser/renderer_host/navigation_request.h
@@ -36,6 +36,7 @@
 #include "content/browser/renderer_host/render_frame_host_csp_context.h"
 #include "content/browser/renderer_host/render_frame_host_impl.h"
 #include "content/browser/renderer_host/should_swap_browsing_instance.h"
+#include "content/browser/renderer_host/subframe_history_navigation_throttle.h"
 #include "content/browser/site_instance_impl.h"
 #include "content/common/content_export.h"
 #include "content/common/navigation_client.mojom-forward.h"
@@ -1095,6 +1096,43 @@
     pending_navigation_api_key_ = key;
   }
 
+  // Returns the navigation token for this request if this NavigationRequest
+  // is for a history navigation, and it might be cancelled via the navigate
+  // event. In that case, any associated subframe history navigations will be
+  // deferred until this navigation commits.
+  // This may only be called if this is a main-frame same-document navigation.
+  // Will return a valid token if canceling traversals via the navigate event is
+  // enabled, this is a history navigation, a navigate event is registered in
+  // the renderer, and the frame has met the user activation requirements to be
+  // allowed to cancel the navigation.
+  // This token will then be provided to the subframe NavigationRequests via
+  // set_main_frame_same_document_history_token().
+  absl::optional<base::UnguessableToken>
+  GetNavigationTokenForDeferringSubframes();
+
+  // For subframe NavigationRequests, these set and return the main frame's
+  // NavigationRequest token, in the case that the main frame returns it from
+  // GetNavigationTokenForDeferringSubframes().
+  void set_main_frame_same_document_history_token(
+      absl::optional<base::UnguessableToken> token) {
+    main_frame_same_document_navigation_token_ = token;
+  }
+  absl::optional<base::UnguessableToken>
+  main_frame_same_document_history_token() {
+    return main_frame_same_document_navigation_token_;
+  }
+
+  // When a SubframeHistoryNavigationThrottle is created, it registers itself
+  // with the NavigationRequest for the main frame. If the main frame
+  // NavigationRequest commits, then these throttles will be resumed in
+  // UnblockPendingSubframeNavigationRequestsIfNeeded().
+  // If it is canceled instead, these throttles will all be canceled.
+  // Takes a WeakPtr because `throttle` is owned by another NavigationRequest,
+  // and that request may be canceled outside of our control, in which case
+  // `throttle` will be destroyed.
+  void AddDeferredSubframeNavigationThrottle(
+      base::WeakPtr<SubframeHistoryNavigationThrottle> throttle);
+
  private:
   friend class NavigationRequestTest;
 
@@ -1732,6 +1770,12 @@
   // See https://crbug.com/1412365
   void CheckSoftNavigationHeuristicsInvariants();
 
+  // Used to resume any SubframeHistoryNavigationThrottles in this FrameTree
+  // when this NavigationRequest commits.
+  // `subframe_history_navigation_throttles_` will only be populated if this
+  // IsInMainFrame().
+  void UnblockPendingSubframeNavigationRequestsIfNeeded();
+
   // Never null. The pointee node owns this navigation request instance.
   FrameTreeNode* const frame_tree_node_;
 
@@ -2353,6 +2397,25 @@
   // See `set_pending_navigation_api_key()` for context.
   absl::optional<std::string> pending_navigation_api_key_;
 
+  // If this NavigationRequest is for a main-frame same-document back/forward
+  // navigation, any subframe NavigationRequests are deferred until the renderer
+  // has a chance to fire a navigate event. If the navigate
+  // event allows the navigation to proceed,
+  // UnblockPendingSubframeNavigationRequestsIfNeeded() will resume these
+  // requests
+  std::vector<base::WeakPtr<SubframeHistoryNavigationThrottle>>
+      subframe_history_navigation_throttles_;
+
+  // If this NavigationRequest is in a subframe and part of a history traversal,
+  // and the main frame is performing a same-document navigation, this token
+  // may be set if there is a possibility that JS in the main frame will cancel
+  // the history traversal via the navigate event. In that case, this token is
+  // used to look up the main frame's NavigationRequest so that it can be passed
+  // SubframeHistoryNavigationThrottle can defer this request until the main
+  // frame commits.
+  absl::optional<base::UnguessableToken>
+      main_frame_same_document_navigation_token_;
+
   base::WeakPtrFactory<NavigationRequest> weak_factory_{this};
 };
 
diff --git a/content/browser/renderer_host/navigation_throttle_runner.cc b/content/browser/renderer_host/navigation_throttle_runner.cc
index bf471013..8cfee57 100644
--- a/content/browser/renderer_host/navigation_throttle_runner.cc
+++ b/content/browser/renderer_host/navigation_throttle_runner.cc
@@ -197,6 +197,14 @@
   // to the end to not delay running other throttles.
   AddThrottle(RendererCancellationThrottle::MaybeCreateThrottleFor(request));
 
+  // Defer any cross-document subframe history navigations if there is an
+  // associated main-frame same-document history navigation in progress, until
+  // the main frame has had an opportunity to fire a navigate event in the
+  // renderer. If the navigate event cancels the history navigation, the
+  // subframe navigations should not proceed.
+  AddThrottle(
+      SubframeHistoryNavigationThrottle::MaybeCreateThrottleFor(request));
+
   // Insert all testing NavigationThrottles last.
   throttles_.insert(throttles_.end(),
                     std::make_move_iterator(testing_throttles.begin()),
@@ -219,10 +227,15 @@
   // Unit tests that do not use NavigationRequest should never call
   // RegisterNavigationThrottlesForCommitWithoutUrlLoader as this function
   // expects |delegate_| to be a NavigationRequest.
-  //
-  // TODO(japhet): Uncomment this once there are throttles for commits without
-  // a URL loader.
-  // NavigationRequest* request = static_cast<NavigationRequest*>(delegate_);
+  NavigationRequest* request = static_cast<NavigationRequest*>(delegate_);
+
+  // Defer any same-document subframe history navigations if there is an
+  // associated main-frame same-document history navigation in progress, until
+  // the main frame has had an opportunity to fire a navigate event in the
+  // renderer. If the navigate event cancels the history navigation, the
+  // subframe navigations should not proceed.
+  AddThrottle(
+      SubframeHistoryNavigationThrottle::MaybeCreateThrottleFor(request));
 
   // Insert all testing NavigationThrottles last.
   throttles_.insert(throttles_.end(),
diff --git a/content/browser/renderer_host/subframe_history_navigation_throttle.cc b/content/browser/renderer_host/subframe_history_navigation_throttle.cc
new file mode 100644
index 0000000..fc5881d
--- /dev/null
+++ b/content/browser/renderer_host/subframe_history_navigation_throttle.cc
@@ -0,0 +1,73 @@
+// 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 "content/browser/renderer_host/subframe_history_navigation_throttle.h"
+
+#include "content/browser/renderer_host/frame_tree.h"
+#include "content/browser/renderer_host/frame_tree_node.h"
+#include "content/browser/renderer_host/navigation_request.h"
+#include "content/browser/renderer_host/render_frame_host_impl.h"
+#include "content/public/browser/navigation_handle.h"
+
+namespace content {
+
+SubframeHistoryNavigationThrottle::SubframeHistoryNavigationThrottle(
+    NavigationHandle* navigation_handle)
+    : NavigationThrottle(navigation_handle) {}
+
+SubframeHistoryNavigationThrottle::~SubframeHistoryNavigationThrottle() =
+    default;
+
+NavigationThrottle::ThrottleCheckResult
+SubframeHistoryNavigationThrottle::WillStartRequest() {
+  // This will defer cross-document subframe history requests.
+  return DEFER;
+}
+
+NavigationThrottle::ThrottleCheckResult
+SubframeHistoryNavigationThrottle::WillCommitWithoutUrlLoader() {
+  // This will defer same-document subframe history commits.
+  return DEFER;
+}
+
+const char* SubframeHistoryNavigationThrottle::GetNameForLogging() {
+  return "SubframeHistoryNavigationThrottle";
+}
+
+void SubframeHistoryNavigationThrottle::Resume() {
+  NavigationThrottle::Resume();
+}
+
+void SubframeHistoryNavigationThrottle::Cancel() {
+  CancelDeferredNavigation(CANCEL_AND_IGNORE);
+}
+
+// static
+std::unique_ptr<NavigationThrottle>
+SubframeHistoryNavigationThrottle::MaybeCreateThrottleFor(
+    NavigationHandle* navigation_handle) {
+  // `main_frame_same_document_history_token()` will only be set if the
+  // navigation is a main-frame same-document history navigation and it might
+  // be cancelable by a navigate event. There's no need to defer the subframe
+  // navigation unless the navigate event might cancel the entire history
+  // traversal.
+  NavigationRequest* request = NavigationRequest::From(navigation_handle);
+  auto main_frame_token = request->main_frame_same_document_history_token();
+  if (!main_frame_token) {
+    return nullptr;
+  }
+  DCHECK(!request->IsInMainFrame());
+
+  RenderFrameHostImpl* root_frame_host =
+      request->frame_tree_node()->frame_tree().root()->current_frame_host();
+  NavigationRequest* root_frame_navigation_request =
+      root_frame_host->GetSameDocumentNavigationRequest(*main_frame_token);
+  DCHECK(root_frame_navigation_request);
+  auto throttle = std::make_unique<SubframeHistoryNavigationThrottle>(request);
+  root_frame_navigation_request->AddDeferredSubframeNavigationThrottle(
+      throttle->weak_factory_.GetWeakPtr());
+  return throttle;
+}
+
+}  // namespace content
diff --git a/content/browser/renderer_host/subframe_history_navigation_throttle.h b/content/browser/renderer_host/subframe_history_navigation_throttle.h
new file mode 100644
index 0000000..6bbd509
--- /dev/null
+++ b/content/browser/renderer_host/subframe_history_navigation_throttle.h
@@ -0,0 +1,46 @@
+// 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 CONTENT_BROWSER_RENDERER_HOST_SUBFRAME_HISTORY_NAVIGATION_THROTTLE_H_
+#define CONTENT_BROWSER_RENDERER_HOST_SUBFRAME_HISTORY_NAVIGATION_THROTTLE_H_
+
+#include <memory>
+
+#include "base/memory/weak_ptr.h"
+#include "content/public/browser/navigation_throttle.h"
+
+namespace content {
+
+// Defers subframe history navigations while waiting for a main-frame
+// same-document history navigation. If a main-frame same-document navigation is
+// cancelled by the web-exposed Navigation API, all associated subframe history
+// navigations should also be cancelled.
+class SubframeHistoryNavigationThrottle final : public NavigationThrottle {
+ public:
+  explicit SubframeHistoryNavigationThrottle(
+      NavigationHandle* navigation_handle);
+  SubframeHistoryNavigationThrottle(const SubframeHistoryNavigationThrottle&) =
+      delete;
+  SubframeHistoryNavigationThrottle& operator=(
+      const SubframeHistoryNavigationThrottle&) = delete;
+  ~SubframeHistoryNavigationThrottle() final;
+
+  // NavigationThrottle method:
+  ThrottleCheckResult WillStartRequest() final;
+  ThrottleCheckResult WillCommitWithoutUrlLoader() final;
+  const char* GetNameForLogging() final;
+  void Resume() final;
+
+  void Cancel();
+
+  static std::unique_ptr<NavigationThrottle> MaybeCreateThrottleFor(
+      NavigationHandle* navigation_handle);
+
+ private:
+  base::WeakPtrFactory<SubframeHistoryNavigationThrottle> weak_factory_{this};
+};
+
+}  // namespace content
+
+#endif  // CONTENT_BROWSER_RENDERER_HOST_SUBFRAME_HISTORY_NAVIGATION_THROTTLE_H_
diff --git a/content/browser/shared_storage/shared_storage_browsertest.cc b/content/browser/shared_storage/shared_storage_browsertest.cc
index 7798aeb4..db96c1f 100644
--- a/content/browser/shared_storage/shared_storage_browsertest.cc
+++ b/content/browser/shared_storage/shared_storage_browsertest.cc
@@ -8,6 +8,7 @@
 #include <tuple>
 #include <vector>
 
+#include "base/functional/overloaded.h"
 #include "base/memory/raw_ptr.h"
 #include "base/metrics/statistics_recorder.h"
 #include "base/strings/strcat.h"
@@ -18,6 +19,7 @@
 #include "base/test/scoped_feature_list.h"
 #include "base/test/simple_test_clock.h"
 #include "base/test/test_future.h"
+#include "base/test/with_feature_override.h"
 #include "content/browser/private_aggregation/private_aggregation_manager_impl.h"
 #include "content/browser/private_aggregation/private_aggregation_test_utils.h"
 #include "content/browser/renderer_host/navigation_request.h"
@@ -38,9 +40,9 @@
 #include "content/public/test/content_browser_test.h"
 #include "content/public/test/content_browser_test_content_browser_client.h"
 #include "content/public/test/content_browser_test_utils.h"
-#include "content/public/test/fenced_frame_test_util.h"
 #include "content/public/test/test_frame_navigation_observer.h"
 #include "content/public/test/test_navigation_observer.h"
+#include "content/public/test/test_select_url_fenced_frame_config_observer.h"
 #include "content/public/test/test_utils.h"
 #include "content/shell/browser/shell.h"
 #include "content/test/content_browser_test_utils_internal.h"
@@ -52,6 +54,7 @@
 #include "testing/gtest/include/gtest/gtest.h"
 #include "third_party/abseil-cpp/absl/numeric/int128.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
+#include "third_party/abseil-cpp/absl/types/variant.h"
 #include "third_party/blink/public/common/features.h"
 #include "third_party/blink/public/common/fenced_frame/fenced_frame_utils.h"
 #include "third_party/blink/public/common/shared_storage/shared_storage_utils.h"
@@ -99,35 +102,20 @@
 
 const int kReportEventBitBudget = 6;
 
-const char kSelectFrom8URLsScript[] = R"(
-    let urls = [];
-    for (let i = 0; i < 8; ++i) {
-      urls.push({url: '/fenced_frames/title' + i.toString() + '.html',
-                 reportingMetadata: {
-                   'click': '/fenced_frames/report' + i.toString() + '.html',
-                   'mouse interaction':
-                   '/fenced_frames/report' + (i + 1).toString() + '.html'
-                 }});
-    }
-
-    sharedStorage.selectURL(
-        'test-url-selection-operation', urls, {data: {'mockResult': 1}});
-  )";
-
-const char kSelectFrom4URLsScript[] = R"(
-    let urls = [];
-    for (let i = 0; i < 4; ++i) {
-      urls.push({url: '/fenced_frames/title' + i.toString() + '.html',
-                 reportingMetadata: {
-                   'click': '/fenced_frames/report' + i.toString() + '.html',
-                   'mouse interaction':
-                   '/fenced_frames/report' + (i + 1).toString() + '.html'
-                 }});
-    }
-
-    sharedStorage.selectURL(
-        'test-url-selection-operation', urls, {data: {'mockResult': 1}});
-  )";
+const char kGenerateURLsListScript[] = R"(
+  function generateUrls(size) {
+    return new Array(size).fill(0).map((e, i) => {
+      return {
+        url: '/fenced_frames/title' + i.toString() + '.html',
+        reportingMetadata: {
+          'click': '/fenced_frames/report' + i.toString() + '.html',
+          'mouse interaction':
+            '/fenced_frames/report' + (i + 1).toString() + '.html'
+        }
+      }
+    });
+  }
+)";
 
 const char kRemainingBudgetPrefix[] = "remaining budget: ";
 
@@ -213,6 +201,18 @@
   return base::StrCat(urls_str_vector);
 }
 
+bool IsErrorMessage(const content::WebContentsConsoleObserver::Message& msg) {
+  return msg.log_level == blink::mojom::ConsoleMessageLevel::kError;
+}
+
+auto describe_param = [](const auto& info) {
+  if (info.param) {
+    return "ResolveSelectURLToConfig";
+  } else {
+    return "ResolveSelectURLToURN";
+  }
+};
+
 }  // namespace
 
 class TestSharedStorageWorkletHost : public SharedStorageWorkletHost {
@@ -421,6 +421,11 @@
     accesses_.emplace_back(type, main_frame_id, owner_origin, params);
   }
 
+  void OnUrnUuidGenerated(const GURL& urn_uuid) override {}
+
+  void OnConfigPopulated(
+      const absl::optional<FencedFrameConfig>& config) override {}
+
   bool EventParamsMatch(const SharedStorageEventParams& expected_params,
                         const SharedStorageEventParams& actual_params) {
     if (expected_params.script_source_url != actual_params.script_source_url) {
@@ -614,24 +619,21 @@
   bool should_defer_worklet_messages_ = false;
 };
 
-class SharedStorageBrowserTest : public ContentBrowserTest {
+class SharedStorageBrowserTestBase : public ContentBrowserTest {
  public:
   using AccessType = TestSharedStorageObserver::AccessType;
 
-  SharedStorageBrowserTest() {
-    scoped_feature_list_
-        .InitWithFeaturesAndParameters(/*enabled_features=*/
-                                       {{blink::features::kSharedStorageAPI,
-                                         {{"SharedStorageBitBudget",
-                                           base::NumberToString(
-                                               kBudgetAllowed)},
-                                          {"SharedStorageStalenessThreshold",
-                                           TimeDeltaToString(base::Days(
-                                               kStalenessThresholdDays))}}},
-                                        {features::
-                                             kPrivacySandboxAdsAPIsOverride,
-                                         {}}},
-                                       /*disabled_features=*/{});
+  SharedStorageBrowserTestBase() {
+    scoped_feature_list_.InitWithFeaturesAndParameters(
+        /*enabled_features=*/
+        {{blink::features::kSharedStorageAPI,
+          {
+              {"SharedStorageBitBudget", base::NumberToString(kBudgetAllowed)},
+              {"SharedStorageStalenessThreshold",
+               TimeDeltaToString(base::Days(kStalenessThresholdDays))},
+          }},
+         {features::kPrivacySandboxAdsAPIsOverride, {}}},
+        /*disabled_features=*/{});
   }
 
   void SetUpOnMainThread() override {
@@ -642,10 +644,7 @@
     test_worklet_host_manager->AddSharedStorageObserver(observer_.get());
     test_worklet_host_manager_ = test_worklet_host_manager.get();
 
-    static_cast<StoragePartitionImpl*>(shell()
-                                           ->web_contents()
-                                           ->GetBrowserContext()
-                                           ->GetDefaultStoragePartition())
+    static_cast<StoragePartitionImpl*>(GetStoragePartition())
         ->OverrideSharedStorageWorkletHostManagerForTesting(
             std::move(test_worklet_host_manager));
 
@@ -653,6 +652,15 @@
     FinishSetup();
   }
 
+  virtual bool ResolveSelectURLToConfig() { return false; }
+
+  StoragePartition* GetStoragePartition() {
+    return shell()
+        ->web_contents()
+        ->GetBrowserContext()
+        ->GetDefaultStoragePartition();
+  }
+
   void TearDownOnMainThread() override {
     test_worklet_host_manager_->RemoveSharedStorageObserver(observer_.get());
   }
@@ -673,10 +681,7 @@
 
   double GetRemainingBudget(const url::Origin& origin) {
     base::test::TestFuture<SharedStorageWorkletHost::BudgetResult> future;
-    static_cast<StoragePartitionImpl*>(shell()
-                                           ->web_contents()
-                                           ->GetBrowserContext()
-                                           ->GetDefaultStoragePartition())
+    static_cast<StoragePartitionImpl*>(GetStoragePartition())
         ->GetSharedStorageManager()
         ->GetRemainingBudget(origin, future.GetCallback());
     return future.Take().bits;
@@ -733,8 +738,7 @@
     DCHECK(out_module_script_url);
 
     base::StringPairs run_function_body_replacement;
-    run_function_body_replacement.push_back(
-        std::make_pair("{{RUN_FUNCTION_BODY}}", script));
+    run_function_body_replacement.emplace_back("{{RUN_FUNCTION_BODY}}", script);
 
     std::string host =
         execution_target.render_frame_host()->GetLastCommittedOrigin().host();
@@ -771,11 +775,9 @@
     EXPECT_EQ(initial_child_count + 1, root->child_count());
     FrameTreeNode* child_node = root->child_at(initial_child_count);
 
-    std::string navigate_frame_script = JsReplace("f.src = $1;", url.spec());
+    TestFrameNavigationObserver observer(child_node);
 
-    TestFrameNavigationObserver observer(child_node->current_frame_host());
-
-    EXPECT_EQ(url.spec(), EvalJs(root, navigate_frame_script));
+    EXPECT_EQ(url.spec(), EvalJs(root, JsReplace("f.src = $1;", url)));
 
     observer.Wait();
 
@@ -802,15 +804,49 @@
         sharedStorage.worklet.addModule('/shared_storage/simple_module.js');
       )"));
 
-    std::string urn_uuid =
-        EvalJs(iframe, kSelectFrom8URLsScript).ExtractString();
+    // Generate 8 candidates urls in to a list variable `urls`.
+    EXPECT_TRUE(ExecJs(iframe, kGenerateURLsListScript));
+    EXPECT_TRUE(
+        ExecJs(iframe, JsReplace("window.resolveSelectURLToConfig = $1;",
+                                 ResolveSelectURLToConfig())));
+
+    TestSelectURLFencedFrameConfigObserver config_observer(
+        GetStoragePartition());
+    EvalJsResult result = EvalJs(iframe, R"(
+        (async function() {
+          const urls = generateUrls(8);
+          window.select_url_result = await sharedStorage.selectURL(
+            'test-url-selection-operation',
+            urls,
+            {
+              data: {'mockResult': 1},
+              resolveToConfig: resolveSelectURLToConfig
+            }
+          );
+          if (resolveSelectURLToConfig &&
+              !(select_url_result instanceof FencedFrameConfig)) {
+            throw new Error('selectURL() did not return a FencedFrameConfig.');
+          }
+          return window.select_url_result;
+        })()
+      )");
+
+    EXPECT_TRUE(result.error.empty());
+    const absl::optional<GURL>& observed_urn_uuid =
+        config_observer.GetUrnUuid();
+    EXPECT_TRUE(observed_urn_uuid.has_value());
+    EXPECT_TRUE(blink::IsValidUrnUuidURL(observed_urn_uuid.value()));
+
+    if (!ResolveSelectURLToConfig()) {
+      EXPECT_EQ(result.ExtractString(), observed_urn_uuid->spec());
+    }
 
     // There are 2 "worklet operations": `addModule()` and `selectURL()`.
     test_worklet_host_manager()
         .GetAttachedWorkletHostForFrame(iframe->current_frame_host())
         ->WaitForWorkletResponsesCount(2);
 
-    return GURL(urn_uuid);
+    return observed_urn_uuid.value();
   }
 
   // Prerequisite: The worklet for `frame` has registered a
@@ -874,7 +910,7 @@
     return *test_worklet_host_manager_;
   }
 
-  ~SharedStorageBrowserTest() override = default;
+  ~SharedStorageBrowserTestBase() override = default;
 
  protected:
   base::test::ScopedFeatureList scoped_feature_list_;
@@ -886,7 +922,24 @@
   std::unique_ptr<TestSharedStorageObserver> observer_;
 };
 
-IN_PROC_BROWSER_TEST_F(SharedStorageBrowserTest, AddModule_Success) {
+class SharedStorageBrowserTest : public base::test::WithFeatureOverride,
+                                 public SharedStorageBrowserTestBase {
+ public:
+  SharedStorageBrowserTest()
+      : base::test::WithFeatureOverride(
+            blink::features::kFencedFramesAPIChanges) {
+    scoped_feature_list_.InitAndEnableFeature(blink::features::kFencedFrames);
+  }
+
+  bool ResolveSelectURLToConfig() override { return IsParamFeatureEnabled(); }
+
+  ~SharedStorageBrowserTest() override = default;
+
+ private:
+  base::test::ScopedFeatureList scoped_feature_list_;
+};
+
+IN_PROC_BROWSER_TEST_P(SharedStorageBrowserTest, AddModule_Success) {
   // The test assumes pages get deleted after navigation. To ensure this,
   // disable back/forward cache.
   content::DisableBackForwardCacheForTesting(
@@ -927,7 +980,7 @@
             "a.test", "/shared_storage/simple_module.js"))}});
 }
 
-IN_PROC_BROWSER_TEST_F(SharedStorageBrowserTest, AddModule_ScriptNotFound) {
+IN_PROC_BROWSER_TEST_P(SharedStorageBrowserTest, AddModule_ScriptNotFound) {
   GURL url = https_server()->GetURL("a.test", kSimplePagePath);
   EXPECT_TRUE(NavigateToURL(shell(), url));
 
@@ -957,7 +1010,7 @@
             "a.test", "/shared_storage/nonexistent_module.js"))}});
 }
 
-IN_PROC_BROWSER_TEST_F(SharedStorageBrowserTest, AddModule_RedirectNotAllowed) {
+IN_PROC_BROWSER_TEST_P(SharedStorageBrowserTest, AddModule_RedirectNotAllowed) {
   GURL url = https_server()->GetURL("a.test", kSimplePagePath);
   EXPECT_TRUE(NavigateToURL(shell(), url));
 
@@ -989,7 +1042,7 @@
             "a.test", "/server-redirect?shared_storage/simple_module.js"))}});
 }
 
-IN_PROC_BROWSER_TEST_F(SharedStorageBrowserTest,
+IN_PROC_BROWSER_TEST_P(SharedStorageBrowserTest,
                        AddModule_ScriptExecutionFailure) {
   GURL url = https_server()->GetURL("a.test", kSimplePagePath);
   EXPECT_TRUE(NavigateToURL(shell(), url));
@@ -1022,7 +1075,7 @@
             "a.test", "/shared_storage/erroneous_module.js"))}});
 }
 
-IN_PROC_BROWSER_TEST_F(SharedStorageBrowserTest,
+IN_PROC_BROWSER_TEST_P(SharedStorageBrowserTest,
                        AddModule_MultipleAddModuleFailure) {
   // The test assumes pages get deleted after navigation. To ensure this,
   // disable back/forward cache.
@@ -1076,7 +1129,7 @@
             "a.test", "/shared_storage/simple_module.js"))}});
 }
 
-IN_PROC_BROWSER_TEST_F(SharedStorageBrowserTest, RunOperation_Success) {
+IN_PROC_BROWSER_TEST_P(SharedStorageBrowserTest, RunOperation_Success) {
   GURL url = https_server()->GetURL("a.test", kSimplePagePath);
   EXPECT_TRUE(NavigateToURL(shell(), url));
 
@@ -1125,7 +1178,7 @@
                                                std::vector<uint8_t>())}});
 }
 
-IN_PROC_BROWSER_TEST_F(SharedStorageBrowserTest,
+IN_PROC_BROWSER_TEST_P(SharedStorageBrowserTest,
                        RunOperation_Failure_RunOperationBeforeAddModule) {
   // The test assumes pages get deleted after navigation. To ensure this,
   // disable back/forward cache.
@@ -1192,7 +1245,7 @@
             "a.test", "/shared_storage/simple_module.js"))}});
 }
 
-IN_PROC_BROWSER_TEST_F(SharedStorageBrowserTest,
+IN_PROC_BROWSER_TEST_P(SharedStorageBrowserTest,
                        RunOperation_Failure_InvalidOptionsArgument) {
   GURL url = https_server()->GetURL("a.test", kSimplePagePath);
   EXPECT_TRUE(NavigateToURL(shell(), url));
@@ -1225,7 +1278,7 @@
             "a.test", "/shared_storage/simple_module.js"))}});
 }
 
-IN_PROC_BROWSER_TEST_F(SharedStorageBrowserTest,
+IN_PROC_BROWSER_TEST_P(SharedStorageBrowserTest,
                        RunOperation_Failure_ErrorInRunOperation) {
   GURL url = https_server()->GetURL("a.test", kSimplePagePath);
   EXPECT_TRUE(NavigateToURL(shell(), url));
@@ -1282,7 +1335,7 @@
                                                std::vector<uint8_t>())}});
 }
 
-IN_PROC_BROWSER_TEST_F(SharedStorageBrowserTest, WorkletDestroyed) {
+IN_PROC_BROWSER_TEST_P(SharedStorageBrowserTest, WorkletDestroyed) {
   // The test assumes pages get deleted after navigation. To ensure this,
   // disable back/forward cache.
   content::DisableBackForwardCacheForTesting(
@@ -1319,7 +1372,7 @@
             "a.test", "/shared_storage/simple_module.js"))}});
 }
 
-IN_PROC_BROWSER_TEST_F(SharedStorageBrowserTest, TwoWorklets) {
+IN_PROC_BROWSER_TEST_P(SharedStorageBrowserTest, TwoWorklets) {
   // The test assumes pages get deleted after navigation. To ensure this,
   // disable back/forward cache.
   content::DisableBackForwardCacheForTesting(
@@ -1382,7 +1435,7 @@
             "a.test", "/shared_storage/simple_module.js"))}});
 }
 
-IN_PROC_BROWSER_TEST_F(
+IN_PROC_BROWSER_TEST_P(
     SharedStorageBrowserTest,
     KeepAlive_StartBeforeAddModuleComplete_EndAfterAddModuleComplete) {
   // The test assumes pages get deleted after navigation. To ensure this,
@@ -1450,7 +1503,7 @@
             "a.test", "/shared_storage/simple_module.js"))}});
 }
 
-IN_PROC_BROWSER_TEST_F(SharedStorageBrowserTest,
+IN_PROC_BROWSER_TEST_P(SharedStorageBrowserTest,
                        KeepAlive_StartBeforeAddModuleComplete_EndAfterTimeout) {
   // The test assumes pages get deleted after navigation. To ensure this,
   // disable back/forward cache.
@@ -1512,7 +1565,7 @@
             "a.test", "/shared_storage/simple_module.js"))}});
 }
 
-IN_PROC_BROWSER_TEST_F(
+IN_PROC_BROWSER_TEST_P(
     SharedStorageBrowserTest,
     KeepAlive_StartBeforeRunOperationComplete_EndAfterRunOperationComplete) {
   // The test assumes pages get deleted after navigation. To ensure this,
@@ -1594,7 +1647,7 @@
                                                std::vector<uint8_t>())}});
 }
 
-IN_PROC_BROWSER_TEST_F(SharedStorageBrowserTest, KeepAlive_SubframeWorklet) {
+IN_PROC_BROWSER_TEST_P(SharedStorageBrowserTest, KeepAlive_SubframeWorklet) {
   // The test assumes pages get deleted after navigation. To ensure this,
   // disable back/forward cache.
   content::DisableBackForwardCacheForTesting(
@@ -1688,7 +1741,7 @@
             "a.test", "/shared_storage/simple_module2.js"))}});
 }
 
-IN_PROC_BROWSER_TEST_F(SharedStorageBrowserTest,
+IN_PROC_BROWSER_TEST_P(SharedStorageBrowserTest,
                        RenderProcessHostDestroyedDuringWorkletKeepAlive) {
   // The test assumes pages gets deleted after navigation, letting the worklet
   // enter keep-alive phase. To ensure this, disable back/forward cache.
@@ -1722,7 +1775,7 @@
 
 // Test that there's no need to charge budget if the input urls' size is 1.
 // This specifically tests the operation success scenario.
-IN_PROC_BROWSER_TEST_F(
+IN_PROC_BROWSER_TEST_P(
     SharedStorageBrowserTest,
     SelectURL_BudgetMetadata_OperationSuccess_SingleInputURL) {
   GURL main_url = https_server()->GetURL("a.test", kSimplePagePath);
@@ -1733,31 +1786,63 @@
   EXPECT_TRUE(ExecJs(shell(), R"(
       sharedStorage.worklet.addModule('shared_storage/simple_module.js');
     )"));
+  EXPECT_TRUE(ExecJs(shell(), JsReplace("window.resolveSelectURLToConfig = $1;",
+                                        ResolveSelectURLToConfig())));
 
-  std::string urn_uuid = EvalJs(shell(), R"(
-      sharedStorage.selectURL(
+  TestSelectURLFencedFrameConfigObserver config_observer(GetStoragePartition());
+  EvalJsResult result = EvalJs(shell(), R"(
+      (async function() {
+        window.select_url_result = await sharedStorage.selectURL(
           'test-url-selection-operation',
-          [{url: "fenced_frames/title0.html",
-          reportingMetadata: {"click": "fenced_frames/report1.html",
-              "mouse interaction": "fenced_frames/report2.html"}}],
-          {data: {'mockResult':0}});
-    )")
-                             .ExtractString();
+          [
+            {
+              url: "fenced_frames/title0.html",
+              reportingMetadata: {
+                "click": "fenced_frames/report1.html",
+                "mouse interaction": "fenced_frames/report2.html"
+              }
+            }
+          ],
+          {
+            data: {'mockResult': 0},
+            resolveToConfig: resolveSelectURLToConfig
+          }
+        );
+        if (resolveSelectURLToConfig &&
+            !(select_url_result instanceof FencedFrameConfig)) {
+          throw new Error('selectURL() did not return a FencedFrameConfig.');
+        }
+        return window.select_url_result;
+      })()
+    )");
 
-  EXPECT_TRUE(blink::IsValidUrnUuidURL(GURL(urn_uuid)));
+  EXPECT_TRUE(result.error.empty());
+  const absl::optional<GURL>& observed_urn_uuid = config_observer.GetUrnUuid();
+  EXPECT_TRUE(observed_urn_uuid.has_value());
+  EXPECT_TRUE(blink::IsValidUrnUuidURL(observed_urn_uuid.value()));
+
+  if (!ResolveSelectURLToConfig()) {
+    EXPECT_EQ(result.ExtractString(), observed_urn_uuid->spec());
+  }
 
   // There are 2 "worklet operations": `addModule()` and `selectURL()`.
   test_worklet_host_manager()
       .GetAttachedWorkletHost()
       ->WaitForWorkletResponsesCount(2);
 
+  ASSERT_TRUE(config_observer.ConfigObserved());
+  const absl::optional<FencedFrameConfig>& fenced_frame_config =
+      config_observer.GetConfig();
+  EXPECT_TRUE(fenced_frame_config.has_value());
+  EXPECT_EQ(fenced_frame_config->urn_uuid_, observed_urn_uuid.value());
+
   SharedStorageBudgetMetadata* metadata =
-      GetSharedStorageBudgetMetadata(GURL(urn_uuid));
+      GetSharedStorageBudgetMetadata(observed_urn_uuid.value());
   EXPECT_TRUE(metadata);
   EXPECT_EQ(metadata->origin, https_server()->GetOrigin("a.test"));
   EXPECT_DOUBLE_EQ(metadata->budget_to_charge, 0.0);
 
-  EXPECT_THAT(GetSharedStorageReportingMap(GURL(urn_uuid)),
+  EXPECT_THAT(GetSharedStorageReportingMap(observed_urn_uuid.value()),
               UnorderedElementsAre(
                   Pair("click", https_server()->GetURL(
                                     "a.test", "/fenced_frames/report1.html")),
@@ -1795,7 +1880,7 @@
 
 // Test that there's no need to charge budget if the input urls' size is 1.
 // This specifically tests the operation failure scenario.
-IN_PROC_BROWSER_TEST_F(
+IN_PROC_BROWSER_TEST_P(
     SharedStorageBrowserTest,
     SelectURL_BudgetMetadata_OperationFailure_SingleInputURL) {
   GURL main_url = https_server()->GetURL("a.test", kSimplePagePath);
@@ -1806,30 +1891,62 @@
   EXPECT_TRUE(ExecJs(shell(), R"(
       sharedStorage.worklet.addModule('shared_storage/simple_module.js');
     )"));
+  EXPECT_TRUE(ExecJs(shell(), JsReplace("window.resolveSelectURLToConfig = $1;",
+                                        ResolveSelectURLToConfig())));
 
-  std::string urn_uuid = EvalJs(shell(), R"(
-      sharedStorage.selectURL(
+  TestSelectURLFencedFrameConfigObserver config_observer(GetStoragePartition());
+  EvalJsResult result = EvalJs(shell(), R"(
+      (async function() {
+        window.select_url_result = await sharedStorage.selectURL(
           'test-url-selection-operation',
-          [{url: "fenced_frames/title0.html",
-          reportingMetadata: {"click": "fenced_frames/report1.html"}}],
-          {data: {'mockResult':-1}});
-    )")
-                             .ExtractString();
+          [
+            {
+              url: "fenced_frames/title0.html",
+              reportingMetadata: {
+                "click": "fenced_frames/report1.html"
+              }
+            }
+          ],
+          {
+            data: {'mockResult': -1},
+            resolveToConfig: resolveSelectURLToConfig
+          }
+        );
+        if (resolveSelectURLToConfig &&
+            !(select_url_result instanceof FencedFrameConfig)) {
+          throw new Error('selectURL() did not return a FencedFrameConfig.');
+        }
+        return window.select_url_result;
+      })()
+    )");
 
-  EXPECT_TRUE(blink::IsValidUrnUuidURL(GURL(urn_uuid)));
+  EXPECT_TRUE(result.error.empty());
+  const absl::optional<GURL>& observed_urn_uuid = config_observer.GetUrnUuid();
+  EXPECT_TRUE(observed_urn_uuid.has_value());
+  EXPECT_TRUE(blink::IsValidUrnUuidURL(observed_urn_uuid.value()));
+
+  if (!ResolveSelectURLToConfig()) {
+    EXPECT_EQ(result.ExtractString(), observed_urn_uuid->spec());
+  }
 
   // There are 2 "worklet operations": `addModule()` and `selectURL()`.
   test_worklet_host_manager()
       .GetAttachedWorkletHost()
       ->WaitForWorkletResponsesCount(2);
 
+  ASSERT_TRUE(config_observer.ConfigObserved());
+  const absl::optional<FencedFrameConfig>& fenced_frame_config =
+      config_observer.GetConfig();
+  EXPECT_TRUE(fenced_frame_config.has_value());
+  EXPECT_EQ(fenced_frame_config->urn_uuid_, observed_urn_uuid.value());
+
   SharedStorageBudgetMetadata* metadata =
-      GetSharedStorageBudgetMetadata(GURL(urn_uuid));
+      GetSharedStorageBudgetMetadata(observed_urn_uuid.value());
   EXPECT_TRUE(metadata);
   EXPECT_EQ(metadata->origin, https_server()->GetOrigin("a.test"));
   EXPECT_DOUBLE_EQ(metadata->budget_to_charge, 0.0);
 
-  EXPECT_THAT(GetSharedStorageReportingMap(GURL(urn_uuid)),
+  EXPECT_THAT(GetSharedStorageReportingMap(observed_urn_uuid.value()),
               UnorderedElementsAre(
                   Pair("click", https_server()->GetURL(
                                     "a.test", "/fenced_frames/report1.html"))));
@@ -1859,7 +1976,7 @@
                         .spec()}}}}))}});
 }
 
-IN_PROC_BROWSER_TEST_F(SharedStorageBrowserTest,
+IN_PROC_BROWSER_TEST_P(SharedStorageBrowserTest,
                        SelectURL_BudgetMetadata_Origin) {
   EXPECT_TRUE(NavigateToURL(
       shell(), https_server()->GetURL("a.test", kPageWithBlankIframePath)));
@@ -1877,31 +1994,69 @@
   EXPECT_EQ(1u, test_worklet_host_manager().GetAttachedWorkletHostsCount());
   EXPECT_EQ(0u, test_worklet_host_manager().GetKeepAliveWorkletHostsCount());
 
-  std::string urn_uuid = EvalJs(iframe, R"(
-      sharedStorage.selectURL(
-          'test-url-selection-operation',
-          [{url: "fenced_frames/title0.html"},
-          {url: "fenced_frames/title1.html",
-          reportingMetadata: {"click": "fenced_frames/report1.html"}},
-          {url: "fenced_frames/title2.html"}], {data: {'mockResult': 1}});
-    )")
-                             .ExtractString();
+  EXPECT_TRUE(ExecJs(iframe, JsReplace("window.resolveSelectURLToConfig = $1;",
+                                       ResolveSelectURLToConfig())));
 
-  EXPECT_TRUE(blink::IsValidUrnUuidURL(GURL(urn_uuid)));
+  TestSelectURLFencedFrameConfigObserver config_observer(GetStoragePartition());
+  EvalJsResult result = EvalJs(iframe, R"(
+      (async function() {
+        window.select_url_result = await sharedStorage.selectURL(
+          'test-url-selection-operation',
+          [
+            {
+              url: "fenced_frames/title0.html"
+            },
+            {
+              url: "fenced_frames/title1.html",
+              reportingMetadata: {
+                "click": "fenced_frames/report1.html"
+              }
+            },
+            {
+              url: "fenced_frames/title2.html"
+            }
+          ],
+          {
+            data: {'mockResult': 1},
+            resolveToConfig: resolveSelectURLToConfig
+          }
+        );
+        if (resolveSelectURLToConfig &&
+            !(select_url_result instanceof FencedFrameConfig)) {
+          throw new Error('selectURL() did not return a FencedFrameConfig.');
+        }
+        return window.select_url_result;
+      })()
+    )");
+
+  EXPECT_TRUE(result.error.empty());
+  const absl::optional<GURL>& observed_urn_uuid = config_observer.GetUrnUuid();
+  EXPECT_TRUE(observed_urn_uuid.has_value());
+  EXPECT_TRUE(blink::IsValidUrnUuidURL(observed_urn_uuid.value()));
+
+  if (!ResolveSelectURLToConfig()) {
+    EXPECT_EQ(result.ExtractString(), observed_urn_uuid->spec());
+  }
 
   // There are 2 "worklet operations": `addModule()` and `selectURL()`.
   test_worklet_host_manager()
       .GetAttachedWorkletHost()
       ->WaitForWorkletResponsesCount(2);
 
+  ASSERT_TRUE(config_observer.ConfigObserved());
+  const absl::optional<FencedFrameConfig>& fenced_frame_config =
+      config_observer.GetConfig();
+  EXPECT_TRUE(fenced_frame_config.has_value());
+  EXPECT_EQ(fenced_frame_config->urn_uuid_, observed_urn_uuid.value());
+
   SharedStorageBudgetMetadata* metadata =
-      GetSharedStorageBudgetMetadata(GURL(urn_uuid));
+      GetSharedStorageBudgetMetadata(observed_urn_uuid.value());
   EXPECT_TRUE(metadata);
   EXPECT_EQ(metadata->origin, https_server()->GetOrigin("b.test"));
   EXPECT_DOUBLE_EQ(metadata->budget_to_charge, std::log2(3));
 
   SharedStorageReportingMap reporting_map =
-      GetSharedStorageReportingMap(GURL(urn_uuid));
+      GetSharedStorageReportingMap(observed_urn_uuid.value());
   EXPECT_FALSE(reporting_map.empty());
   EXPECT_EQ(1U, reporting_map.size());
   EXPECT_EQ("click", reporting_map.begin()->first);
@@ -1935,7 +2090,7 @@
                   {}}}))}});
 }
 
-IN_PROC_BROWSER_TEST_F(SharedStorageBrowserTest,
+IN_PROC_BROWSER_TEST_P(SharedStorageBrowserTest,
                        SelectURL_ReportingMetadata_EmptyReportEvent) {
   GURL main_url = https_server()->GetURL("a.test", kSimplePagePath);
   EXPECT_TRUE(NavigateToURL(shell(), main_url));
@@ -1945,30 +2100,62 @@
   EXPECT_TRUE(ExecJs(shell(), R"(
       sharedStorage.worklet.addModule('shared_storage/simple_module.js');
     )"));
+  EXPECT_TRUE(ExecJs(shell(), JsReplace("window.resolveSelectURLToConfig = $1;",
+                                        ResolveSelectURLToConfig())));
 
-  std::string urn_uuid = EvalJs(shell(), R"(
-      sharedStorage.selectURL(
+  TestSelectURLFencedFrameConfigObserver config_observer(GetStoragePartition());
+  EvalJsResult result = EvalJs(shell(), R"(
+      (async function() {
+        window.select_url_result = await sharedStorage.selectURL(
           'test-url-selection-operation',
-          [{url: "fenced_frames/title0.html",
-          reportingMetadata: {"": "fenced_frames/report1.html"}}],
-          {data: {'mockResult':0}});
-    )")
-                             .ExtractString();
+          [
+            {
+              url: "fenced_frames/title0.html",
+              reportingMetadata: {
+                "": "fenced_frames/report1.html"
+              }
+            }
+          ],
+          {
+            data: {'mockResult': 0},
+            resolveToConfig: resolveSelectURLToConfig
+          }
+        );
+        if (resolveSelectURLToConfig &&
+            !(select_url_result instanceof FencedFrameConfig)) {
+          throw new Error('selectURL() did not return a FencedFrameConfig.');
+        }
+        return window.select_url_result;
+      })()
+    )");
 
-  EXPECT_TRUE(blink::IsValidUrnUuidURL(GURL(urn_uuid)));
+  EXPECT_TRUE(result.error.empty());
+  const absl::optional<GURL>& observed_urn_uuid = config_observer.GetUrnUuid();
+  EXPECT_TRUE(observed_urn_uuid.has_value());
+  EXPECT_TRUE(blink::IsValidUrnUuidURL(observed_urn_uuid.value()));
+
+  if (!ResolveSelectURLToConfig()) {
+    EXPECT_EQ(result.ExtractString(), observed_urn_uuid->spec());
+  }
 
   // There are 2 "worklet operations": `addModule()` and `selectURL()`.
   test_worklet_host_manager()
       .GetAttachedWorkletHost()
       ->WaitForWorkletResponsesCount(2);
 
+  ASSERT_TRUE(config_observer.ConfigObserved());
+  const absl::optional<FencedFrameConfig>& fenced_frame_config =
+      config_observer.GetConfig();
+  EXPECT_TRUE(fenced_frame_config.has_value());
+  EXPECT_EQ(fenced_frame_config->urn_uuid_, observed_urn_uuid.value());
+
   SharedStorageBudgetMetadata* metadata =
-      GetSharedStorageBudgetMetadata(GURL(urn_uuid));
+      GetSharedStorageBudgetMetadata(observed_urn_uuid.value());
   EXPECT_TRUE(metadata);
   EXPECT_EQ(metadata->origin, https_server()->GetOrigin("a.test"));
   EXPECT_DOUBLE_EQ(metadata->budget_to_charge, 0.0);
 
-  EXPECT_THAT(GetSharedStorageReportingMap(GURL(urn_uuid)),
+  EXPECT_THAT(GetSharedStorageReportingMap(observed_urn_uuid.value()),
               UnorderedElementsAre(
                   Pair("", https_server()->GetURL(
                                "a.test", "/fenced_frames/report1.html"))));
@@ -1996,7 +2183,7 @@
                             .spec()}}}}))}});
 }
 
-IN_PROC_BROWSER_TEST_F(SharedStorageBrowserTest, SetAppendOperationInDocument) {
+IN_PROC_BROWSER_TEST_P(SharedStorageBrowserTest, SetAppendOperationInDocument) {
   GURL url = https_server()->GetURL("a.test", kSimplePagePath);
   EXPECT_TRUE(NavigateToURL(shell(), url));
 
@@ -2072,7 +2259,7 @@
         SharedStorageEventParams::CreateDefault()}});
 }
 
-IN_PROC_BROWSER_TEST_F(SharedStorageBrowserTest, DeleteOperationInDocument) {
+IN_PROC_BROWSER_TEST_P(SharedStorageBrowserTest, DeleteOperationInDocument) {
   GURL url = https_server()->GetURL("a.test", kSimplePagePath);
   EXPECT_TRUE(NavigateToURL(shell(), url));
 
@@ -2119,7 +2306,7 @@
         SharedStorageEventParams::CreateForGetOrDelete("key0")}});
 }
 
-IN_PROC_BROWSER_TEST_F(SharedStorageBrowserTest, ClearOperationInDocument) {
+IN_PROC_BROWSER_TEST_P(SharedStorageBrowserTest, ClearOperationInDocument) {
   GURL url = https_server()->GetURL("a.test", kSimplePagePath);
   EXPECT_TRUE(NavigateToURL(shell(), url));
 
@@ -2157,7 +2344,7 @@
         SharedStorageEventParams::CreateDefault()}});
 }
 
-IN_PROC_BROWSER_TEST_F(SharedStorageBrowserTest, SetAppendOperationInWorklet) {
+IN_PROC_BROWSER_TEST_P(SharedStorageBrowserTest, SetAppendOperationInWorklet) {
   GURL url = https_server()->GetURL("a.test", kSimplePagePath);
   EXPECT_TRUE(NavigateToURL(shell(), url));
 
@@ -2231,7 +2418,7 @@
         SharedStorageEventParams::CreateDefault()}});
 }
 
-IN_PROC_BROWSER_TEST_F(SharedStorageBrowserTest,
+IN_PROC_BROWSER_TEST_P(SharedStorageBrowserTest,
                        AppendOperationFailedInWorklet) {
   GURL url = https_server()->GetURL("a.test", kSimplePagePath);
   EXPECT_TRUE(NavigateToURL(shell(), url));
@@ -2270,7 +2457,7 @@
         SharedStorageEventParams::CreateForAppend("key0", "a")}});
 }
 
-IN_PROC_BROWSER_TEST_F(SharedStorageBrowserTest, DeleteOperationInWorklet) {
+IN_PROC_BROWSER_TEST_P(SharedStorageBrowserTest, DeleteOperationInWorklet) {
   GURL url = https_server()->GetURL("a.test", kSimplePagePath);
   EXPECT_TRUE(NavigateToURL(shell(), url));
 
@@ -2329,7 +2516,7 @@
         SharedStorageEventParams::CreateForGetOrDelete("key0")}});
 }
 
-IN_PROC_BROWSER_TEST_F(SharedStorageBrowserTest, ClearOperationInWorklet) {
+IN_PROC_BROWSER_TEST_P(SharedStorageBrowserTest, ClearOperationInWorklet) {
   GURL url = https_server()->GetURL("a.test", kSimplePagePath);
   EXPECT_TRUE(NavigateToURL(shell(), url));
 
@@ -2375,13 +2562,10 @@
         SharedStorageEventParams::CreateDefault()}});
 }
 
-IN_PROC_BROWSER_TEST_F(SharedStorageBrowserTest, GetOperationInWorklet) {
+IN_PROC_BROWSER_TEST_P(SharedStorageBrowserTest, GetOperationInWorklet) {
   base::SimpleTestClock clock;
   base::RunLoop loop;
-  static_cast<StoragePartitionImpl*>(shell()
-                                         ->web_contents()
-                                         ->GetBrowserContext()
-                                         ->GetDefaultStoragePartition())
+  static_cast<StoragePartitionImpl*>(GetStoragePartition())
       ->GetSharedStorageManager()
       ->OverrideClockForTesting(&clock, loop.QuitClosure());
   loop.Run();
@@ -2465,7 +2649,7 @@
         SharedStorageEventParams::CreateForGetOrDelete("key0")}});
 }
 
-IN_PROC_BROWSER_TEST_F(SharedStorageBrowserTest,
+IN_PROC_BROWSER_TEST_P(SharedStorageBrowserTest,
                        AccessStorageInSameOriginDocument) {
   GURL url = https_server()->GetURL("a.test", kSimplePagePath);
   EXPECT_TRUE(NavigateToURL(shell(), url));
@@ -2504,7 +2688,7 @@
         SharedStorageEventParams::CreateDefault()}});
 }
 
-IN_PROC_BROWSER_TEST_F(SharedStorageBrowserTest,
+IN_PROC_BROWSER_TEST_P(SharedStorageBrowserTest,
                        AccessStorageInDifferentOriginDocument) {
   GURL url1 = https_server()->GetURL("a.test", kSimplePagePath);
   EXPECT_TRUE(NavigateToURL(shell(), url1));
@@ -2544,7 +2728,7 @@
         SharedStorageEventParams::CreateDefault()}});
 }
 
-IN_PROC_BROWSER_TEST_F(SharedStorageBrowserTest, KeysAndEntriesOperation) {
+IN_PROC_BROWSER_TEST_P(SharedStorageBrowserTest, KeysAndEntriesOperation) {
   GURL url = https_server()->GetURL("a.test", kSimplePagePath);
   EXPECT_TRUE(NavigateToURL(shell(), url));
 
@@ -2600,7 +2784,7 @@
         SharedStorageEventParams::CreateDefault()}});
 }
 
-IN_PROC_BROWSER_TEST_F(SharedStorageBrowserTest,
+IN_PROC_BROWSER_TEST_P(SharedStorageBrowserTest,
                        KeysAndEntriesOperation_MultipleBatches) {
   GURL url = https_server()->GetURL("a.test", kSimplePagePath);
   EXPECT_TRUE(NavigateToURL(shell(), url));
@@ -2664,19 +2848,31 @@
   ExpectAccessObserved(expected_accesses);
 }
 
+INSTANTIATE_TEST_SUITE_P(All,
+                         SharedStorageBrowserTest,
+                         testing::Bool(),
+                         describe_param);
+
 class SharedStorageAllowURNsInIframesBrowserTest
-    : public SharedStorageBrowserTest {
+    : public base::test::WithFeatureOverride,
+      public SharedStorageBrowserTestBase {
  public:
-  SharedStorageAllowURNsInIframesBrowserTest() {
-    scoped_feature_list_.InitAndEnableFeature(
-        blink::features::kAllowURNsInIframes);
+  SharedStorageAllowURNsInIframesBrowserTest()
+      : base::test::WithFeatureOverride(
+            blink::features::kFencedFramesAPIChanges) {
+    scoped_feature_list_.InitWithFeatures(
+        /*enabled_features=*/
+        {blink::features::kFencedFrames, blink::features::kAllowURNsInIframes},
+        /*disabled_features=*/{});
   }
 
+  bool ResolveSelectURLToConfig() override { return GetParam(); }
+
  private:
   base::test::ScopedFeatureList scoped_feature_list_;
 };
 
-IN_PROC_BROWSER_TEST_F(SharedStorageAllowURNsInIframesBrowserTest,
+IN_PROC_BROWSER_TEST_P(SharedStorageAllowURNsInIframesBrowserTest,
                        RenderSelectURLResultInIframe) {
   GURL main_url = https_server()->GetURL("a.test", kSimplePagePath);
   EXPECT_TRUE(NavigateToURL(shell(), main_url));
@@ -2698,7 +2894,7 @@
 
   TestNavigationObserver top_navigation_observer(shell()->web_contents());
   EXPECT_TRUE(
-      ExecJs(iframe_node, JsReplace("top.location = $1", new_page_url.spec())));
+      ExecJs(iframe_node, JsReplace("top.location = $1", new_page_url)));
   top_navigation_observer.Wait();
 
   // After the top navigation, log(8)=3 bits should have been withdrawn from the
@@ -2707,17 +2903,24 @@
                    kBudgetAllowed - 3);
 }
 
-class SharedStorageFencedFrameInteractionBrowserTest
-    : public SharedStorageBrowserTest {
+INSTANTIATE_TEST_SUITE_P(All,
+                         SharedStorageAllowURNsInIframesBrowserTest,
+                         testing::Bool(),
+                         describe_param);
+
+class SharedStorageFencedFrameInteractionBrowserTestBase
+    : public SharedStorageBrowserTestBase {
  public:
-  SharedStorageFencedFrameInteractionBrowserTest() {
-    scoped_feature_list_
-        .InitWithFeaturesAndParameters(/*enabled_features=*/
-                                       {{blink::features::kFencedFrames, {}}},
-                                       /*disabled_features=*/{});
+  SharedStorageFencedFrameInteractionBrowserTestBase() {
+    scoped_feature_list_.InitAndEnableFeature(blink::features::kFencedFrames);
   }
 
-  FrameTreeNode* CreateFencedFrame(FrameTreeNode* root, const GURL& url) {
+  using FencedFrameNavigationTarget = absl::variant<GURL, std::string>;
+
+  // TODO(crbug.com/1414429): This function should be removed. Use
+  // `CreateFencedFrame` in fenced_frame_test_util.h instead.
+  FrameTreeNode* CreateFencedFrame(FrameTreeNode* root,
+                                   const FencedFrameNavigationTarget& target) {
     size_t initial_child_count = root->child_count();
 
     EXPECT_TRUE(ExecJs(root,
@@ -2729,28 +2932,55 @@
     FrameTreeNode* fenced_frame_root_node =
         GetFencedFrameRootNode(root->child_at(initial_child_count));
 
-    std::string navigate_fenced_frame_script =
-        JsReplace("f.src = $1;", url.spec());
+    TestFrameNavigationObserver observer(fenced_frame_root_node);
 
-    TestFrameNavigationObserver observer(
-        fenced_frame_root_node->current_frame_host());
-
-    EXPECT_EQ(url.spec(), EvalJs(root, navigate_fenced_frame_script));
+    EvalJsResult result = NavigateFencedFrame(root, target);
 
     observer.Wait();
 
+    EXPECT_TRUE(result.error.empty());
+    if (absl::holds_alternative<GURL>(target)) {
+      EXPECT_EQ(result, absl::get<GURL>(target).spec());
+    }
+
     return fenced_frame_root_node;
   }
 
-  FrameTreeNode* CreateFencedFrame(const GURL& url) {
-    return CreateFencedFrame(PrimaryFrameTreeNodeRoot(), url);
+  FrameTreeNode* CreateFencedFrame(const FencedFrameNavigationTarget& target) {
+    return CreateFencedFrame(PrimaryFrameTreeNodeRoot(), target);
+  }
+
+  EvalJsResult NavigateFencedFrame(FrameTreeNode* root,
+                                   const FencedFrameNavigationTarget& target) {
+    return EvalJs(root, absl::visit(base::Overloaded{
+                                        [](const GURL& url) {
+                                          return JsReplace("f.src = $1;", url);
+                                        },
+                                        [](const std::string& config) {
+                                          return JsReplace(
+                                              "f.config = window[$1]", config);
+                                        },
+                                    },
+                                    target));
+    ;
   }
 
  private:
   base::test::ScopedFeatureList scoped_feature_list_;
 };
 
-IN_PROC_BROWSER_TEST_F(SharedStorageFencedFrameInteractionBrowserTest,
+class SharedStorageFencedFrameInteractionBrowserTest
+    : public base::test::WithFeatureOverride,
+      public SharedStorageFencedFrameInteractionBrowserTestBase {
+ public:
+  SharedStorageFencedFrameInteractionBrowserTest()
+      : base::test::WithFeatureOverride(
+            blink::features::kFencedFramesAPIChanges) {}
+
+  bool ResolveSelectURLToConfig() override { return IsParamFeatureEnabled(); }
+};
+
+IN_PROC_BROWSER_TEST_P(SharedStorageFencedFrameInteractionBrowserTest,
                        SelectURL_FinishBeforeStartingFencedFrameNavigation) {
   GURL main_url = https_server()->GetURL("a.test", kSimplePagePath);
   EXPECT_TRUE(NavigateToURL(shell(), main_url));
@@ -2769,30 +2999,68 @@
   EXPECT_EQ("Finish executing simple_module.js",
             base::UTF16ToUTF8(console_observer.messages()[1].message));
 
-  std::string urn_uuid = EvalJs(shell(), R"(
-      sharedStorage.selectURL(
-          'test-url-selection-operation',
-          [{url: "fenced_frames/title0.html"},
-          {url: "fenced_frames/title1.html",
-          reportingMetadata: {"click": "fenced_frames/report1.html"}},
-          {url: "fenced_frames/title2.html"}], {data: {'mockResult': 1}});
-    )")
-                             .ExtractString();
+  EXPECT_TRUE(ExecJs(shell(), JsReplace("window.resolveSelectURLToConfig = $1;",
+                                        ResolveSelectURLToConfig())));
 
-  EXPECT_TRUE(blink::IsValidUrnUuidURL(GURL(urn_uuid)));
+  TestSelectURLFencedFrameConfigObserver config_observer(GetStoragePartition());
+  EvalJsResult result = EvalJs(shell(), R"(
+      (async function() {
+        window.select_url_result = await sharedStorage.selectURL(
+          'test-url-selection-operation',
+          [
+            {
+              url: "fenced_frames/title0.html"
+            },
+            {
+              url: "fenced_frames/title1.html",
+              reportingMetadata: {
+                "click": "fenced_frames/report1.html"
+              }
+            },
+            {
+              url: "fenced_frames/title2.html"
+            }
+          ],
+          {
+            data: {'mockResult': 1},
+            resolveToConfig: resolveSelectURLToConfig
+          }
+        );
+        if (resolveSelectURLToConfig &&
+            !(select_url_result instanceof FencedFrameConfig)) {
+          throw new Error('selectURL() did not return a FencedFrameConfig.');
+        }
+        return window.select_url_result;
+      })()
+    )");
+
+  EXPECT_TRUE(result.error.empty());
+  const absl::optional<GURL>& observed_urn_uuid = config_observer.GetUrnUuid();
+  EXPECT_TRUE(observed_urn_uuid.has_value());
+  EXPECT_TRUE(blink::IsValidUrnUuidURL(observed_urn_uuid.value()));
+
+  if (!ResolveSelectURLToConfig()) {
+    EXPECT_EQ(result.ExtractString(), observed_urn_uuid->spec());
+  }
 
   // There are 2 "worklet operations": `addModule()` and `selectURL()`.
   test_worklet_host_manager()
       .GetAttachedWorkletHost()
       ->WaitForWorkletResponsesCount(2);
 
+  ASSERT_TRUE(config_observer.ConfigObserved());
+  const absl::optional<FencedFrameConfig>& fenced_frame_config =
+      config_observer.GetConfig();
+  EXPECT_TRUE(fenced_frame_config.has_value());
+  EXPECT_EQ(fenced_frame_config->urn_uuid_, observed_urn_uuid.value());
+
   SharedStorageBudgetMetadata* metadata =
-      GetSharedStorageBudgetMetadata(GURL(urn_uuid));
+      GetSharedStorageBudgetMetadata(observed_urn_uuid.value());
   EXPECT_TRUE(metadata);
   EXPECT_EQ(metadata->origin, https_server()->GetOrigin("a.test"));
   EXPECT_DOUBLE_EQ(metadata->budget_to_charge, std::log2(3));
 
-  EXPECT_THAT(GetSharedStorageReportingMap(GURL(urn_uuid)),
+  EXPECT_THAT(GetSharedStorageReportingMap(observed_urn_uuid.value()),
               UnorderedElementsAre(
                   Pair("click", https_server()->GetURL(
                                     "a.test", "/fenced_frames/report1.html"))));
@@ -2823,13 +3091,15 @@
   FrameTreeNode* fenced_frame_root_node =
       GetFencedFrameRootNode(root->child_at(0));
 
-  std::string navigate_fenced_frame_to_urn_script =
-      JsReplace("f.src = $1;", urn_uuid);
+  TestFrameNavigationObserver observer(fenced_frame_root_node);
 
-  TestFrameNavigationObserver observer(
-      fenced_frame_root_node->current_frame_host());
-
-  EXPECT_EQ(urn_uuid, EvalJs(root, navigate_fenced_frame_to_urn_script));
+  EvalJsResult navigation_result = NavigateFencedFrame(
+      root, ResolveSelectURLToConfig()
+                ? FencedFrameNavigationTarget("select_url_result")
+                : FencedFrameNavigationTarget(observed_urn_uuid.value()));
+  if (!ResolveSelectURLToConfig()) {
+    EXPECT_EQ(navigation_result, observed_urn_uuid.value());
+  }
 
   observer.Wait();
 
@@ -2842,7 +3112,7 @@
                                      1);
 }
 
-IN_PROC_BROWSER_TEST_F(SharedStorageFencedFrameInteractionBrowserTest,
+IN_PROC_BROWSER_TEST_P(SharedStorageFencedFrameInteractionBrowserTest,
                        SelectURL_FinishAfterStartingFencedFrameNavigation) {
   GURL main_url = https_server()->GetURL("a.test", kSimplePagePath);
   EXPECT_TRUE(NavigateToURL(shell(), main_url));
@@ -2860,17 +3130,49 @@
       .GetAttachedWorkletHost()
       ->set_should_defer_worklet_messages(true);
 
-  std::string urn_uuid = EvalJs(shell(), R"(
-      sharedStorage.selectURL(
-          'test-url-selection-operation',
-          [{url: "fenced_frames/title0.html"},
-          {url: "fenced_frames/title1.html",
-          reportingMetadata: {"click": "fenced_frames/report1.html"}},
-          {url: "fenced_frames/title2.html"}], {data: {'mockResult': 1}});
-    )")
-                             .ExtractString();
+  EXPECT_TRUE(ExecJs(shell(), JsReplace("window.resolveSelectURLToConfig = $1;",
+                                        ResolveSelectURLToConfig())));
 
-  EXPECT_TRUE(blink::IsValidUrnUuidURL(GURL(urn_uuid)));
+  TestSelectURLFencedFrameConfigObserver config_observer(GetStoragePartition());
+  EvalJsResult result = EvalJs(shell(), R"(
+      (async function() {
+        window.select_url_result = await sharedStorage.selectURL(
+          'test-url-selection-operation',
+          [
+            {
+              url: "fenced_frames/title0.html"
+            },
+            {
+              url: "fenced_frames/title1.html",
+              reportingMetadata: {
+                "click": "fenced_frames/report1.html"
+              }
+            },
+            {
+              url: "fenced_frames/title2.html"
+            }
+          ],
+          {
+            data: {'mockResult': 1},
+            resolveToConfig: resolveSelectURLToConfig
+          }
+        );
+        if (resolveSelectURLToConfig &&
+            !(select_url_result instanceof FencedFrameConfig)) {
+          throw new Error('selectURL() did not return a FencedFrameConfig.');
+        }
+        return window.select_url_result;
+      })()
+    )");
+
+  EXPECT_TRUE(result.error.empty());
+  const absl::optional<GURL>& observed_urn_uuid = config_observer.GetUrnUuid();
+  EXPECT_TRUE(observed_urn_uuid.has_value());
+  EXPECT_TRUE(blink::IsValidUrnUuidURL(observed_urn_uuid.value()));
+
+  if (!ResolveSelectURLToConfig()) {
+    EXPECT_EQ(result.ExtractString(), observed_urn_uuid->spec());
+  }
 
   // There are 2 "worklet operations": `addModule()` and `selectURL()`.
   test_worklet_host_manager()
@@ -2888,13 +3190,15 @@
   FrameTreeNode* fenced_frame_root_node =
       GetFencedFrameRootNode(root->child_at(0));
 
-  std::string navigate_fenced_frame_to_urn_script =
-      JsReplace("f.src = $1;", urn_uuid);
+  TestFrameNavigationObserver observer(fenced_frame_root_node);
 
-  TestFrameNavigationObserver observer(
-      fenced_frame_root_node->current_frame_host());
-
-  EXPECT_EQ(urn_uuid, EvalJs(root, navigate_fenced_frame_to_urn_script));
+  EvalJsResult navigation_result = NavigateFencedFrame(
+      root, ResolveSelectURLToConfig()
+                ? FencedFrameNavigationTarget("select_url_result")
+                : FencedFrameNavigationTarget(observed_urn_uuid.value()));
+  if (!ResolveSelectURLToConfig()) {
+    EXPECT_EQ(navigation_result, observed_urn_uuid.value());
+  }
 
   // After the previous EvalJs, the NavigationRequest should have been created,
   // but may not have begun. Wait for BeginNavigation() and expect it to be
@@ -2913,7 +3217,8 @@
       root->current_frame_host()->GetPage().fenced_frame_urls_map();
   FencedFrameURLMappingTestPeer url_mapping_test_peer(&url_mapping);
 
-  EXPECT_TRUE(url_mapping_test_peer.HasObserver(GURL(urn_uuid), request));
+  EXPECT_TRUE(
+      url_mapping_test_peer.HasObserver(observed_urn_uuid.value(), request));
 
   // Execute the deferred messages. This should finish the url mapping and
   // resume the deferred navigation.
@@ -2923,13 +3228,19 @@
 
   observer.Wait();
 
+  ASSERT_TRUE(config_observer.ConfigObserved());
+  const absl::optional<FencedFrameConfig>& fenced_frame_config =
+      config_observer.GetConfig();
+  EXPECT_TRUE(fenced_frame_config.has_value());
+  EXPECT_EQ(fenced_frame_config->urn_uuid_, observed_urn_uuid.value());
+
   SharedStorageBudgetMetadata* metadata =
-      GetSharedStorageBudgetMetadata(GURL(urn_uuid));
+      GetSharedStorageBudgetMetadata(observed_urn_uuid.value());
   EXPECT_TRUE(metadata);
   EXPECT_EQ(metadata->origin, https_server()->GetOrigin("a.test"));
   EXPECT_DOUBLE_EQ(metadata->budget_to_charge, std::log2(3));
 
-  EXPECT_THAT(GetSharedStorageReportingMap(GURL(urn_uuid)),
+  EXPECT_THAT(GetSharedStorageReportingMap(observed_urn_uuid.value()),
               UnorderedElementsAre(
                   Pair("click", https_server()->GetURL(
                                     "a.test", "/fenced_frames/report1.html"))));
@@ -2948,7 +3259,7 @@
 
 // Tests that the URN from SelectURL() is valid in different
 // context in the page, but it's not valid in a new page.
-IN_PROC_BROWSER_TEST_F(SharedStorageFencedFrameInteractionBrowserTest,
+IN_PROC_BROWSER_TEST_P(SharedStorageFencedFrameInteractionBrowserTest,
                        SelectURL_URNLifetime) {
   GURL main_url = https_server()->GetURL("a.test", kSimplePagePath);
   EXPECT_TRUE(NavigateToURL(shell(), main_url));
@@ -2959,9 +3270,9 @@
   FrameTreeNode* iframe_node = PrimaryFrameTreeNodeRoot()->child_at(0);
 
   // Navigate the iframe to about:blank.
-  TestFrameNavigationObserver observer(iframe_node->current_frame_host());
+  TestFrameNavigationObserver observer(iframe_node);
   EXPECT_TRUE(ExecJs(iframe_node, JsReplace("window.location.href=$1",
-                                            GURL(url::kAboutBlankURL).spec())));
+                                            GURL(url::kAboutBlankURL))));
   observer.Wait();
 
   // Verify that the `urn_uuid` is still valid in the main page.
@@ -2987,7 +3298,7 @@
 
 // Tests that if the URN mapping is not finished before the keep-alive timeout,
 // the mapping will be considered to be failed when the timeout is reached.
-IN_PROC_BROWSER_TEST_F(SharedStorageFencedFrameInteractionBrowserTest,
+IN_PROC_BROWSER_TEST_P(SharedStorageFencedFrameInteractionBrowserTest,
                        SelectURL_NotFinishBeforeKeepAliveTimeout) {
   // The test assumes pages get deleted after navigation. To ensure this,
   // disable back/forward cache.
@@ -3014,7 +3325,49 @@
       .GetAttachedWorkletHost()
       ->set_should_defer_worklet_messages(true);
 
-  std::string urn_uuid = EvalJs(iframe, kSelectFrom8URLsScript).ExtractString();
+  EXPECT_TRUE(ExecJs(iframe, kGenerateURLsListScript));
+  EXPECT_TRUE(ExecJs(iframe, JsReplace("window.resolveSelectURLToConfig = $1;",
+                                       ResolveSelectURLToConfig())));
+
+  TestSelectURLFencedFrameConfigObserver config_observer(GetStoragePartition());
+  EvalJsResult result = EvalJs(iframe, R"(
+      (async function() {
+        const urls = generateUrls(8);
+        window.select_url_result = await sharedStorage.selectURL(
+          'test-url-selection-operation',
+          urls,
+          {
+            data: {'mockResult': 1},
+            resolveToConfig: resolveSelectURLToConfig
+          }
+        );
+        if (resolveSelectURLToConfig &&
+            !(select_url_result instanceof FencedFrameConfig)) {
+          throw new Error('selectURL() did not return a FencedFrameConfig.');
+        }
+        return window.select_url_result;
+      })()
+    )");
+
+  EXPECT_TRUE(result.error.empty());
+  const absl::optional<GURL>& observed_urn_uuid = config_observer.GetUrnUuid();
+  EXPECT_TRUE(observed_urn_uuid.has_value());
+  EXPECT_TRUE(blink::IsValidUrnUuidURL(observed_urn_uuid.value()));
+
+  if (!ResolveSelectURLToConfig()) {
+    EXPECT_EQ(result.ExtractString(), observed_urn_uuid->spec());
+  }
+
+  FrameTreeNode* root = PrimaryFrameTreeNodeRoot();
+  if (ResolveSelectURLToConfig()) {
+    // Preserve the config in a variable. It is then installed to the new fenced
+    // frame. Without this step, the config will be gone after navigating the
+    // iframe to about::blank.
+    EXPECT_TRUE(ExecJs(root, R"(var fenced_frame_config = document
+                                        .getElementById('test_iframe')
+                                        .contentWindow
+                                        .select_url_result;)"));
+  }
 
   // Navigate away to let the subframe's worklet enter keep-alive.
   NavigateIframeToURL(shell()->web_contents(), "test_iframe",
@@ -3028,8 +3381,6 @@
       .GetKeepAliveWorkletHost()
       ->WaitForWorkletResponsesCount(2);
 
-  FrameTreeNode* root = PrimaryFrameTreeNodeRoot();
-
   EXPECT_TRUE(ExecJs(root,
                      "var f = document.createElement('fencedframe');"
                      "f.mode = 'opaque-ads';"
@@ -3039,13 +3390,15 @@
   FrameTreeNode* fenced_frame_root_node =
       GetFencedFrameRootNode(root->child_at(1));
 
-  std::string navigate_fenced_frame_to_urn_script =
-      JsReplace("f.src = $1;", urn_uuid);
+  TestFrameNavigationObserver observer(fenced_frame_root_node);
 
-  TestFrameNavigationObserver observer(
-      fenced_frame_root_node->current_frame_host());
-
-  EXPECT_EQ(urn_uuid, EvalJs(root, navigate_fenced_frame_to_urn_script));
+  EvalJsResult navigation_result = NavigateFencedFrame(
+      root, ResolveSelectURLToConfig()
+                ? FencedFrameNavigationTarget("fenced_frame_config")
+                : FencedFrameNavigationTarget(observed_urn_uuid.value()));
+  if (!ResolveSelectURLToConfig()) {
+    EXPECT_EQ(navigation_result, observed_urn_uuid.value());
+  }
 
   // After the previous EvalJs, the NavigationRequest should have been created,
   // but may not have begun. Wait for BeginNavigation() and expect it to be
@@ -3060,6 +3413,11 @@
     EXPECT_TRUE(request->is_deferred_on_fenced_frame_url_mapping_for_testing());
   }
 
+  ASSERT_FALSE(config_observer.ConfigObserved());
+  const absl::optional<FencedFrameConfig>& fenced_frame_config =
+      config_observer.GetConfig();
+  EXPECT_FALSE(fenced_frame_config.has_value());
+
   // Fire the keep-alive timer. This will terminate the keep-alive, and the
   // deferred navigation will resume to navigate to the default url (at index
   // 0).
@@ -3073,12 +3431,12 @@
   observer.Wait();
 
   SharedStorageBudgetMetadata* metadata =
-      GetSharedStorageBudgetMetadata(GURL(urn_uuid));
+      GetSharedStorageBudgetMetadata(observed_urn_uuid.value());
   EXPECT_TRUE(metadata);
   EXPECT_EQ(metadata->origin, https_server()->GetOrigin("a.test"));
   EXPECT_DOUBLE_EQ(metadata->budget_to_charge, 0.0);
 
-  EXPECT_THAT(GetSharedStorageReportingMap(GURL(urn_uuid)),
+  EXPECT_THAT(GetSharedStorageReportingMap(observed_urn_uuid.value()),
               UnorderedElementsAre(
                   Pair("click", https_server()->GetURL(
                                     "a.test", "/fenced_frames/report0.html")),
@@ -3094,14 +3452,21 @@
   // `kTimingSelectUrlExecutedInWorkletHistogram` histogram isn't recorded.
   histogram_tester_.ExpectTotalCount(kTimingSelectUrlExecutedInWorkletHistogram,
                                      0);
+
+  // The worklet is destructed. The config corresponds to the unresolved URN is
+  // populated in the destructor of `SharedStorageWorkletHost`.
+  ASSERT_TRUE(config_observer.ConfigObserved());
+  EXPECT_TRUE(fenced_frame_config.has_value());
+  EXPECT_EQ(fenced_frame_config->urn_uuid_, observed_urn_uuid.value());
 }
 
-IN_PROC_BROWSER_TEST_F(SharedStorageFencedFrameInteractionBrowserTest,
+IN_PROC_BROWSER_TEST_P(SharedStorageFencedFrameInteractionBrowserTest,
                        SelectURL_WorkletReturnInvalidIndex) {
   GURL main_url = https_server()->GetURL("a.test", kSimplePagePath);
   EXPECT_TRUE(NavigateToURL(shell(), main_url));
 
   WebContentsConsoleObserver console_observer(shell()->web_contents());
+  console_observer.SetFilter(base::BindRepeating(IsErrorMessage));
 
   EXPECT_TRUE(ExecJs(shell(), R"(
       sharedStorage.worklet.addModule('shared_storage/simple_module.js');
@@ -3110,34 +3475,73 @@
   EXPECT_EQ(1u, test_worklet_host_manager().GetAttachedWorkletHostsCount());
   EXPECT_EQ(0u, test_worklet_host_manager().GetKeepAliveWorkletHostsCount());
 
-  std::string urn_uuid = EvalJs(shell(), R"(
-      sharedStorage.selectURL(
-          'test-url-selection-operation',
-          [{url: "fenced_frames/title0.html"},
-          {url: "fenced_frames/title1.html",
-          reportingMetadata: {"click": "fenced_frames/report1.html"}},
-          {url: "fenced_frames/title2.html"}], {data: {'mockResult': 3}});
-    )")
-                             .ExtractString();
+  EXPECT_TRUE(ExecJs(shell(), JsReplace("window.resolveSelectURLToConfig = $1;",
+                                        ResolveSelectURLToConfig())));
 
-  EXPECT_TRUE(blink::IsValidUrnUuidURL(GURL(urn_uuid)));
+  TestSelectURLFencedFrameConfigObserver config_observer(GetStoragePartition());
+  EvalJsResult result = EvalJs(shell(), R"(
+      (async function() {
+        window.select_url_result = await sharedStorage.selectURL(
+          'test-url-selection-operation',
+          [
+            {
+              url: "fenced_frames/title0.html"
+            },
+            {
+              url: "fenced_frames/title1.html",
+              reportingMetadata:
+              {
+                "click": "fenced_frames/report1.html"
+              }
+            },
+            {
+              url: "fenced_frames/title2.html"
+            }
+          ],
+          {
+            data: {'mockResult': 3},
+            resolveToConfig: resolveSelectURLToConfig
+          }
+        );
+        if (resolveSelectURLToConfig &&
+            !(select_url_result instanceof FencedFrameConfig)) {
+          throw new Error('selectURL() did not return a FencedFrameConfig.');
+        }
+        return window.select_url_result;
+      })()
+    )");
+
+  EXPECT_TRUE(result.error.empty());
+  const absl::optional<GURL>& observed_urn_uuid = config_observer.GetUrnUuid();
+  EXPECT_TRUE(observed_urn_uuid.has_value());
+  EXPECT_TRUE(blink::IsValidUrnUuidURL(observed_urn_uuid.value()));
+
+  if (!ResolveSelectURLToConfig()) {
+    EXPECT_EQ(result.ExtractString(), observed_urn_uuid->spec());
+  }
 
   // There are 2 "worklet operations": `addModule()` and `selectURL()`.
   test_worklet_host_manager()
       .GetAttachedWorkletHost()
       ->WaitForWorkletResponsesCount(2);
 
+  ASSERT_TRUE(config_observer.ConfigObserved());
+  const absl::optional<FencedFrameConfig>& fenced_frame_config =
+      config_observer.GetConfig();
+  EXPECT_TRUE(fenced_frame_config.has_value());
+  EXPECT_EQ(fenced_frame_config->urn_uuid_, observed_urn_uuid->spec());
+
   EXPECT_EQ(
       "Promise resolved to a number outside the length of the input urls.",
       base::UTF16ToUTF8(console_observer.messages().back().message));
 
   SharedStorageBudgetMetadata* metadata =
-      GetSharedStorageBudgetMetadata(GURL(urn_uuid));
+      GetSharedStorageBudgetMetadata(observed_urn_uuid.value());
   EXPECT_TRUE(metadata);
   EXPECT_EQ(metadata->origin, https_server()->GetOrigin("a.test"));
   EXPECT_DOUBLE_EQ(metadata->budget_to_charge, std::log2(3));
 
-  EXPECT_TRUE(GetSharedStorageReportingMap(GURL(urn_uuid)).empty());
+  EXPECT_TRUE(GetSharedStorageReportingMap(observed_urn_uuid.value()).empty());
 
   FrameTreeNode* root = PrimaryFrameTreeNodeRoot();
 
@@ -3150,13 +3554,15 @@
   FrameTreeNode* fenced_frame_root_node =
       GetFencedFrameRootNode(root->child_at(0));
 
-  std::string navigate_fenced_frame_to_urn_script =
-      JsReplace("f.src = $1;", urn_uuid);
+  TestFrameNavigationObserver observer(fenced_frame_root_node);
 
-  TestFrameNavigationObserver observer(
-      fenced_frame_root_node->current_frame_host());
-
-  EXPECT_EQ(urn_uuid, EvalJs(root, navigate_fenced_frame_to_urn_script));
+  EvalJsResult navigation_result = NavigateFencedFrame(
+      root, ResolveSelectURLToConfig()
+                ? FencedFrameNavigationTarget("select_url_result")
+                : FencedFrameNavigationTarget(observed_urn_uuid.value()));
+  if (!ResolveSelectURLToConfig()) {
+    EXPECT_EQ(navigation_result, observed_urn_uuid.value());
+  }
 
   observer.Wait();
 
@@ -3169,7 +3575,7 @@
                                      1);
 }
 
-IN_PROC_BROWSER_TEST_F(SharedStorageFencedFrameInteractionBrowserTest,
+IN_PROC_BROWSER_TEST_P(SharedStorageFencedFrameInteractionBrowserTest,
                        SelectURL_DuplicateUrl) {
   GURL main_url = https_server()->GetURL("a.test", kSimplePagePath);
   EXPECT_TRUE(NavigateToURL(shell(), main_url));
@@ -3183,34 +3589,72 @@
   EXPECT_EQ(1u, test_worklet_host_manager().GetAttachedWorkletHostsCount());
   EXPECT_EQ(0u, test_worklet_host_manager().GetKeepAliveWorkletHostsCount());
 
-  std::string urn_uuid = EvalJs(shell(), R"(
-      sharedStorage.selectURL(
-          'test-url-selection-operation',
-          [{url: "fenced_frames/title.html"},
-          {url: "fenced_frames/title0.html",
-          url: "fenced_frames/title1.html",
-          reportingMetadata: {"click": "fenced_frames/report1.html"}},
-          {url: "fenced_frames/title2.html"}], {data: {'mockResult': 1}});
-    )")
-                             .ExtractString();
+  EXPECT_TRUE(ExecJs(shell(), JsReplace("window.resolveSelectURLToConfig = $1;",
+                                        ResolveSelectURLToConfig())));
 
-  EXPECT_TRUE(blink::IsValidUrnUuidURL(GURL(urn_uuid)));
+  TestSelectURLFencedFrameConfigObserver config_observer(GetStoragePartition());
+  EvalJsResult result = EvalJs(shell(), R"(
+      (async function() {
+        window.select_url_result = await sharedStorage.selectURL(
+          'test-url-selection-operation',
+          [
+            {
+              url: "fenced_frames/title0.html"
+            },
+            {
+              url: "fenced_frames/title1.html",
+              reportingMetadata:
+              {
+                "click": "fenced_frames/report1.html"
+              }
+            },
+            {
+              url: "fenced_frames/title2.html"
+            }
+          ],
+          {
+            data: {'mockResult': 1},
+            resolveToConfig: resolveSelectURLToConfig
+          }
+        );
+        if (resolveSelectURLToConfig &&
+            !(select_url_result instanceof FencedFrameConfig)) {
+          throw new Error('selectURL() did not return a FencedFrameConfig.');
+        }
+        return window.select_url_result;
+      })()
+    )");
+
+  EXPECT_TRUE(result.error.empty());
+  const absl::optional<GURL>& observed_urn_uuid = config_observer.GetUrnUuid();
+  EXPECT_TRUE(observed_urn_uuid.has_value());
+  EXPECT_TRUE(blink::IsValidUrnUuidURL(observed_urn_uuid.value()));
+
+  if (!ResolveSelectURLToConfig()) {
+    EXPECT_EQ(result.ExtractString(), observed_urn_uuid->spec());
+  }
 
   // There are 2 "worklet operations": `addModule()` and `selectURL()`.
   test_worklet_host_manager()
       .GetAttachedWorkletHost()
       ->WaitForWorkletResponsesCount(2);
 
+  ASSERT_TRUE(config_observer.ConfigObserved());
+  const absl::optional<FencedFrameConfig>& fenced_frame_config =
+      config_observer.GetConfig();
+  EXPECT_TRUE(fenced_frame_config.has_value());
+  EXPECT_EQ(fenced_frame_config->urn_uuid_, observed_urn_uuid.value());
+
   EXPECT_EQ("Finish executing 'test-url-selection-operation'",
             base::UTF16ToUTF8(console_observer.messages().back().message));
 
   SharedStorageBudgetMetadata* metadata =
-      GetSharedStorageBudgetMetadata(GURL(urn_uuid));
+      GetSharedStorageBudgetMetadata(observed_urn_uuid.value());
   EXPECT_TRUE(metadata);
   EXPECT_EQ(metadata->origin, https_server()->GetOrigin("a.test"));
   EXPECT_DOUBLE_EQ(metadata->budget_to_charge, std::log2(3));
 
-  EXPECT_THAT(GetSharedStorageReportingMap(GURL(urn_uuid)),
+  EXPECT_THAT(GetSharedStorageReportingMap(observed_urn_uuid.value()),
               UnorderedElementsAre(
                   Pair("click", https_server()->GetURL(
                                     "a.test", "/fenced_frames/report1.html"))));
@@ -3226,13 +3670,15 @@
   FrameTreeNode* fenced_frame_root_node =
       GetFencedFrameRootNode(root->child_at(0));
 
-  std::string navigate_fenced_frame_to_urn_script =
-      JsReplace("f.src = $1;", urn_uuid);
+  TestFrameNavigationObserver observer(fenced_frame_root_node);
 
-  TestFrameNavigationObserver observer(
-      fenced_frame_root_node->current_frame_host());
-
-  EXPECT_EQ(urn_uuid, EvalJs(root, navigate_fenced_frame_to_urn_script));
+  EvalJsResult navigation_result = NavigateFencedFrame(
+      root, ResolveSelectURLToConfig()
+                ? FencedFrameNavigationTarget("select_url_result")
+                : FencedFrameNavigationTarget(observed_urn_uuid.value()));
+  if (!ResolveSelectURLToConfig()) {
+    EXPECT_EQ(navigation_result, observed_urn_uuid.value());
+  }
 
   observer.Wait();
 
@@ -3245,7 +3691,7 @@
                                      1);
 }
 
-IN_PROC_BROWSER_TEST_F(SharedStorageFencedFrameInteractionBrowserTest,
+IN_PROC_BROWSER_TEST_P(SharedStorageFencedFrameInteractionBrowserTest,
                        FencedFrameNavigateSelf_NoBudgetWithdrawal) {
   GURL main_url = https_server()->GetURL("a.test", kSimplePagePath);
   EXPECT_TRUE(NavigateToURL(shell(), main_url));
@@ -3262,8 +3708,7 @@
       RemainingBudgetViaJSForFrame(PrimaryFrameTreeNodeRoot()->child_at(0)),
       kBudgetAllowed);
 
-  TestFrameNavigationObserver observer(
-      fenced_frame_root_node->current_frame_host());
+  TestFrameNavigationObserver observer(fenced_frame_root_node);
   EXPECT_TRUE(ExecJs(fenced_frame_root_node, "location.reload()"));
   observer.Wait();
 
@@ -3278,7 +3723,7 @@
                                      1);
 }
 
-IN_PROC_BROWSER_TEST_F(SharedStorageFencedFrameInteractionBrowserTest,
+IN_PROC_BROWSER_TEST_P(SharedStorageFencedFrameInteractionBrowserTest,
                        FencedFrameNavigateTop_BudgetWithdrawal) {
   GURL main_url = https_server()->GetURL("a.test", kSimplePagePath);
   EXPECT_TRUE(NavigateToURL(shell(), main_url));
@@ -3298,9 +3743,9 @@
   GURL new_page_url = https_server()->GetURL("c.test", kSimplePagePath);
 
   TestNavigationObserver top_navigation_observer(shell()->web_contents());
-  EXPECT_TRUE(ExecJs(
-      fenced_frame_root_node,
-      JsReplace("window.open($1, '_unfencedTop')", new_page_url.spec())));
+  EXPECT_TRUE(
+      ExecJs(fenced_frame_root_node,
+             JsReplace("window.open($1, '_unfencedTop')", new_page_url)));
   top_navigation_observer.Wait();
 
   // After the top navigation, log(8)=3 bits should have been withdrawn from the
@@ -3315,7 +3760,7 @@
                                      1);
 }
 
-IN_PROC_BROWSER_TEST_F(
+IN_PROC_BROWSER_TEST_P(
     SharedStorageFencedFrameInteractionBrowserTest,
     FencedFrameNavigateFromParentToRegularURLAndThenNavigateTop_NoBudgetWithdrawal) {
   GURL main_url = https_server()->GetURL("a.test", kSimplePagePath);
@@ -3335,11 +3780,10 @@
 
   GURL new_frame_url = https_server()->GetURL("c.test", kFencedFramePath);
 
-  TestFrameNavigationObserver observer(
-      fenced_frame_root_node->current_frame_host());
+  TestFrameNavigationObserver observer(fenced_frame_root_node);
   std::string navigate_fenced_frame_script = JsReplace(
       "var f = document.getElementsByTagName('fencedframe')[0]; f.src = $1;",
-      new_frame_url.spec());
+      new_frame_url);
 
   EXPECT_TRUE(ExecJs(shell(), navigate_fenced_frame_script));
   observer.Wait();
@@ -3347,9 +3791,9 @@
   GURL new_page_url = https_server()->GetURL("d.test", kSimplePagePath);
 
   TestNavigationObserver top_navigation_observer(shell()->web_contents());
-  EXPECT_TRUE(ExecJs(
-      fenced_frame_root_node,
-      JsReplace("window.open($1, '_unfencedTop')", new_page_url.spec())));
+  EXPECT_TRUE(
+      ExecJs(fenced_frame_root_node,
+             JsReplace("window.open($1, '_unfencedTop')", new_page_url)));
   top_navigation_observer.Wait();
 
   // No budget withdrawal as the initial fenced frame was navigated away by its
@@ -3367,7 +3811,7 @@
                                      1);
 }
 
-IN_PROC_BROWSER_TEST_F(
+IN_PROC_BROWSER_TEST_P(
     SharedStorageFencedFrameInteractionBrowserTest,
     FencedFrameNavigateSelfAndThenNavigateTop_BudgetWithdrawal) {
   GURL main_url = https_server()->GetURL("a.test", kSimplePagePath);
@@ -3383,11 +3827,9 @@
   {
     GURL new_frame_url = https_server()->GetURL("c.test", kFencedFramePath);
 
-    TestFrameNavigationObserver observer(
-        fenced_frame_root_node->current_frame_host());
-    EXPECT_TRUE(
-        ExecJs(fenced_frame_root_node,
-               JsReplace("window.location.href=$1", new_frame_url.spec())));
+    TestFrameNavigationObserver observer(fenced_frame_root_node);
+    EXPECT_TRUE(ExecJs(fenced_frame_root_node,
+                       JsReplace("window.location.href=$1", new_frame_url)));
     observer.Wait();
   }
 
@@ -3400,9 +3842,9 @@
     GURL new_page_url = https_server()->GetURL("d.test", kSimplePagePath);
 
     TestNavigationObserver top_navigation_observer(shell()->web_contents());
-    EXPECT_TRUE(ExecJs(
-        fenced_frame_root_node,
-        JsReplace("window.open($1, '_unfencedTop')", new_page_url.spec())));
+    EXPECT_TRUE(
+        ExecJs(fenced_frame_root_node,
+               JsReplace("window.open($1, '_unfencedTop')", new_page_url)));
     top_navigation_observer.Wait();
   }
 
@@ -3418,7 +3860,7 @@
                                      1);
 }
 
-IN_PROC_BROWSER_TEST_F(SharedStorageFencedFrameInteractionBrowserTest,
+IN_PROC_BROWSER_TEST_P(SharedStorageFencedFrameInteractionBrowserTest,
                        NestedFencedFrameNavigateTop_BudgetWithdrawal) {
   GURL main_url = https_server()->GetURL("a.test", kSimplePagePath);
   EXPECT_TRUE(NavigateToURL(shell(), main_url));
@@ -3442,9 +3884,9 @@
 
   GURL new_page_url = https_server()->GetURL("d.test", kSimplePagePath);
   TestNavigationObserver top_navigation_observer(shell()->web_contents());
-  EXPECT_TRUE(ExecJs(
-      nested_fenced_frame_root_node,
-      JsReplace("window.open($1, '_unfencedTop')", new_page_url.spec())));
+  EXPECT_TRUE(
+      ExecJs(nested_fenced_frame_root_node,
+             JsReplace("window.open($1, '_unfencedTop')", new_page_url)));
   top_navigation_observer.Wait();
 
   // After the top navigation, log(8)=3 bits should have been withdrawn from the
@@ -3459,73 +3901,85 @@
                                      1);
 }
 
-IN_PROC_BROWSER_TEST_F(
+IN_PROC_BROWSER_TEST_P(
     SharedStorageFencedFrameInteractionBrowserTest,
     NestedFencedFrameNavigateTop_BudgetWithdrawalFromTwoMetadata) {
   GURL main_url = https_server()->GetURL("a.test", kSimplePagePath);
   EXPECT_TRUE(NavigateToURL(shell(), main_url));
 
-  url::Origin shared_storage_origin1 =
+  url::Origin shared_storage_origin_1 =
       url::Origin::Create(https_server()->GetURL("b.test", kSimplePagePath));
 
-  GURL urn_uuid1 = SelectFrom8URLsInContext(shared_storage_origin1);
-  FrameTreeNode* fenced_frame_root_node1 = CreateFencedFrame(urn_uuid1);
+  GURL urn_uuid_1 = SelectFrom8URLsInContext(shared_storage_origin_1);
+  FrameTreeNode* fenced_frame_root_node_1 = CreateFencedFrame(urn_uuid_1);
 
-  url::Origin shared_storage_origin2 =
+  url::Origin shared_storage_origin_2 =
       url::Origin::Create(https_server()->GetURL("c.test", kSimplePagePath));
 
-  GURL urn_uuid2 =
-      SelectFrom8URLsInContext(shared_storage_origin2, fenced_frame_root_node1);
+  GURL urn_uuid_2 = SelectFrom8URLsInContext(shared_storage_origin_2,
+                                             fenced_frame_root_node_1);
 
-  FrameTreeNode* fenced_frame_root_node2 =
-      CreateFencedFrame(fenced_frame_root_node1, urn_uuid2);
+  FrameTreeNode* fenced_frame_root_node_2 =
+      CreateFencedFrame(fenced_frame_root_node_1, urn_uuid_2);
 
-  EXPECT_DOUBLE_EQ(GetRemainingBudget(shared_storage_origin1), kBudgetAllowed);
-  EXPECT_DOUBLE_EQ(GetRemainingBudget(shared_storage_origin2), kBudgetAllowed);
+  EXPECT_DOUBLE_EQ(GetRemainingBudget(shared_storage_origin_1), kBudgetAllowed);
+  EXPECT_DOUBLE_EQ(GetRemainingBudget(shared_storage_origin_2), kBudgetAllowed);
 
   GURL new_page_url = https_server()->GetURL("d.test", kSimplePagePath);
   TestNavigationObserver top_navigation_observer(shell()->web_contents());
-  EXPECT_TRUE(ExecJs(
-      fenced_frame_root_node2,
-      JsReplace("window.open($1, '_unfencedTop')", new_page_url.spec())));
+  EXPECT_TRUE(
+      ExecJs(fenced_frame_root_node_2,
+             JsReplace("window.open($1, '_unfencedTop')", new_page_url)));
   top_navigation_observer.Wait();
 
   // After the top navigation, log(8)=3 bits should have been withdrawn from
-  // both `shared_storage_origin1` and `shared_storage_origin2`.
-  EXPECT_DOUBLE_EQ(GetRemainingBudget(shared_storage_origin1),
+  // both `shared_storage_origin_1` and `shared_storage_origin_2`.
+  EXPECT_DOUBLE_EQ(GetRemainingBudget(shared_storage_origin_1),
                    kBudgetAllowed - 3);
-  EXPECT_DOUBLE_EQ(GetRemainingBudget(shared_storage_origin2),
+  EXPECT_DOUBLE_EQ(GetRemainingBudget(shared_storage_origin_2),
                    kBudgetAllowed - 3);
 }
 
-IN_PROC_BROWSER_TEST_F(SharedStorageFencedFrameInteractionBrowserTest,
+IN_PROC_BROWSER_TEST_P(SharedStorageFencedFrameInteractionBrowserTest,
                        SelectURLNotAllowedInNestedFencedFrame) {
   GURL main_url = https_server()->GetURL("a.test", kSimplePagePath);
   EXPECT_TRUE(NavigateToURL(shell(), main_url));
 
-  url::Origin shared_storage_origin1 =
+  url::Origin shared_storage_origin_1 =
       url::Origin::Create(https_server()->GetURL("b.test", kSimplePagePath));
 
-  GURL urn_uuid1 = SelectFrom8URLsInContext(shared_storage_origin1);
-  FrameTreeNode* fenced_frame_root_node1 = CreateFencedFrame(urn_uuid1);
+  GURL urn_uuid_1 = SelectFrom8URLsInContext(shared_storage_origin_1);
+  FrameTreeNode* fenced_frame_root_node_1 = CreateFencedFrame(urn_uuid_1);
 
-  url::Origin shared_storage_origin2 =
+  url::Origin shared_storage_origin_2 =
       url::Origin::Create(https_server()->GetURL("c.test", kSimplePagePath));
 
-  GURL urn_uuid2 =
-      SelectFrom8URLsInContext(shared_storage_origin2, fenced_frame_root_node1);
+  GURL urn_uuid_2 = SelectFrom8URLsInContext(shared_storage_origin_2,
+                                             fenced_frame_root_node_1);
 
-  FrameTreeNode* fenced_frame_root_node2 =
-      CreateFencedFrame(fenced_frame_root_node1, urn_uuid2);
+  FrameTreeNode* fenced_frame_root_node_2 =
+      CreateFencedFrame(fenced_frame_root_node_1, urn_uuid_2);
 
-  EXPECT_TRUE(ExecJs(fenced_frame_root_node2, R"(
+  EXPECT_TRUE(ExecJs(fenced_frame_root_node_2, R"(
       sharedStorage.worklet.addModule('/shared_storage/simple_module.js');
     )"));
+  EXPECT_TRUE(ExecJs(fenced_frame_root_node_2,
+                     JsReplace("window.resolveSelectURLToConfig = $1;",
+                               ResolveSelectURLToConfig())));
 
-  EvalJsResult result = EvalJs(fenced_frame_root_node2, R"(
+  EvalJsResult result = EvalJs(fenced_frame_root_node_2, R"(
       sharedStorage.selectURL(
-          'test-url-selection-operation',
-          [{url: "/fenced_frames/title0.html"}], {data: {'mockResult': 0}});
+        'test-url-selection-operation',
+        [
+          {
+            url: "fenced_frames/title0.html"
+          }
+        ],
+        {
+          data: {'mockResult': 0},
+          resolveToConfig: resolveSelectURLToConfig
+        }
+      );
     )");
 
   EXPECT_THAT(result.error,
@@ -3534,7 +3988,7 @@
                   "depth (2) exceeding the maximum allowed number (1)."));
 }
 
-IN_PROC_BROWSER_TEST_F(SharedStorageFencedFrameInteractionBrowserTest,
+IN_PROC_BROWSER_TEST_P(SharedStorageFencedFrameInteractionBrowserTest,
                        IframeInFencedFrameNavigateTop_BudgetWithdrawal) {
   GURL main_url = https_server()->GetURL("a.test", kSimplePagePath);
   EXPECT_TRUE(NavigateToURL(shell(), main_url));
@@ -3558,9 +4012,9 @@
 
   GURL new_page_url = https_server()->GetURL("d.test", kSimplePagePath);
   TestNavigationObserver top_navigation_observer(shell()->web_contents());
-  EXPECT_TRUE(ExecJs(
-      nested_fenced_frame_root_node,
-      JsReplace("window.open($1, '_unfencedTop')", new_page_url.spec())));
+  EXPECT_TRUE(
+      ExecJs(nested_fenced_frame_root_node,
+             JsReplace("window.open($1, '_unfencedTop')", new_page_url)));
   top_navigation_observer.Wait();
 
   // After the top navigation, log(8)=3 bits should have been withdrawn from the
@@ -3575,7 +4029,7 @@
                                      1);
 }
 
-IN_PROC_BROWSER_TEST_F(SharedStorageFencedFrameInteractionBrowserTest,
+IN_PROC_BROWSER_TEST_P(SharedStorageFencedFrameInteractionBrowserTest,
                        FencedFrame_PopupTwice_BudgetWithdrawalOnce) {
   GURL main_url = https_server()->GetURL("a.test", kSimplePagePath);
   EXPECT_TRUE(NavigateToURL(shell(), main_url));
@@ -3614,7 +4068,7 @@
                                      1);
 }
 
-IN_PROC_BROWSER_TEST_F(
+IN_PROC_BROWSER_TEST_P(
     SharedStorageFencedFrameInteractionBrowserTest,
     TwoFencedFrames_DifferentURNs_EachPopupOnce_BudgetWithdrawalTwice) {
   GURL main_url = https_server()->GetURL("a.test", kSimplePagePath);
@@ -3626,10 +4080,70 @@
       sharedStorage.worklet.addModule('shared_storage/simple_module.js');
     )"));
 
-  GURL urn_uuid1 =
-      GURL(EvalJs(shell(), kSelectFrom8URLsScript).ExtractString());
-  GURL urn_uuid2 =
-      GURL(EvalJs(shell(), kSelectFrom8URLsScript).ExtractString());
+  EXPECT_TRUE(ExecJs(shell(), kGenerateURLsListScript));
+  EXPECT_TRUE(ExecJs(shell(), "window.urls = generateUrls(8);"));
+  EXPECT_TRUE(ExecJs(shell(), JsReplace("window.resolveSelectURLToConfig = $1;",
+                                        ResolveSelectURLToConfig())));
+
+  TestSelectURLFencedFrameConfigObserver config_observer_1(
+      GetStoragePartition());
+  EvalJsResult result_1 = EvalJs(shell(), R"(
+      (async function() {
+        window.select_url_result_1 = await sharedStorage.selectURL(
+          'test-url-selection-operation',
+          urls,
+          {
+            data: {'mockResult': 1},
+            resolveToConfig: resolveSelectURLToConfig
+          }
+        );
+        if (resolveSelectURLToConfig &&
+            !(select_url_result_1 instanceof FencedFrameConfig)) {
+          throw new Error('selectURL() did not return a FencedFrameConfig.');
+        }
+        return window.select_url_result_1;
+      })()
+    )");
+
+  EXPECT_TRUE(result_1.error.empty());
+  const absl::optional<GURL>& observed_urn_uuid_1 =
+      config_observer_1.GetUrnUuid();
+  EXPECT_TRUE(observed_urn_uuid_1.has_value());
+  EXPECT_TRUE(blink::IsValidUrnUuidURL(observed_urn_uuid_1.value()));
+
+  if (!ResolveSelectURLToConfig()) {
+    EXPECT_EQ(result_1.ExtractString(), observed_urn_uuid_1->spec());
+  }
+
+  TestSelectURLFencedFrameConfigObserver config_observer_2(
+      GetStoragePartition());
+  EvalJsResult result_2 = EvalJs(shell(), R"(
+      (async function() {
+        window.select_url_result_2 = await sharedStorage.selectURL(
+          'test-url-selection-operation',
+          urls,
+          {
+            data: {'mockResult': 1},
+            resolveToConfig: resolveSelectURLToConfig
+          }
+        );
+        if (resolveSelectURLToConfig &&
+            !(select_url_result_2 instanceof FencedFrameConfig)) {
+          throw new Error('selectURL() did not return a FencedFrameConfig.');
+        }
+        return window.select_url_result_2;
+      })()
+    )");
+
+  EXPECT_TRUE(result_2.error.empty());
+  const absl::optional<GURL>& observed_urn_uuid_2 =
+      config_observer_2.GetUrnUuid();
+  EXPECT_TRUE(observed_urn_uuid_2.has_value());
+  EXPECT_TRUE(blink::IsValidUrnUuidURL(observed_urn_uuid_2.value()));
+
+  if (!ResolveSelectURLToConfig()) {
+    EXPECT_EQ(result_2.ExtractString(), observed_urn_uuid_2->spec());
+  }
 
   // There are three "worklet operations": one `addModule()` and two
   // `selectURL()`.
@@ -3637,14 +4151,32 @@
       .GetAttachedWorkletHost()
       ->WaitForWorkletResponsesCount(3);
 
-  FrameTreeNode* fenced_frame_root_node1 = CreateFencedFrame(urn_uuid1);
-  FrameTreeNode* fenced_frame_root_node2 = CreateFencedFrame(urn_uuid2);
+  ASSERT_TRUE(config_observer_1.ConfigObserved());
+  const absl::optional<FencedFrameConfig>& fenced_frame_config_1 =
+      config_observer_1.GetConfig();
+  EXPECT_TRUE(fenced_frame_config_1.has_value());
+  EXPECT_EQ(fenced_frame_config_1->urn_uuid_, observed_urn_uuid_1.value());
+
+  ASSERT_TRUE(config_observer_2.ConfigObserved());
+  const absl::optional<FencedFrameConfig>& fenced_frame_config_2 =
+      config_observer_2.GetConfig();
+  EXPECT_TRUE(fenced_frame_config_2.has_value());
+  EXPECT_EQ(fenced_frame_config_2->urn_uuid_, observed_urn_uuid_2.value());
+
+  FrameTreeNode* fenced_frame_root_node_1 = CreateFencedFrame(
+      ResolveSelectURLToConfig()
+          ? FencedFrameNavigationTarget("select_url_result_1")
+          : FencedFrameNavigationTarget(observed_urn_uuid_1.value()));
+  FrameTreeNode* fenced_frame_root_node_2 = CreateFencedFrame(
+      ResolveSelectURLToConfig()
+          ? FencedFrameNavigationTarget("select_url_result_2")
+          : FencedFrameNavigationTarget(observed_urn_uuid_2.value()));
 
   EXPECT_DOUBLE_EQ(GetRemainingBudget(shared_storage_origin), kBudgetAllowed);
   EXPECT_DOUBLE_EQ(RemainingBudgetViaJSForFrame(PrimaryFrameTreeNodeRoot()),
                    kBudgetAllowed);
 
-  OpenPopup(fenced_frame_root_node1,
+  OpenPopup(fenced_frame_root_node_1,
             https_server()->GetURL("b.test", kSimplePagePath), /*name=*/"");
 
   // After the popup, log(8)=3 bits should have been withdrawn from the
@@ -3652,7 +4184,7 @@
   EXPECT_DOUBLE_EQ(GetRemainingBudget(shared_storage_origin),
                    kBudgetAllowed - 3);
 
-  OpenPopup(fenced_frame_root_node2,
+  OpenPopup(fenced_frame_root_node_2,
             https_server()->GetURL("b.test", kSimplePagePath), /*name=*/"");
 
   // After the popup, log(8)=3 bits should have been withdrawn from the
@@ -3669,7 +4201,7 @@
                                      2);
 }
 
-IN_PROC_BROWSER_TEST_F(
+IN_PROC_BROWSER_TEST_P(
     SharedStorageFencedFrameInteractionBrowserTest,
     TwoFencedFrames_SameURNs_EachPopupOnce_BudgetWithdrawalOnce) {
   GURL main_url = https_server()->GetURL("a.test", kSimplePagePath);
@@ -3679,15 +4211,15 @@
 
   GURL urn_uuid = SelectFrom8URLsInContext(shared_storage_origin);
 
-  FrameTreeNode* fenced_frame_root_node1 = CreateFencedFrame(urn_uuid);
-  FrameTreeNode* fenced_frame_root_node2 = CreateFencedFrame(urn_uuid);
+  FrameTreeNode* fenced_frame_root_node_1 = CreateFencedFrame(urn_uuid);
+  FrameTreeNode* fenced_frame_root_node_2 = CreateFencedFrame(urn_uuid);
 
   EXPECT_DOUBLE_EQ(GetRemainingBudget(shared_storage_origin), kBudgetAllowed);
   EXPECT_DOUBLE_EQ(
       RemainingBudgetViaJSForFrame(PrimaryFrameTreeNodeRoot()->child_at(0)),
       kBudgetAllowed);
 
-  OpenPopup(fenced_frame_root_node1,
+  OpenPopup(fenced_frame_root_node_1,
             https_server()->GetURL("b.test", kSimplePagePath), /*name=*/"");
 
   // After the popup, log(8)=3 bits should have been withdrawn from the
@@ -3695,7 +4227,7 @@
   EXPECT_DOUBLE_EQ(GetRemainingBudget(shared_storage_origin),
                    kBudgetAllowed - 3);
 
-  OpenPopup(fenced_frame_root_node2,
+  OpenPopup(fenced_frame_root_node_2,
             https_server()->GetURL("b.test", kSimplePagePath), /*name=*/"");
 
   // The budget can only be withdrawn once for each urn_uuid.
@@ -3709,7 +4241,7 @@
                                      1);
 }
 
-IN_PROC_BROWSER_TEST_F(SharedStorageFencedFrameInteractionBrowserTest,
+IN_PROC_BROWSER_TEST_P(SharedStorageFencedFrameInteractionBrowserTest,
                        SelectURL_InsufficientBudget) {
   GURL main_url = https_server()->GetURL("a.test", kSimplePagePath);
   EXPECT_TRUE(NavigateToURL(shell(), main_url));
@@ -3717,37 +4249,115 @@
   url::Origin shared_storage_origin = url::Origin::Create(main_url);
 
   WebContentsConsoleObserver console_observer(shell()->web_contents());
+  console_observer.SetFilter(base::BindRepeating(IsErrorMessage));
 
   EXPECT_TRUE(ExecJs(shell(), R"(
       sharedStorage.worklet.addModule('shared_storage/simple_module.js');
     )"));
 
-  GURL urn_uuid1 =
-      GURL(EvalJs(shell(), kSelectFrom8URLsScript).ExtractString());
+  EXPECT_TRUE(ExecJs(shell(), kGenerateURLsListScript));
+  EXPECT_TRUE(ExecJs(shell(), "window.urls = generateUrls(8);"));
+  EXPECT_TRUE(ExecJs(shell(), JsReplace("window.resolveSelectURLToConfig = $1;",
+                                        ResolveSelectURLToConfig())));
 
-  FrameTreeNode* fenced_frame_root_node1 = CreateFencedFrame(urn_uuid1);
-  OpenPopup(fenced_frame_root_node1,
+  TestSelectURLFencedFrameConfigObserver config_observer_1(
+      GetStoragePartition());
+  EvalJsResult result_1 = EvalJs(shell(), R"(
+      (async function() {
+        window.select_url_result_1 = await sharedStorage.selectURL(
+          'test-url-selection-operation',
+          urls,
+          {
+            data: {'mockResult': 1},
+            resolveToConfig: resolveSelectURLToConfig
+          }
+        );
+        if (resolveSelectURLToConfig &&
+            !(select_url_result_1 instanceof FencedFrameConfig)) {
+          throw new Error('selectURL() did not return a FencedFrameConfig.');
+        }
+        return window.select_url_result_1;
+      })()
+    )");
+
+  EXPECT_TRUE(result_1.error.empty());
+  const absl::optional<GURL>& observed_urn_uuid_1 =
+      config_observer_1.GetUrnUuid();
+  EXPECT_TRUE(observed_urn_uuid_1.has_value());
+  EXPECT_TRUE(blink::IsValidUrnUuidURL(observed_urn_uuid_1.value()));
+
+  if (!ResolveSelectURLToConfig()) {
+    EXPECT_EQ(result_1.ExtractString(), observed_urn_uuid_1->spec());
+  }
+
+  FrameTreeNode* fenced_frame_root_node_1 = CreateFencedFrame(
+      ResolveSelectURLToConfig()
+          ? FencedFrameNavigationTarget("select_url_result_1")
+          : FencedFrameNavigationTarget(observed_urn_uuid_1.value()));
+  OpenPopup(fenced_frame_root_node_1,
             https_server()->GetURL("b.test", kSimplePagePath), /*name=*/"");
 
   EXPECT_DOUBLE_EQ(GetRemainingBudget(shared_storage_origin),
                    kBudgetAllowed - 3);
 
-  GURL urn_uuid2 =
-      GURL(EvalJs(shell(), kSelectFrom8URLsScript).ExtractString());
+  TestSelectURLFencedFrameConfigObserver config_observer_2(
+      GetStoragePartition());
+  EvalJsResult result_2 = EvalJs(shell(), R"(
+      (async function() {
+        window.select_url_result_2 = await sharedStorage.selectURL(
+          'test-url-selection-operation',
+          urls,
+          {
+            data: {'mockResult': 1},
+            resolveToConfig: resolveSelectURLToConfig
+          }
+        );
+        if (resolveSelectURLToConfig &&
+            !(select_url_result_2 instanceof FencedFrameConfig)) {
+          throw new Error('selectURL() did not return a FencedFrameConfig.');
+        }
+        return window.select_url_result_2;
+      })()
+    )");
+
+  EXPECT_TRUE(result_2.error.empty());
+  const absl::optional<GURL>& observed_urn_uuid_2 =
+      config_observer_2.GetUrnUuid();
+  EXPECT_TRUE(observed_urn_uuid_2.has_value());
+  EXPECT_TRUE(blink::IsValidUrnUuidURL(observed_urn_uuid_2.value()));
+
+  if (!ResolveSelectURLToConfig()) {
+    EXPECT_EQ(result_2.ExtractString(), observed_urn_uuid_2->spec());
+  }
 
   // Wait for the `addModule()` and two `selectURL()` to finish.
   test_worklet_host_manager()
       .GetAttachedWorkletHost()
       ->WaitForWorkletResponsesCount(3);
 
+  ASSERT_TRUE(config_observer_1.ConfigObserved());
+  const absl::optional<FencedFrameConfig>& fenced_frame_config_1 =
+      config_observer_1.GetConfig();
+  EXPECT_TRUE(fenced_frame_config_1.has_value());
+  EXPECT_EQ(fenced_frame_config_1->urn_uuid_, observed_urn_uuid_1.value());
+
+  ASSERT_TRUE(config_observer_2.ConfigObserved());
+  const absl::optional<FencedFrameConfig>& fenced_frame_config_2 =
+      config_observer_2.GetConfig();
+  EXPECT_TRUE(fenced_frame_config_2.has_value());
+  EXPECT_EQ(fenced_frame_config_2->urn_uuid_, observed_urn_uuid_2.value());
+
   EXPECT_EQ("Insufficient budget for selectURL().",
             base::UTF16ToUTF8(console_observer.messages().back().message));
 
-  // The failed mapping due to insufficient budget (i.e. `urn_uuid2`) should not
-  // incur any budget withdrawal on subsequent top navigation from inside
+  // The failed mapping due to insufficient budget (i.e. `urn_uuid_2`) should
+  // not incur any budget withdrawal on subsequent top navigation from inside
   // the fenced frame.
-  FrameTreeNode* fenced_frame_root_node2 = CreateFencedFrame(urn_uuid2);
-  OpenPopup(fenced_frame_root_node2,
+  FrameTreeNode* fenced_frame_root_node_2 = CreateFencedFrame(
+      ResolveSelectURLToConfig()
+          ? FencedFrameNavigationTarget("select_url_result_2")
+          : FencedFrameNavigationTarget(observed_urn_uuid_2.value()));
+  OpenPopup(fenced_frame_root_node_2,
             https_server()->GetURL("c.test", kSimplePagePath), /*name=*/"");
 
   EXPECT_DOUBLE_EQ(GetRemainingBudget(shared_storage_origin),
@@ -3762,7 +4372,7 @@
 
 // When number of urn mappings limit has been reached, subsequent `selectURL()`
 // calls will fail.
-IN_PROC_BROWSER_TEST_F(SharedStorageFencedFrameInteractionBrowserTest,
+IN_PROC_BROWSER_TEST_P(SharedStorageFencedFrameInteractionBrowserTest,
                        SelectURL_Fails_ExceedNumOfUrnMappingsLimit) {
   GURL main_url = https_server()->GetURL("a.test", kSimplePagePath);
   EXPECT_TRUE(NavigateToURL(shell(), main_url));
@@ -3776,12 +4386,23 @@
       sharedStorage.worklet.addModule('shared_storage/simple_module.js');
     )"));
 
-  EvalJsResult result = EvalJs(shell(), R"(
+  EXPECT_TRUE(ExecJs(shell(), JsReplace("window.resolveSelectURLToConfig = $1;",
+                                        ResolveSelectURLToConfig())));
+  std::string select_url_script = R"(
       sharedStorage.selectURL(
-          'test-url-selection-operation',
-          [{url: "fenced_frames/title0.html"}], {data: {'mockResult': 0}});
-    )");
-  EXPECT_TRUE(result.error.empty());
+        'test-url-selection-operation',
+        [
+          {
+            url: "fenced_frames/title0.html"
+          }
+        ],
+        {
+          data: {'mockResult': 0},
+          resolveToConfig: resolveSelectURLToConfig
+        }
+      );
+    )";
+  EXPECT_TRUE(ExecJs(shell(), select_url_script));
 
   // Wait for the `addModule()` and `selectURL()` to finish.
   test_worklet_host_manager()
@@ -3797,11 +4418,7 @@
   GURL url("https://a.test");
   fenced_frame_url_mapping_test_peer.FillMap(url);
 
-  EvalJsResult extra_result = EvalJs(shell(), R"(
-      sharedStorage.selectURL(
-          'test-url-selection-operation',
-          [{url: "fenced_frames/title1.html"}], {data: {'mockResult': 0}});
-    )");
+  EvalJsResult extra_result = EvalJs(shell(), select_url_script);
 
   // `selectURL()` fails when map is full.
   std::string expected_error = base::StrCat(
@@ -3811,30 +4428,33 @@
   EXPECT_EQ(expected_error, extra_result.error);
 }
 
+INSTANTIATE_TEST_SUITE_P(All,
+                         SharedStorageFencedFrameInteractionBrowserTest,
+                         testing::Bool(),
+                         describe_param);
+
 class SharedStorageSelectURLNotAllowedInFencedFrameBrowserTest
     : public SharedStorageFencedFrameInteractionBrowserTest {
  public:
   SharedStorageSelectURLNotAllowedInFencedFrameBrowserTest() {
-    scoped_feature_list_
-        .InitWithFeaturesAndParameters(/*enabled_features=*/
-                                       {{blink::features::kSharedStorageAPI,
-                                         {{"SharedStorageBitBudget",
-                                           base::NumberToString(
-                                               kBudgetAllowed)},
-                                          {"SharedStorageMaxAllowedFencedFrameD"
-                                           "epthForSelectURL",
-                                           "0"}}},
-                                        {features::
-                                             kPrivacySandboxAdsAPIsOverride,
-                                         {}}},
-                                       /*disabled_features=*/{});
+    scoped_feature_list_.InitWithFeaturesAndParameters(
+        /*enabled_features=*/
+        {{blink::features::kSharedStorageAPI,
+          {{"SharedStorageBitBudget", base::NumberToString(kBudgetAllowed)},
+           {"SharedStorageMaxAllowedFencedFrameDepthForSelectURL", "0"}}},
+         {features::kPrivacySandboxAdsAPIsOverride, {}}},
+        /*disabled_features=*/{});
+
+    fenced_frame_api_change_feature_.InitWithFeatureState(
+        blink::features::kFencedFramesAPIChanges, ResolveSelectURLToConfig());
   }
 
  private:
   base::test::ScopedFeatureList scoped_feature_list_;
+  base::test::ScopedFeatureList fenced_frame_api_change_feature_;
 };
 
-IN_PROC_BROWSER_TEST_F(SharedStorageSelectURLNotAllowedInFencedFrameBrowserTest,
+IN_PROC_BROWSER_TEST_P(SharedStorageSelectURLNotAllowedInFencedFrameBrowserTest,
                        SelectURLNotAllowedInFencedFrame) {
   GURL main_frame_url = https_server()->GetURL("a.test", kSimplePagePath);
 
@@ -3852,10 +4472,22 @@
   EXPECT_EQ(1u, test_worklet_host_manager().GetAttachedWorkletHostsCount());
   EXPECT_EQ(0u, test_worklet_host_manager().GetKeepAliveWorkletHostsCount());
 
+  EXPECT_TRUE(ExecJs(fenced_frame_node,
+                     JsReplace("window.resolveSelectURLToConfig = $1;",
+                               ResolveSelectURLToConfig())));
   EvalJsResult result = EvalJs(fenced_frame_node, R"(
       sharedStorage.selectURL(
-          'test-url-selection-operation',
-          [{url: "fenced_frames/title0.html"}], {data: {'mockResult': 0}});
+        'test-url-selection-operation',
+        [
+          {
+            url: "fenced_frames/title0.html"
+          }
+        ],
+        {
+          data: {'mockResult': 0},
+          resolveToConfig: resolveSelectURLToConfig
+        }
+      );
     )");
 
   EXPECT_THAT(result.error,
@@ -3864,6 +4496,12 @@
                   "depth (1) exceeding the maximum allowed number (0)."));
 }
 
+INSTANTIATE_TEST_SUITE_P(
+    All,
+    SharedStorageSelectURLNotAllowedInFencedFrameBrowserTest,
+    testing::Bool(),
+    describe_param);
+
 class SharedStorageReportEventBrowserTest
     : public SharedStorageFencedFrameInteractionBrowserTest {
   void FinishSetup() override {
@@ -3872,7 +4510,7 @@
   }
 };
 
-IN_PROC_BROWSER_TEST_F(SharedStorageReportEventBrowserTest,
+IN_PROC_BROWSER_TEST_P(SharedStorageReportEventBrowserTest,
                        SelectURL_ReportEvent) {
   net::test_server::ControllableHttpResponse response1(
       https_server(), "/fenced_frames/report1.html");
@@ -3888,17 +4526,47 @@
   EXPECT_TRUE(ExecJs(shell(), R"(
       sharedStorage.worklet.addModule('shared_storage/simple_module.js');
     )"));
+  EXPECT_TRUE(ExecJs(shell(), JsReplace("window.resolveSelectURLToConfig = $1;",
+                                        ResolveSelectURLToConfig())));
 
-  GURL urn_uuid = GURL(EvalJs(shell(), R"(
-      sharedStorage.selectURL(
+  TestSelectURLFencedFrameConfigObserver config_observer(GetStoragePartition());
+  EvalJsResult result = EvalJs(shell(), R"(
+      (async function() {
+        window.select_url_result = await sharedStorage.selectURL(
           'test-url-selection-operation',
-          [{url: "fenced_frames/title0.html"},
-          {url: "fenced_frames/title1.html",
-          reportingMetadata: {'click': "fenced_frames/report1.html",
-              'mouse interaction': "fenced_frames/report2.html"}}],
-          {data: {'mockResult':1}});
-    )")
-                           .ExtractString());
+          [
+            {
+              url: "fenced_frames/title0.html"
+            },
+            {
+              url: "fenced_frames/title1.html",
+              reportingMetadata: {
+                "click": "fenced_frames/report1.html",
+                "mouse interaction": "fenced_frames/report2.html"
+              }
+            }
+          ],
+          {
+            data: {'mockResult': 1},
+            resolveToConfig: resolveSelectURLToConfig
+          }
+        );
+        if (resolveSelectURLToConfig &&
+            !(select_url_result instanceof FencedFrameConfig)) {
+          throw new Error('selectURL() did not return a FencedFrameConfig.');
+        }
+        return window.select_url_result;
+      })()
+    )");
+
+  EXPECT_TRUE(result.error.empty());
+  const absl::optional<GURL>& observed_urn_uuid = config_observer.GetUrnUuid();
+  EXPECT_TRUE(observed_urn_uuid.has_value());
+  EXPECT_TRUE(blink::IsValidUrnUuidURL(observed_urn_uuid.value()));
+
+  if (!ResolveSelectURLToConfig()) {
+    EXPECT_EQ(result.ExtractString(), observed_urn_uuid->spec());
+  }
 
   // There are three "worklet operations": one `addModule()` and two
   // `selectURL()`.
@@ -3906,7 +4574,10 @@
       .GetAttachedWorkletHost()
       ->WaitForWorkletResponsesCount(2);
 
-  FrameTreeNode* fenced_frame_root_node = CreateFencedFrame(urn_uuid);
+  FrameTreeNode* fenced_frame_root_node = CreateFencedFrame(
+      ResolveSelectURLToConfig()
+          ? FencedFrameNavigationTarget("select_url_result")
+          : FencedFrameNavigationTarget(observed_urn_uuid.value()));
 
   std::string event_data1 = "this is a click";
   EXPECT_TRUE(
@@ -3937,8 +4608,13 @@
                                      1);
 }
 
+INSTANTIATE_TEST_SUITE_P(All,
+                         SharedStorageReportEventBrowserTest,
+                         testing::Bool(),
+                         describe_param);
+
 class SharedStoragePrivateAggregationDisabledBrowserTest
-    : public SharedStorageBrowserTest {
+    : public SharedStorageBrowserTestBase {
  public:
   SharedStoragePrivateAggregationDisabledBrowserTest() {
     scoped_feature_list_.InitAndDisableFeature(content::kPrivateAggregationApi);
@@ -3969,7 +4645,7 @@
 }
 
 class SharedStoragePrivateAggregationDisabledForSharedStorageOnlyBrowserTest
-    : public SharedStorageBrowserTest {
+    : public SharedStorageBrowserTestBase {
  public:
   SharedStoragePrivateAggregationDisabledForSharedStorageOnlyBrowserTest() {
     scoped_feature_list_.InitAndEnableFeatureWithParameters(
@@ -4003,7 +4679,7 @@
 }
 
 class SharedStoragePrivateAggregationEnabledBrowserTest
-    : public SharedStorageBrowserTest {
+    : public SharedStorageBrowserTestBase {
  public:
   // TODO(alexmt): Consider factoring out along with FLEDGE definition.
   class TestPrivateAggregationManagerImpl
@@ -4022,7 +4698,7 @@
   }
 
   void SetUpOnMainThread() override {
-    SharedStorageBrowserTest::SetUpOnMainThread();
+    SharedStorageBrowserTestBase::SetUpOnMainThread();
 
     browser_client_ =
         std::make_unique<MockPrivateAggregationShellContentBrowserClient>();
@@ -4030,10 +4706,7 @@
     a_test_origin_ = https_server()->GetOrigin("a.test");
 
     auto* storage_partition_impl =
-        static_cast<StoragePartitionImpl*>(shell()
-                                               ->web_contents()
-                                               ->GetBrowserContext()
-                                               ->GetDefaultStoragePartition());
+        static_cast<StoragePartitionImpl*>(GetStoragePartition());
 
     private_aggregation_host_ = new PrivateAggregationHost(
         /*on_report_request_received=*/mock_callback_.Get(),
@@ -4243,29 +4916,31 @@
 }
 
 class SharedStorageSelectURLLimitBrowserTest
-    : public SharedStorageBrowserTest,
-      public testing::WithParamInterface<bool> {
+    : public SharedStorageBrowserTestBase,
+      public testing::WithParamInterface<std::tuple<bool, bool>> {
  public:
   SharedStorageSelectURLLimitBrowserTest() {
-    if (GetParam()) {
-      feature_list_
-          .InitWithFeaturesAndParameters(/*enabled_features=*/
-                                         {{blink::features::
-                                               kSharedStorageSelectURLLimit,
-                                           {{"SharedStorageMaxAllowedSelectURLC"
-                                             "al"
-                                             "lsPerOriginPerPageLoad",
-                                             base::NumberToString(
-                                                 kMaxSelectURLCalls)}}}},
-                                         /*disabled_features=*/{});
+    if (LimitSelectURLCalls()) {
+      select_url_limit_feature_list_.InitWithFeaturesAndParameters(
+          /*enabled_features=*/{{blink::features::kSharedStorageSelectURLLimit,
+                                 {{"SharedStorageMaxAllowedSelectURLCallsPerOri"
+                                   "ginPerPageLoad",
+                                   base::NumberToString(kMaxSelectURLCalls)}}}},
+          /*disabled_features=*/{});
     } else {
-      feature_list_.InitWithFeaturesAndParameters(
-          /*enabled_features=*/{},
-          /*disabled_features=*/
-          {blink::features::kSharedStorageSelectURLLimit});
+      select_url_limit_feature_list_.InitAndDisableFeature(
+          blink::features::kSharedStorageSelectURLLimit);
     }
+
+    fenced_frame_api_change_feature_.InitWithFeatureState(
+        blink::features::kFencedFramesAPIChanges, ResolveSelectURLToConfig());
+    fenced_frame_feature_.InitAndEnableFeature(blink::features::kFencedFrames);
   }
 
+  bool LimitSelectURLCalls() const { return std::get<0>(GetParam()); }
+
+  bool ResolveSelectURLToConfig() override { return std::get<1>(GetParam()); }
+
   // Precondition: `addModule('shared_storage/simple_module.js')` has been
   // called in the main frame.
   void RunSuccessfulSelectURLInMainFrame(
@@ -4273,9 +4948,16 @@
       WebContentsConsoleObserver* console_observer) {
     std::string urn_uuid = EvalJs(shell(), R"(
       sharedStorage.selectURL(
-          'test-url-selection-operation',
-          [{url: "fenced_frames/title0.html"}],
-          {data: {'mockResult':0}});
+        'test-url-selection-operation',
+        [
+          {
+            url: "fenced_frames/title0.html"
+          }
+        ],
+        {
+          data: {'mockResult':0}
+        }
+      );
     )")
                                .ExtractString();
 
@@ -4303,16 +4985,43 @@
     EXPECT_TRUE(ExecJs(iframe_node, R"(
       sharedStorage.worklet.addModule('shared_storage/simple_module.js');
     )"));
+    EXPECT_TRUE(
+        ExecJs(iframe_node, JsReplace("window.resolveSelectURLToConfig = $1;",
+                                      ResolveSelectURLToConfig())));
 
-    std::string urn_uuid = EvalJs(iframe_node, R"(
-      sharedStorage.selectURL(
+    TestSelectURLFencedFrameConfigObserver config_observer(
+        GetStoragePartition());
+    EvalJsResult result = EvalJs(iframe_node, R"(
+      (async function() {
+        window.select_url_result = await sharedStorage.selectURL(
           'test-url-selection-operation',
-          [{url: "fenced_frames/title0.html"}],
-          {data: {'mockResult':0}});
-    )")
-                               .ExtractString();
+          [
+            {
+              url: "fenced_frames/title0.html"
+            }
+          ],
+          {
+            data: {'mockResult': 0},
+            resolveToConfig: resolveSelectURLToConfig
+          }
+        );
+        if (resolveSelectURLToConfig &&
+            !(select_url_result instanceof FencedFrameConfig)) {
+          throw new Error('selectURL() did not return a FencedFrameConfig.');
+        }
+        return window.select_url_result;
+      })()
+    )");
 
-    EXPECT_TRUE(blink::IsValidUrnUuidURL(GURL(urn_uuid)));
+    EXPECT_TRUE(result.error.empty());
+    const absl::optional<GURL>& observed_urn_uuid =
+        config_observer.GetUrnUuid();
+    EXPECT_TRUE(observed_urn_uuid.has_value());
+    EXPECT_TRUE(blink::IsValidUrnUuidURL(observed_urn_uuid.value()));
+
+    if (!ResolveSelectURLToConfig()) {
+      EXPECT_EQ(result.ExtractString(), observed_urn_uuid->spec());
+    }
 
     // There are 2 "worklet operations": `addModule()` and  `selectURL()`.
     test_worklet_host_manager()
@@ -4320,7 +5029,7 @@
         ->WaitForWorkletResponsesCount(2);
 
     SharedStorageBudgetMetadata* metadata =
-        GetSharedStorageBudgetMetadata(GURL(urn_uuid));
+        GetSharedStorageBudgetMetadata(observed_urn_uuid.value());
     EXPECT_TRUE(metadata);
     EXPECT_EQ(metadata->origin, https_server()->GetOrigin(host_str));
     EXPECT_DOUBLE_EQ(metadata->budget_to_charge, 0.0);
@@ -4330,18 +5039,21 @@
   }
 
  private:
-  base::test::ScopedFeatureList feature_list_;
+  base::test::ScopedFeatureList select_url_limit_feature_list_;
+  base::test::ScopedFeatureList fenced_frame_api_change_feature_;
+  base::test::ScopedFeatureList fenced_frame_feature_;
 };
 
 INSTANTIATE_TEST_SUITE_P(All,
                          SharedStorageSelectURLLimitBrowserTest,
-                         testing::Bool(),
+                         testing::Combine(testing::Bool(), testing::Bool()),
                          [](const auto& info) {
-                           if (info.param) {
-                             return "SelectURLLimit";
-                           } else {
-                             return "NoSelectURLLimit";
-                           }
+                           return base::StrCat(
+                               {"LimitSelectURLCalls",
+                                std::get<0>(info.param) ? "Enabled"
+                                                        : "Disabled",
+                                "_ResolveSelectURLTo",
+                                std::get<1>(info.param) ? "Config" : "URN"});
                          });
 
 IN_PROC_BROWSER_TEST_P(SharedStorageSelectURLLimitBrowserTest,
@@ -4364,14 +5076,25 @@
     RunSuccessfulSelectURLInMainFrame("a.test", &console_observer);
   }
 
-  if (GetParam()) {
+  if (LimitSelectURLCalls()) {
     // The limit for `selectURL()` has now been reached for "a.test". Make one
     // more call, which will be blocked.
+    EXPECT_TRUE(
+        ExecJs(shell(), JsReplace("window.resolveSelectURLToConfig = $1;",
+                                  ResolveSelectURLToConfig())));
     EvalJsResult result = EvalJs(shell(), R"(
       sharedStorage.selectURL(
-          'test-url-selection-operation',
-          [{url: "fenced_frames/title0.html"}],
-          {data: {'mockResult':0}});
+        'test-url-selection-operation',
+        [
+          {
+            url: "fenced_frames/title0.html"
+          }
+        ],
+        {
+          data: {'mockResult': 0},
+          resolveToConfig: resolveSelectURLToConfig
+        }
+      );
     )");
 
     EXPECT_EQ(
@@ -4386,7 +5109,7 @@
   WaitForHistograms({kTimingSelectUrlExecutedInWorkletHistogram});
 
   int expected_success_count =
-      GetParam() ? kMaxSelectURLCalls : kMaxSelectURLCalls + 1;
+      LimitSelectURLCalls() ? kMaxSelectURLCalls : kMaxSelectURLCalls + 1;
   histogram_tester_.ExpectTotalCount(kTimingSelectUrlExecutedInWorkletHistogram,
                                      expected_success_count);
 
@@ -4429,7 +5152,7 @@
   FrameTreeNode* iframe_node =
       CreateIFrame(PrimaryFrameTreeNodeRoot(), iframe_url);
 
-  if (GetParam()) {
+  if (LimitSelectURLCalls()) {
     EXPECT_TRUE(ExecJs(iframe_node, R"(
       sharedStorage.worklet.addModule('shared_storage/simple_module.js');
     )"));
@@ -4441,11 +5164,22 @@
 
     // The limit for `selectURL()` has now been reached for "b.test". Make one
     // more call, which will be blocked.
+    EXPECT_TRUE(
+        ExecJs(iframe_node, JsReplace("window.resolveSelectURLToConfig = $1;",
+                                      ResolveSelectURLToConfig())));
     EvalJsResult result = EvalJs(iframe_node, R"(
       sharedStorage.selectURL(
-          'test-url-selection-operation',
-          [{url: "fenced_frames/title0.html"}],
-          {data: {'mockResult':0}});
+        'test-url-selection-operation',
+        [
+          {
+            url: "fenced_frames/title0.html"
+          }
+        ],
+        {
+          data: {'mockResult': 0},
+          resolveToConfig: resolveSelectURLToConfig
+        }
+      );
     )");
 
     EXPECT_EQ(
@@ -4460,7 +5194,7 @@
   WaitForHistograms({kTimingSelectUrlExecutedInWorkletHistogram});
 
   int expected_success_count =
-      GetParam() ? kMaxSelectURLCalls : kMaxSelectURLCalls + 1;
+      LimitSelectURLCalls() ? kMaxSelectURLCalls : kMaxSelectURLCalls + 1;
   histogram_tester_.ExpectTotalCount(kTimingSelectUrlExecutedInWorkletHistogram,
                                      expected_success_count);
 
@@ -4471,7 +5205,7 @@
         AccessType::kDocumentAddModule, MainFrameId(), origin_str,
         SharedStorageEventParams::CreateForAddModule(https_server()->GetURL(
             "b.test", "/shared_storage/simple_module.js")));
-    if (GetParam() && i == kMaxSelectURLCalls) {
+    if (LimitSelectURLCalls() && i == kMaxSelectURLCalls) {
       break;
     }
     expected_accesses.emplace_back(
@@ -4551,28 +5285,40 @@
 }
 
 class SharedStorageReportEventLimitBrowserTest
-    : public SharedStorageReportEventBrowserTest,
-      public testing::WithParamInterface<bool> {
+    : public SharedStorageFencedFrameInteractionBrowserTestBase,
+      public testing::WithParamInterface<std::tuple<bool, bool>> {
  public:
   SharedStorageReportEventLimitBrowserTest() {
-    if (GetParam()) {
-      feature_list_
-          .InitWithFeaturesAndParameters(/*enabled_features=*/
-                                         {{blink::features::
-                                               kSharedStorageReportEventLimit,
-                                           {{"SharedStorageReportEventBitBudget"
-                                             "PerPageLoad",
-                                             base::NumberToString(
-                                                 kReportEventBitBudget)}}}},
-                                         /*disabled_features=*/{});
+    if (LimitSharedStorageReportEventCalls()) {
+      report_event_feature_list_.InitWithFeaturesAndParameters(
+          /*enabled_features=*/
+          {{blink::features::kSharedStorageReportEventLimit,
+            {{"SharedStorageReportEventBitBudgetPerPageLoad",
+              base::NumberToString(kReportEventBitBudget)}}}},
+          /*disabled_features=*/{});
     } else {
-      feature_list_.InitWithFeaturesAndParameters(
+      report_event_feature_list_.InitWithFeaturesAndParameters(
           /*enabled_features=*/{},
           /*disabled_features=*/
           {blink::features::kSharedStorageReportEventLimit});
     }
+
+    fenced_frame_feature_list_.InitWithFeatureState(
+        blink::features::kFencedFramesAPIChanges, ResolveSelectURLToConfig());
   }
 
+  // Defer the server to start after `ControllableHttpResponse` is constructed.
+  void FinishSetup() override {
+    https_server()->ServeFilesFromSourceDirectory(GetTestDataFilePath());
+    https_server()->SetSSLConfig(net::EmbeddedTestServer::CERT_TEST_NAMES);
+  }
+
+  bool LimitSharedStorageReportEventCalls() const {
+    return std::get<0>(GetParam());
+  }
+
+  bool ResolveSelectURLToConfig() override { return std::get<1>(GetParam()); }
+
   // Precondition: `addModule('shared_storage/simple_module.js')` and
   // `selectURL()` have been called in the main frame.
   void RunSuccessfulReportEvents(
@@ -4605,19 +5351,19 @@
   }
 
  private:
-  base::test::ScopedFeatureList feature_list_;
+  base::test::ScopedFeatureList report_event_feature_list_;
+  base::test::ScopedFeatureList fenced_frame_feature_list_;
 };
 
-INSTANTIATE_TEST_SUITE_P(All,
-                         SharedStorageReportEventLimitBrowserTest,
-                         testing::Bool(),
-                         [](const auto& info) {
-                           if (info.param) {
-                             return "ReportEventLimit";
-                           } else {
-                             return "NoReportEventLimit";
-                           }
-                         });
+INSTANTIATE_TEST_SUITE_P(
+    All,
+    SharedStorageReportEventLimitBrowserTest,
+    testing::Combine(testing::Bool(), testing::Bool()),
+    [](const auto& info) {
+      return base::StrCat(
+          {"ReportEventLimit", std::get<0>(info.param) ? "Enabled" : "Disabled",
+           "_ResolveSelectURLTo", std::get<1>(info.param) ? "Config" : "URN"});
+    });
 
 IN_PROC_BROWSER_TEST_P(SharedStorageReportEventLimitBrowserTest,
                        ReportEvent_SameEntropyCalls_LimitReached) {
@@ -4653,9 +5399,43 @@
       .GetAttachedWorkletHost()
       ->WaitForWorkletResponsesCount(1);
 
+  EXPECT_TRUE(ExecJs(shell(), kGenerateURLsListScript));
+  EXPECT_TRUE(ExecJs(shell(), "window.urls = generateUrls(8);"));
+  EXPECT_TRUE(ExecJs(shell(), JsReplace("window.resolveSelectURLToConfig = $1;",
+                                        ResolveSelectURLToConfig())));
   std::vector<GURL> urns;
   for (size_t i = 0; i <= call_limit; ++i) {
-    urns.emplace_back(EvalJs(shell(), kSelectFrom8URLsScript).ExtractString());
+    TestSelectURLFencedFrameConfigObserver config_observer(
+        GetStoragePartition());
+    EvalJsResult result = EvalJs(shell(), R"(
+      (async function() {
+        window.select_url_result = await sharedStorage.selectURL(
+          'test-url-selection-operation',
+          urls,
+          {
+            data: {'mockResult': 1},
+            resolveToConfig: resolveSelectURLToConfig
+          }
+        );
+        if (resolveSelectURLToConfig &&
+            !(select_url_result instanceof FencedFrameConfig)) {
+          throw new Error('selectURL() did not return a FencedFrameConfig.');
+        }
+        return window.select_url_result;
+      })()
+    )");
+
+    EXPECT_TRUE(result.error.empty());
+    const absl::optional<GURL>& observed_urn_uuid =
+        config_observer.GetUrnUuid();
+    EXPECT_TRUE(observed_urn_uuid.has_value());
+    EXPECT_TRUE(blink::IsValidUrnUuidURL(observed_urn_uuid.value()));
+
+    if (!ResolveSelectURLToConfig()) {
+      EXPECT_EQ(result.ExtractString(), observed_urn_uuid->spec());
+    }
+
+    urns.push_back(observed_urn_uuid.value());
   }
 
   // There are `call_limit + 1` "worklet operations": `selectURL()`.
@@ -4672,7 +5452,7 @@
 
   FrameTreeNode* fenced_frame_root_node = CreateFencedFrame(urns[call_limit]);
 
-  if (GetParam()) {
+  if (LimitSharedStorageReportEventCalls()) {
     // The limit for `reportEvent()` has now been reached for this page. Make
     // one more call, which will be blocked.
     std::string click_event_data = "this is a click";
@@ -4733,10 +5513,78 @@
       ->WaitForWorkletResponsesCount(1);
 
   std::vector<GURL> urns;
-  urns.emplace_back(EvalJs(shell(), kSelectFrom8URLsScript).ExtractString());
+  EXPECT_TRUE(ExecJs(shell(), kGenerateURLsListScript));
+  EXPECT_TRUE(ExecJs(shell(), JsReplace("window.resolveSelectURLToConfig = $1;",
+                                        ResolveSelectURLToConfig())));
+
+  TestSelectURLFencedFrameConfigObserver select_from_8urls_config_observer(
+      GetStoragePartition());
+  EvalJsResult select_from_8urls_result = EvalJs(shell(), R"(
+      (async function() {
+        const urls_8 = generateUrls(8);
+        window.select_url_result = await sharedStorage.selectURL(
+          'test-url-selection-operation',
+          urls_8,
+          {
+            data: {'mockResult': 1},
+            resolveToConfig: resolveSelectURLToConfig
+          }
+        );
+        if (resolveSelectURLToConfig &&
+            !(select_url_result instanceof FencedFrameConfig)) {
+          throw new Error('selectURL() did not return a FencedFrameConfig.');
+        }
+        return window.select_url_result;
+      })()
+    )");
+
+  EXPECT_TRUE(select_from_8urls_result.error.empty());
+  const absl::optional<GURL>& observed_urn_uuid_from_8urls =
+      select_from_8urls_config_observer.GetUrnUuid();
+  EXPECT_TRUE(observed_urn_uuid_from_8urls.has_value());
+  EXPECT_TRUE(blink::IsValidUrnUuidURL(observed_urn_uuid_from_8urls.value()));
+
+  if (!ResolveSelectURLToConfig()) {
+    EXPECT_EQ(select_from_8urls_result.ExtractString(),
+              observed_urn_uuid_from_8urls->spec());
+  }
+
+  urns.push_back(observed_urn_uuid_from_8urls.value());
+  EXPECT_TRUE(ExecJs(shell(), "window.urls_4 = generateUrls(4);"));
 
   for (size_t i = 0; i <= input4_call_limit; ++i) {
-    urns.emplace_back(EvalJs(shell(), kSelectFrom4URLsScript).ExtractString());
+    TestSelectURLFencedFrameConfigObserver select_from_4urls_config_observer(
+        GetStoragePartition());
+    EvalJsResult select_from_4urls_result = EvalJs(shell(), R"(
+      (async function() {
+        window.select_url_result = await sharedStorage.selectURL(
+          'test-url-selection-operation',
+          urls_4,
+          {
+            data: {'mockResult': 1},
+            resolveToConfig: resolveSelectURLToConfig
+          }
+        );
+        if (resolveSelectURLToConfig &&
+            !(select_url_result instanceof FencedFrameConfig)) {
+          throw new Error('selectURL() did not return a FencedFrameConfig.');
+        }
+        return window.select_url_result;
+      })()
+    )");
+
+    EXPECT_TRUE(select_from_4urls_result.error.empty());
+    const absl::optional<GURL>& observed_urn_uuid_from_4urls =
+        select_from_4urls_config_observer.GetUrnUuid();
+    EXPECT_TRUE(observed_urn_uuid_from_4urls.has_value());
+    EXPECT_TRUE(blink::IsValidUrnUuidURL(observed_urn_uuid_from_4urls.value()));
+
+    if (!ResolveSelectURLToConfig()) {
+      EXPECT_EQ(select_from_4urls_result.ExtractString(),
+                observed_urn_uuid_from_4urls->spec());
+    }
+
+    urns.push_back(observed_urn_uuid_from_4urls.value());
   }
 
   // There are `input4_call_limit + 2` "worklet operations": `selectURL()`.
@@ -4745,31 +5593,31 @@
       ->WaitForWorkletResponsesCount(input4_call_limit + 2);
 
   // The first pair of `reportEvent()` calls will deduct 3 bits from the budget.
-  FrameTreeNode* fenced_frame_root_node0 = CreateFencedFrame(urns[0]);
+  FrameTreeNode* fenced_frame_root_node_0 = CreateFencedFrame(urns[0]);
 
-  RunSuccessfulReportEvents(fenced_frame_root_node0, responses[0].get(),
+  RunSuccessfulReportEvents(fenced_frame_root_node_0, responses[0].get(),
                             responses[1].get());
 
   for (size_t i = 1; i <= input4_call_limit; ++i) {
     // Subsequent pairs of calls to `reportEvent()` will deduct 2 bits from the
     // budget.
-    FrameTreeNode* fenced_frame_root_node1 = CreateFencedFrame(urns[i]);
+    FrameTreeNode* fenced_frame_root_node_1 = CreateFencedFrame(urns[i]);
 
-    RunSuccessfulReportEvents(fenced_frame_root_node1, responses[2 * i].get(),
+    RunSuccessfulReportEvents(fenced_frame_root_node_1, responses[2 * i].get(),
                               responses[2 * i + 1].get());
   }
 
-  FrameTreeNode* fenced_frame_root_node2 =
+  FrameTreeNode* fenced_frame_root_node_2 =
       CreateFencedFrame(urns[input4_call_limit + 1]);
 
   size_t current_response_index = 2 * (input4_call_limit + 1);
 
-  if (GetParam()) {
+  if (LimitSharedStorageReportEventCalls()) {
     // The limit for `reportEvent()` has now been reached for this page. Make
     // one more call, which will be blocked.
     std::string click_event_data = "this is a click";
     EXPECT_TRUE(
-        ExecJs(fenced_frame_root_node2,
+        ExecJs(fenced_frame_root_node_2,
                JsReplace("window.fence.reportEvent({"
                          "  eventType: 'click',"
                          "  eventData: $1,"
@@ -4783,12 +5631,12 @@
         base::UTF16ToUTF8(console_observer.messages().back().message));
 
     // Running the first pair of calls again will not cause any errors.
-    RunSuccessfulReportEvents(fenced_frame_root_node0,
+    RunSuccessfulReportEvents(fenced_frame_root_node_0,
                               responses[current_response_index].get(),
                               responses[current_response_index + 1].get());
   } else {
     // The `reportEvent()` limit is disabled. The calls will run successfully.
-    RunSuccessfulReportEvents(fenced_frame_root_node2,
+    RunSuccessfulReportEvents(fenced_frame_root_node_2,
                               responses[current_response_index].get(),
                               responses[current_response_index + 1].get());
   }
@@ -4820,14 +5668,48 @@
       .GetAttachedWorkletHost()
       ->WaitForWorkletResponsesCount(1);
 
-  GURL urn = GURL(EvalJs(shell(), kSelectFrom8URLsScript).ExtractString());
+  EXPECT_TRUE(ExecJs(shell(), kGenerateURLsListScript));
+  EXPECT_TRUE(ExecJs(shell(), JsReplace("window.resolveSelectURLToConfig = $1;",
+                                        ResolveSelectURLToConfig())));
+
+  TestSelectURLFencedFrameConfigObserver config_observer(GetStoragePartition());
+  EvalJsResult result = EvalJs(shell(), R"(
+      (async function() {
+        const urls = generateUrls(8);
+        window.select_url_result = await sharedStorage.selectURL(
+          'test-url-selection-operation',
+          urls,
+          {
+            data: {'mockResult': 1},
+            resolveToConfig: resolveSelectURLToConfig
+          }
+        );
+        if (resolveSelectURLToConfig &&
+            !(select_url_result instanceof FencedFrameConfig)) {
+          throw new Error('selectURL() did not return a FencedFrameConfig.');
+        }
+        return window.select_url_result;
+      })()
+    )");
+
+  EXPECT_TRUE(result.error.empty());
+  const absl::optional<GURL>& observed_urn_uuid = config_observer.GetUrnUuid();
+  EXPECT_TRUE(observed_urn_uuid.has_value());
+  EXPECT_TRUE(blink::IsValidUrnUuidURL(observed_urn_uuid.value()));
+
+  if (!ResolveSelectURLToConfig()) {
+    EXPECT_EQ(result.ExtractString(), observed_urn_uuid->spec());
+  }
 
   // There is one "worklet operation": `selectURL()`.
   test_worklet_host_manager()
       .GetAttachedWorkletHost()
       ->WaitForWorkletResponsesCount(1);
 
-  FrameTreeNode* fenced_frame_root_node = CreateFencedFrame(urn);
+  FrameTreeNode* fenced_frame_root_node = CreateFencedFrame(
+      ResolveSelectURLToConfig()
+          ? FencedFrameNavigationTarget("select_url_result")
+          : FencedFrameNavigationTarget(observed_urn_uuid.value()));
 
   RunSuccessfulReportEvents(fenced_frame_root_node, responses[0].get(),
                             responses[1].get());
@@ -4872,21 +5754,56 @@
       .GetAttachedWorkletHost()
       ->WaitForWorkletResponsesCount(1);
 
-  GURL urn = GURL(EvalJs(shell(), kSelectFrom8URLsScript).ExtractString());
+  EXPECT_TRUE(ExecJs(shell(), kGenerateURLsListScript));
+  EXPECT_TRUE(ExecJs(shell(), JsReplace("window.resolveSelectURLToConfig = $1;",
+                                        ResolveSelectURLToConfig())));
+
+  TestSelectURLFencedFrameConfigObserver config_observer(GetStoragePartition());
+  EvalJsResult result = EvalJs(shell(), R"(
+      (async function() {
+        const urls = generateUrls(8);
+        window.select_url_result = await sharedStorage.selectURL(
+          'test-url-selection-operation',
+          urls,
+          {
+            data: {'mockResult': 1},
+            resolveToConfig: resolveSelectURLToConfig
+          }
+        );
+        if (resolveSelectURLToConfig &&
+            !(select_url_result instanceof FencedFrameConfig)) {
+          throw new Error('selectURL() did not return a FencedFrameConfig.');
+        }
+        return window.select_url_result;
+      })()
+    )");
+
+  EXPECT_TRUE(result.error.empty());
+  const absl::optional<GURL>& observed_urn_uuid = config_observer.GetUrnUuid();
+  EXPECT_TRUE(observed_urn_uuid.has_value());
+  EXPECT_TRUE(blink::IsValidUrnUuidURL(observed_urn_uuid.value()));
+
+  if (!ResolveSelectURLToConfig()) {
+    EXPECT_EQ(result.ExtractString(), observed_urn_uuid->spec());
+  }
 
   // There is one "worklet operation": `selectURL()`.
   test_worklet_host_manager()
       .GetAttachedWorkletHost()
       ->WaitForWorkletResponsesCount(1);
 
-  FrameTreeNode* fenced_frame_root_node = CreateFencedFrame(urn);
+  FrameTreeNode* fenced_frame_root_node = CreateFencedFrame(
+      ResolveSelectURLToConfig()
+          ? FencedFrameNavigationTarget("select_url_result")
+          : FencedFrameNavigationTarget(observed_urn_uuid.value()));
 
   EXPECT_DOUBLE_EQ(GetRemainingBudget(shared_storage_origin), kBudgetAllowed);
   EXPECT_DOUBLE_EQ(RemainingBudgetViaJSForFrame(PrimaryFrameTreeNodeRoot()),
                    kBudgetAllowed);
 
   OpenPopup(fenced_frame_root_node,
-            https_server()->GetURL("b.test", kSimplePagePath), /*name=*/"");
+            https_server()->GetURL("b.test", kSimplePagePath),
+            /*name=*/"");
 
   // After the popup, log(8)=3 bits should have been withdrawn from the
   // original shared storage origin.
@@ -4919,35 +5836,35 @@
       MakeFilter({"The call to fence.reportEvent was blocked due to "
                   "insufficient budget."}));
 
-  url::Origin shared_storage_origin1 =
+  url::Origin shared_storage_origin_1 =
       url::Origin::Create(https_server()->GetURL("b.test", kSimplePagePath));
 
   // This call to `selectURL()` will have 8 input URLs, and hence
   // 3 = log2(8) bits of entropy.
-  GURL urn_uuid1 = SelectFrom8URLsInContext(shared_storage_origin1);
-  FrameTreeNode* outer_fenced_frame_root_node = CreateFencedFrame(urn_uuid1);
+  GURL urn_uuid_1 = SelectFrom8URLsInContext(shared_storage_origin_1);
+  FrameTreeNode* outer_fenced_frame_root_node = CreateFencedFrame(urn_uuid_1);
 
-  url::Origin shared_storage_origin2 =
+  url::Origin shared_storage_origin_2 =
       url::Origin::Create(https_server()->GetURL("c.test", kSimplePagePath));
 
   // This call to `selectURL()` will have 8 input URLs, and hence
   // 3 = log2(8) bits of entropy.
-  GURL urn_uuid2 = SelectFrom8URLsInContext(shared_storage_origin2,
-                                            outer_fenced_frame_root_node);
+  GURL urn_uuid_2 = SelectFrom8URLsInContext(shared_storage_origin_2,
+                                             outer_fenced_frame_root_node);
 
   FrameTreeNode* inner_fenced_frame_root_node =
-      CreateFencedFrame(outer_fenced_frame_root_node, urn_uuid2);
+      CreateFencedFrame(outer_fenced_frame_root_node, urn_uuid_2);
 
   RunSuccessfulReportEvents(inner_fenced_frame_root_node, responses[0].get(),
                             responses[1].get());
 
   // This call to `selectURL()` will have 8 input URLs, and hence
   // 3 = log2(8) bits of entropy.
-  GURL extra_urn = SelectFrom8URLsInContext(shared_storage_origin1);
+  GURL extra_urn = SelectFrom8URLsInContext(shared_storage_origin_1);
 
   FrameTreeNode* extra_fenced_frame_root_node = CreateFencedFrame(extra_urn);
 
-  if (GetParam()) {
+  if (LimitSharedStorageReportEventCalls()) {
     // The limit for `reportEvent()` has now been reached for this page. Make
     // one more call, which will be blocked.
     std::string click_event_data = "this is a click";
diff --git a/content/browser/shared_storage/shared_storage_worklet_host.cc b/content/browser/shared_storage/shared_storage_worklet_host.cc
index 23c77b3..32a23fa 100644
--- a/content/browser/shared_storage/shared_storage_worklet_host.cc
+++ b/content/browser/shared_storage/shared_storage_worklet_host.cc
@@ -136,12 +136,16 @@
     const GURL& urn_uuid = it->first;
 
     bool failed_due_to_no_budget = false;
-    page_->fenced_frame_urls_map().OnSharedStorageURNMappingResultDetermined(
-        urn_uuid,
-        CreateSharedStorageURNMappingResult(
-            storage_partition_, browser_context_, shared_storage_origin_,
-            std::move(it->second),
-            /*index=*/0, /*budget_remaining=*/0.0, failed_due_to_no_budget));
+    absl::optional<FencedFrameConfig> config =
+        page_->fenced_frame_urls_map()
+            .OnSharedStorageURNMappingResultDetermined(
+                urn_uuid, CreateSharedStorageURNMappingResult(
+                              storage_partition_, browser_context_,
+                              shared_storage_origin_, std::move(it->second),
+                              /*index=*/0, /*budget_remaining=*/0.0,
+                              failed_due_to_no_budget));
+
+    shared_storage_worklet_host_manager_->NotifyConfigPopulated(config);
 
     it = unresolved_urns_.erase(it);
   }
@@ -265,6 +269,8 @@
       /*result_config=*/
       config.RedactFor(FencedFrameEntity::kEmbedder));
 
+  shared_storage_worklet_host_manager_->NotifyUrnUuidGenerated(urn_uuid);
+
   GetAndConnectToSharedStorageWorkletService()->RunURLSelectionOperation(
       name, urls, serialized_data,
       base::BindOnce(
@@ -774,8 +780,12 @@
       }
     }
 
-    page_->fenced_frame_urls_map().OnSharedStorageURNMappingResultDetermined(
-        urn_uuid, std::move(mapping_result));
+    absl::optional<FencedFrameConfig> config =
+        page_->fenced_frame_urls_map()
+            .OnSharedStorageURNMappingResultDetermined(
+                urn_uuid, std::move(mapping_result));
+
+    shared_storage_worklet_host_manager_->NotifyConfigPopulated(config);
   }
 
   base::UmaHistogramLongTimes(
diff --git a/content/browser/shared_storage/shared_storage_worklet_host_manager.cc b/content/browser/shared_storage/shared_storage_worklet_host_manager.cc
index 593846a..4ef52e47 100644
--- a/content/browser/shared_storage/shared_storage_worklet_host_manager.cc
+++ b/content/browser/shared_storage/shared_storage_worklet_host_manager.cc
@@ -101,4 +101,18 @@
   keep_alive_shared_storage_worklet_hosts_.erase(worklet_host);
 }
 
+void SharedStorageWorkletHostManager::NotifyUrnUuidGenerated(
+    const GURL& urn_uuid) {
+  for (SharedStorageObserverInterface& observer : observers_) {
+    observer.OnUrnUuidGenerated(urn_uuid);
+  }
+}
+
+void SharedStorageWorkletHostManager::NotifyConfigPopulated(
+    const absl::optional<FencedFrameConfig>& config) {
+  for (SharedStorageObserverInterface& observer : observers_) {
+    observer.OnConfigPopulated(config);
+  }
+}
+
 }  // namespace content
diff --git a/content/browser/shared_storage/shared_storage_worklet_host_manager.h b/content/browser/shared_storage/shared_storage_worklet_host_manager.h
index 3850e9f..032ba05 100644
--- a/content/browser/shared_storage/shared_storage_worklet_host_manager.h
+++ b/content/browser/shared_storage/shared_storage_worklet_host_manager.h
@@ -17,6 +17,7 @@
 
 namespace content {
 
+struct FencedFrameConfig;
 class SharedStorageDocumentServiceImpl;
 class SharedStorageWorkletDriver;
 class SharedStorageWorkletHost;
@@ -55,6 +56,11 @@
         const std::string& main_frame_id,
         const std::string& owner_origin,
         const SharedStorageEventParams& params) = 0;
+
+    virtual void OnUrnUuidGenerated(const GURL& urn_uuid) = 0;
+
+    virtual void OnConfigPopulated(
+        const absl::optional<FencedFrameConfig>& config) = 0;
   };
 
   void OnDocumentServiceDestroyed(
@@ -85,6 +91,10 @@
     return keep_alive_shared_storage_worklet_hosts_;
   }
 
+  void NotifyUrnUuidGenerated(const GURL& urn_uuid);
+
+  void NotifyConfigPopulated(const absl::optional<FencedFrameConfig>& config);
+
  protected:
   void OnWorkletKeepAliveFinished(SharedStorageWorkletHost*);
 
diff --git a/content/browser/speech/tts_controller_impl.cc b/content/browser/speech/tts_controller_impl.cc
index a083e7b..203bcf9 100644
--- a/content/browser/speech/tts_controller_impl.cc
+++ b/content/browser/speech/tts_controller_impl.cc
@@ -61,6 +61,14 @@
   return static_cast<TtsUtteranceImpl*>(utterance);
 }
 
+bool IsUtteranceSpokenByRemoteEngine(TtsUtterance* utterance) {
+  if (utterance && !utterance->GetEngineId().empty()) {
+    TtsUtteranceImpl* utterance_impl = AsUtteranceImpl(utterance);
+    return utterance_impl->spoken_by_remote_engine();
+  }
+  return false;
+}
+
 }  // namespace
 
 //
@@ -233,13 +241,8 @@
 }
 
 void TtsControllerImpl::StopCurrentUtterance() {
-  bool spoken_by_remote_engine = false;
-  if (current_utterance_ && !current_utterance_->GetEngineId().empty()) {
-    TtsUtteranceImpl* utterance_impl =
-        AsUtteranceImpl(current_utterance_.get());
-    spoken_by_remote_engine = utterance_impl->spoken_by_remote_engine();
-  }
-
+  bool spoken_by_remote_engine =
+      IsUtteranceSpokenByRemoteEngine(current_utterance_.get());
   if (engine_delegate_ && current_utterance_ &&
       !current_utterance_->GetEngineId().empty() && !spoken_by_remote_engine) {
     engine_delegate_->Stop(current_utterance_.get());
@@ -261,13 +264,25 @@
 
 void TtsControllerImpl::Pause() {
   base::RecordAction(base::UserMetricsAction("TextToSpeech.Pause"));
+
+  auto* external_delegate = GetTtsPlatform()->GetExternalPlatformDelegate();
+  if (external_delegate) {
+    external_delegate->Pause();
+    return;
+  }
+
   if (paused_)
     return;
 
   paused_ = true;
+  bool spoken_by_remote_engine =
+      IsUtteranceSpokenByRemoteEngine(current_utterance_.get());
   if (engine_delegate_ && current_utterance_ &&
-      !current_utterance_->GetEngineId().empty()) {
+      !current_utterance_->GetEngineId().empty() && !spoken_by_remote_engine) {
     engine_delegate_->Pause(current_utterance_.get());
+  } else if (current_utterance_ && !current_utterance_->GetEngineId().empty() &&
+             spoken_by_remote_engine && remote_engine_delegate_) {
+    remote_engine_delegate_->Pause(current_utterance_.get());
   } else if (current_utterance_) {
     DCHECK(TtsPlatformReady());
     GetTtsPlatform()->ClearError();
@@ -277,13 +292,24 @@
 
 void TtsControllerImpl::Resume() {
   base::RecordAction(base::UserMetricsAction("TextToSpeech.Resume"));
+  auto* external_delegate = GetTtsPlatform()->GetExternalPlatformDelegate();
+  if (external_delegate) {
+    external_delegate->Resume();
+    return;
+  }
+
   if (!paused_)
     return;
 
   paused_ = false;
+  bool spoken_by_remote_engine =
+      IsUtteranceSpokenByRemoteEngine(current_utterance_.get());
   if (engine_delegate_ && current_utterance_ &&
-      !current_utterance_->GetEngineId().empty()) {
+      !current_utterance_->GetEngineId().empty() && !spoken_by_remote_engine) {
     engine_delegate_->Resume(current_utterance_.get());
+  } else if (current_utterance_ && !current_utterance_->GetEngineId().empty() &&
+             spoken_by_remote_engine && remote_engine_delegate_) {
+    remote_engine_delegate_->Resume(current_utterance_.get());
   } else if (current_utterance_) {
     DCHECK(TtsPlatformReady());
     GetTtsPlatform()->ClearError();
diff --git a/content/browser/speech/tts_controller_unittest.cc b/content/browser/speech/tts_controller_unittest.cc
index 06057705..2f59ab28 100644
--- a/content/browser/speech/tts_controller_unittest.cc
+++ b/content/browser/speech/tts_controller_unittest.cc
@@ -148,10 +148,6 @@
 
   // Count API calls (TtsEngineDelegate:)
   void Stop(TtsUtterance* utterance) override { ++stop_called_; }
-  void Stop(BrowserContext* browser_context,
-            const std::string& engine_id) override {
-    ++stop_called_;
-  }
   void Pause(TtsUtterance* utterance) override { ++pause_called_; }
   void Resume(TtsUtterance* utterance) override { ++resume_called_; }
 
diff --git a/content/public/android/BUILD.gn b/content/public/android/BUILD.gn
index 9c56059..13fefa88 100644
--- a/content/public/android/BUILD.gn
+++ b/content/public/android/BUILD.gn
@@ -196,6 +196,7 @@
 
   sources = [
     "java/src/org/chromium/content/browser/AppWebMessagePort.java",
+    "java/src/org/chromium/content/browser/AttributionOsLevelManager.java",
     "java/src/org/chromium/content/browser/AudioFocusDelegate.java",
     "java/src/org/chromium/content/browser/BackgroundSyncNetworkObserver.java",
     "java/src/org/chromium/content/browser/BindingManager.java",
@@ -419,6 +420,7 @@
     "java/src/org/chromium/content/app/ContentChildProcessServiceDelegate.java",
     "java/src/org/chromium/content/app/ContentMain.java",
     "java/src/org/chromium/content/browser/AppWebMessagePort.java",
+    "java/src/org/chromium/content/browser/AttributionOsLevelManager.java",
     "java/src/org/chromium/content/browser/AudioFocusDelegate.java",
     "java/src/org/chromium/content/browser/BackgroundSyncNetworkObserver.java",
     "java/src/org/chromium/content/browser/BrowserContextHandleImpl.java",
diff --git a/content/public/android/java/src/org/chromium/content/browser/AttributionOsLevelManager.java b/content/public/android/java/src/org/chromium/content/browser/AttributionOsLevelManager.java
new file mode 100644
index 0000000..bbd6ff3
--- /dev/null
+++ b/content/public/android/java/src/org/chromium/content/browser/AttributionOsLevelManager.java
@@ -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.
+
+package org.chromium.content.browser;
+
+import org.chromium.base.annotations.CalledByNative;
+import org.chromium.url.GURL;
+
+/**
+ * Handles passing registrations with Web Attribution Reporting API to the underlying native
+ * library.
+ */
+public class AttributionOsLevelManager {
+    private long mNativePtr;
+
+    @CalledByNative
+    private AttributionOsLevelManager(long nativePtr) {
+        mNativePtr = nativePtr;
+    }
+
+    /**
+     * Registers a web attribution source with native, see `registerWebSource()`:
+     * https://developer.android.com/design-for-safety/privacy-sandbox/reference/adservices/measurement/MeasurementManager.
+     */
+    @CalledByNative
+    private void registerAttributionSource(
+            GURL registrationUrl, GURL topLevelOrigin, boolean isDebugKeyAllowed) {
+        // TODO(johnidel): Register with the Android API, see
+        // https://developer.android.com/design-for-safety/privacy-sandbox/guides/attribution.
+        // This is dependent on support for the Tiramisu Privacy Sandbox SDK.
+    }
+
+    @CalledByNative
+    private void nativeDestroyed() {
+        mNativePtr = 0;
+    }
+}
diff --git a/content/public/browser/tts_controller.h b/content/public/browser/tts_controller.h
index 3d17c9a..97a1816 100644
--- a/content/public/browser/tts_controller.h
+++ b/content/public/browser/tts_controller.h
@@ -65,17 +65,10 @@
   // Stop speaking the given utterance by sending an event to the target
   // associated with this utterance.
   virtual void Stop(TtsUtterance* utterance) = 0;
-
-  // Stop the given speech engine loaded in |browser_context|.
-  virtual void Stop(BrowserContext* browser_context,
-                    const std::string& engine_id) = 0;
-
   // Pause in the middle of speaking this utterance.
   virtual void Pause(TtsUtterance* utterance) = 0;
-
   // Resume speaking this utterance.
   virtual void Resume(TtsUtterance* utterance) = 0;
-
   // Load the built-in TTS engine.
   virtual void LoadBuiltInTtsEngine(BrowserContext* browser_context) = 0;
 
@@ -100,6 +93,14 @@
   // Requests the remote TTS engine associated with |utterance| to stop
   // speaking the |utterance|.
   virtual void Stop(TtsUtterance* utterance) = 0;
+
+  // Requests the remote TTS engine associated with |utterance| to pause
+  // speaking the |utterance|.
+  virtual void Pause(TtsUtterance* utterance) = 0;
+
+  // Requests the remote TTS engine associated with |utterance| to resume
+  // speaking the |utterance|.
+  virtual void Resume(TtsUtterance* utterance) = 0;
 };
 
 // Class that wants to be notified when the set of
diff --git a/content/public/browser/tts_platform.h b/content/public/browser/tts_platform.h
index 5c80cf0..53f23a0 100644
--- a/content/public/browser/tts_platform.h
+++ b/content/public/browser/tts_platform.h
@@ -58,6 +58,12 @@
   // Requests external TtsController to stop the current utterance if it matches
   // the given |source_url|.
   virtual void Stop(const GURL& source_url) = 0;
+
+  // Requests external TtsController to pause speech synthesis.
+  virtual void Pause() = 0;
+
+  // Requests external TtsController to resume speech synthesis.
+  virtual void Resume() = 0;
 };
 
 // Abstract class that defines the native platform TTS interface,
diff --git a/content/public/test/browser_test_base.cc b/content/public/test/browser_test_base.cc
index 986dd5c..f29b7b99 100644
--- a/content/public/test/browser_test_base.cc
+++ b/content/public/test/browser_test_base.cc
@@ -123,16 +123,10 @@
 #endif
 
 #if BUILDFLAG(IS_CHROMEOS_LACROS)
-#include "base/base_switches.h"
-#include "base/files/file_path_watcher.h"
-#include "base/files/file_util.h"
 #include "base/files/scoped_file.h"
-#include "base/strings/string_util.h"
-#include "base/test/task_environment.h"
 #include "chromeos/crosapi/cpp/crosapi_constants.h"  // nogncheck
 #include "chromeos/lacros/lacros_test_helper.h"
 #include "chromeos/startup/startup_switches.h"  // nogncheck
-#include "content/public/test/browser_test_switches.h"
 #include "mojo/public/cpp/platform/socket_utils_posix.h"
 #endif
 
@@ -436,35 +430,17 @@
       disable_crosapi_ =
           std::make_unique<chromeos::ScopedDisableCrosapiForTesting>();
     } else {
-      bool connected_to_ash = false;
-      int retry_left = 2;
-      base::ScopedFD socket_fd;
-      int flags = 0;
-      while (retry_left-- > 0 && !connected_to_ash) {
-        auto channel = mojo::NamedPlatformChannel::ConnectToServer(socket_path);
-        socket_fd = channel.TakePlatformHandle().TakeFD();
+      auto channel = mojo::NamedPlatformChannel::ConnectToServer(socket_path);
+      base::ScopedFD socket_fd = channel.TakePlatformHandle().TakeFD();
 
-        // Mark the channel as blocking.
-        flags = fcntl(socket_fd.get(), F_GETFL);
-
-        connected_to_ash = flags != -1;
-
-        if (!connected_to_ash) {
-          LOG(WARNING) << "Ash is probably not running. Perhaps it crashed?"
-                       << " Try to start ash again.";
-          StartAshChrome();
-        }
-      }
+      // Mark the channel as blocking.
+      int flags = fcntl(socket_fd.get(), F_GETFL);
       std::string helper_msg =
           "On bot, open CAS outputs on test result page(Milo),"
           "there is a ash_chrome.log file which contains ash log."
           "For local debugging, pass in --ash-logging-path to test runner.";
-      if (connected_to_ash) {
-        LOG(INFO) << "Connected to ash.";
-      } else {
-        LOG(FATAL) << "Ash is probably not running. Perhaps it crashed?"
-                   << helper_msg;
-      }
+      PCHECK(flags != -1) << "Ash is probably not running. Perhaps it crashed?"
+                          << helper_msg;
       fcntl(socket_fd.get(), F_SETFL, flags & ~O_NONBLOCK);
 
       uint8_t buf[32];
@@ -1165,82 +1141,4 @@
   return nullptr;
 }
 
-#if BUILDFLAG(IS_CHROMEOS_LACROS)
-void BrowserTestBase::StartAshChrome() {
-  base::test::SingleThreadTaskEnvironment task_environment;
-  base::CommandLine* cmdline = base::CommandLine::ForCurrentProcess();
-
-  base::FilePath mojo_socket_file =
-      cmdline->GetSwitchValuePath("lacros-mojo-socket-for-testing");
-  if (mojo_socket_file.empty()) {
-    LOG(WARNING) << "Start ash failed because of missing "
-                 << "--lacros-mojo-socket-for-testing";
-    return;
-  }
-  // Delete the existing file because we will reuse the same file for the new
-  // ash chrome instance.
-  CHECK(base::DeleteFile(mojo_socket_file));
-  base::FilePath ash_chrome_path =
-      cmdline->GetSwitchValuePath("ash-chrome-path");
-  CHECK(!ash_chrome_path.empty());
-
-  base::CommandLine ash_cmdline(ash_chrome_path);
-  base::FilePath ash_user_data_dir =
-      cmdline->GetSwitchValuePath(content::test::switches::kAshUserDataDir);
-  CHECK(base::DeletePathRecursively(ash_user_data_dir));
-  ash_cmdline.AppendSwitchPath("user-data-dir", ash_user_data_dir);
-  ash_cmdline.AppendSwitch("enable-wayland-server");
-  ash_cmdline.AppendSwitch("no-startup-window");
-  ash_cmdline.AppendSwitch("disable-lacros-keep-alive");
-  ash_cmdline.AppendSwitch("disable-login-lacros-opening");
-
-  std::string ash_features = "LacrosSupport,LacrosPrimary,LacrosOnly";
-  ash_cmdline.AppendSwitchASCII(switches::kEnableFeatures, ash_features);
-
-  ash_cmdline.AppendSwitchPath("lacros-mojo-socket-for-testing",
-                               mojo_socket_file);
-
-  std::string wayland_socket;
-  CHECK(
-      base::Environment::Create()->GetVar("WAYLAND_DISPLAY", &wayland_socket));
-  DCHECK(wayland_socket.length() > 0);
-  ash_cmdline.AppendSwitchASCII("wayland-server-socket", wayland_socket);
-
-  const std::string ash_ready_file =
-      ash_user_data_dir.AppendASCII("ash_ready.txt").MaybeAsASCII();
-  ash_cmdline.AppendSwitchASCII(content::test::switches::kAshReadyFilePath,
-                                ash_ready_file);
-
-  base::FilePathWatcher watcher;
-  base::RunLoop run_loop;
-  CHECK(watcher.Watch(base::FilePath(ash_ready_file),
-                      base::FilePathWatcher::Type::kNonRecursive,
-                      base::BindLambdaForTesting(
-                          [&](const base::FilePath& filepath, bool error) {
-                            CHECK(!error);
-                            run_loop.Quit();
-                          })));
-  base::LaunchOptions option;
-  option.new_process_group = true;
-  base::Process process = base::LaunchProcess(ash_cmdline, option);
-  CHECK(process.IsValid());
-  run_loop.Run();
-  // When ash is ready and crosapi was enabled, we expect mojo socket is
-  // also ready.
-  CHECK(base::PathExists(mojo_socket_file));
-  base::FilePath ash_processes_dir = cmdline->GetSwitchValuePath(
-      content::test::switches::kAshProcessesDirPath);
-  CHECK(!ash_processes_dir.empty());
-  base::FilePath temp_filepath;
-  std::string str_pid = base::StringPrintf("%d", process.Pid());
-  base::File f =
-      CreateAndOpenTemporaryFileInDir(ash_processes_dir, &temp_filepath);
-  int ret_code = f.Write(0, str_pid.c_str(), str_pid.length());
-  CHECK(ret_code >= 0 && (unsigned int)ret_code == str_pid.length())
-      << "Cannot write to file " << temp_filepath.AsUTF8Unsafe()
-      << "Return code is " << ret_code;
-  f.Close();
-}
-#endif
-
 }  // namespace content
diff --git a/content/public/test/browser_test_base.h b/content/public/test/browser_test_base.h
index 28072eb8..1bc8e19b 100644
--- a/content/public/test/browser_test_base.h
+++ b/content/public/test/browser_test_base.h
@@ -102,10 +102,6 @@
   // needed when triggering the crash via SimulateNetworkServiceCrash method.
   void IgnoreNetworkServiceCrashes();
 
-#if BUILDFLAG(IS_CHROMEOS_LACROS)
-  void StartAshChrome();
-#endif
-
   // Returns the host resolver being used for the tests. Subclasses might want
   // to configure it inside tests.
   net::RuleBasedHostResolverProc* host_resolver() {
diff --git a/content/public/test/browser_test_switches.cc b/content/public/test/browser_test_switches.cc
deleted file mode 100644
index e15cac5..0000000
--- a/content/public/test/browser_test_switches.cc
+++ /dev/null
@@ -1,23 +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 "content/public/test/browser_test_switches.h"
-
-#if BUILDFLAG(IS_CHROMEOS_LACROS)
-// The file path to indicate if ash is ready for testing.
-// The file should not be on the file system initially. After
-// ash is ready for testing, the file will be created.
-// This is used to communicate between launcher and runner process. In general
-// you should not pass in this arg directly.
-const char content::test::switches::kAshReadyFilePath[] = "ash-ready-file-path";
-
-// Ash chrome user data dir path.
-const char content::test::switches::kAshUserDataDir[] = "ash-user-data-dir";
-
-// A dir to store all the pids of ash chrome created during tests.
-// This is used to communicate between launcher and runner process. In general
-// you should not pass in this arg directly.
-const char content::test::switches::kAshProcessesDirPath[] =
-    "ash-processes-dir-path";
-#endif
diff --git a/content/public/test/browser_test_switches.h b/content/public/test/browser_test_switches.h
deleted file mode 100644
index 5cdfba8..0000000
--- a/content/public/test/browser_test_switches.h
+++ /dev/null
@@ -1,23 +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.
-
-// This file should contain test switches for non unit gtests. Like
-// browser test or interactive ui test.
-
-#ifndef CONTENT_PUBLIC_TEST_BROWSER_TEST_SWITCHES_H_
-#define CONTENT_PUBLIC_TEST_BROWSER_TEST_SWITCHES_H_
-
-#include "build/chromeos_buildflags.h"
-
-namespace content::test::switches {
-
-#if BUILDFLAG(IS_CHROMEOS_LACROS)
-extern const char kAshReadyFilePath[];
-extern const char kAshUserDataDir[];
-extern const char kAshProcessesDirPath[];
-#endif
-
-}  // namespace content::test::switches
-
-#endif  // CONTENT_PUBLIC_TEST_BROWSER_TEST_SWITCHES_H_
diff --git a/content/public/test/shared_storage_test_utils.cc b/content/public/test/shared_storage_test_utils.cc
index 919b9e9..2d34c4ef 100644
--- a/content/public/test/shared_storage_test_utils.cc
+++ b/content/public/test/shared_storage_test_utils.cc
@@ -6,7 +6,7 @@
 
 #include <map>
 
-#include "base/task/task_runner.h"
+#include "base/functional/overloaded.h"
 #include "content/browser/renderer_host/frame_tree_node.h"
 #include "content/browser/renderer_host/render_frame_host_impl.h"
 #include "content/browser/shared_storage/shared_storage_document_service_impl.h"
@@ -22,8 +22,6 @@
 
 namespace content {
 
-namespace {
-
 SharedStorageWorkletHostManager*
 GetSharedStorageWorkletHostManagerForStoragePartition(
     StoragePartition* storage_partition) {
@@ -31,8 +29,6 @@
       ->GetSharedStorageWorkletHostManager();
 }
 
-}  // namespace
-
 std::string GetSharedStorageDisabledMessage() {
   return kSharedStorageDisabledMessage;
 }
@@ -66,7 +62,8 @@
   return manager->GetKeepAliveWorkletHostsForTesting().size();
 }
 
-RenderFrameHost* CreateFencedFrame(RenderFrameHost* root, const GURL& url) {
+RenderFrameHost* CreateFencedFrame(RenderFrameHost* root,
+                                   const FencedFrameNavigationTarget& target) {
   FrameTreeNode* root_node =
       static_cast<RenderFrameHostImpl*>(root)->frame_tree_node();
   size_t initial_child_count = root_node->child_count();
@@ -80,18 +77,28 @@
   FrameTreeNode* fenced_frame_root_node =
       GetFencedFrameRootNode(root_node->child_at(initial_child_count));
 
-  std::string navigate_fenced_frame_script =
-      JsReplace("f.src = $1;", url.spec());
-
   TestFrameNavigationObserver observer(
       fenced_frame_root_node->current_frame_host());
 
-  EXPECT_EQ(url.spec(), EvalJs(root, navigate_fenced_frame_script));
+  EvalJsResult result = EvalJs(
+      root,
+      absl::visit(
+          base::Overloaded{
+              [](const GURL& url) { return JsReplace("f.src = $1;", url); },
+              [](const std::string& config) {
+                return JsReplace("f.config =  window[$1]", config);
+              },
+          },
+          target));
 
   observer.Wait();
 
-  return static_cast<RenderFrameHost*>(
-      fenced_frame_root_node->current_frame_host());
+  EXPECT_TRUE(result.error.empty());
+  if (absl::holds_alternative<GURL>(target)) {
+    EXPECT_EQ(result, absl::get<GURL>(target).spec());
+  }
+
+  return fenced_frame_root_node->current_frame_host();
 }
 
 }  // namespace content
diff --git a/content/public/test/shared_storage_test_utils.h b/content/public/test/shared_storage_test_utils.h
index aee8684..7938309 100644
--- a/content/public/test/shared_storage_test_utils.h
+++ b/content/public/test/shared_storage_test_utils.h
@@ -8,13 +8,22 @@
 #include <stddef.h>
 #include <string>
 
+#include "third_party/abseil-cpp/absl/types/variant.h"
+
 class GURL;
 
 namespace content {
 
 class RenderFrameHost;
+class SharedStorageWorkletHostManager;
 class StoragePartition;
 
+using FencedFrameNavigationTarget = absl::variant<GURL, std::string>;
+
+SharedStorageWorkletHostManager*
+GetSharedStorageWorkletHostManagerForStoragePartition(
+    StoragePartition* storage_partition);
+
 std::string GetSharedStorageDisabledMessage();
 
 std::string GetSharedStorageSelectURLDisabledMessage();
@@ -29,7 +38,10 @@
 size_t GetKeepAliveSharedStorageWorkletHostsCount(
     StoragePartition* storage_partition);
 
-RenderFrameHost* CreateFencedFrame(RenderFrameHost* root, const GURL& url);
+// TODO(crbug.com/1414429): This function should be removed. Use
+// `CreateFencedFrame` in fenced_frame_test_util.h instead.
+RenderFrameHost* CreateFencedFrame(RenderFrameHost* root,
+                                   const FencedFrameNavigationTarget& target);
 
 }  // namespace content
 
diff --git a/content/public/test/test_select_url_fenced_frame_config_observer.h b/content/public/test/test_select_url_fenced_frame_config_observer.h
new file mode 100644
index 0000000..fbbe61c7
--- /dev/null
+++ b/content/public/test/test_select_url_fenced_frame_config_observer.h
@@ -0,0 +1,51 @@
+// Copyright 2023 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CONTENT_PUBLIC_TEST_TEST_SELECT_URL_FENCED_FRAME_CONFIG_OBSERVER_H_
+#define CONTENT_PUBLIC_TEST_TEST_SELECT_URL_FENCED_FRAME_CONFIG_OBSERVER_H_
+
+#include <memory>
+
+#include "base/memory/raw_ptr.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
+
+class GURL;
+
+namespace content {
+
+struct FencedFrameConfig;
+class StoragePartition;
+class TestSelectURLFencedFrameConfigObserverImpl;
+
+// This observes:
+// 1. the next generated urn::uuid.
+// 2. the next populated fenced frame config that contains the observed
+// urn::uuid.
+//
+// With the fenced frame API change, `selectURL()` can return a fenced frame
+// config object instead of an urn::uuid. The urn::uuid in the returned config
+// is opaque, but it is often needed to check the associated info
+// (e.g. SharedStorageBudgetMetadata). Tests can use this observer to obtain the
+// urn::uuid and the browser-side FencedFrameConfig.
+//
+// This observes only the first url::uuid and config. To observe a new one, a
+// new observer must be created before calling `selectURL().
+class TestSelectURLFencedFrameConfigObserver {
+ public:
+  explicit TestSelectURLFencedFrameConfigObserver(
+      StoragePartition* storage_partition);
+  ~TestSelectURLFencedFrameConfigObserver();
+
+  const absl::optional<GURL>& GetUrnUuid() const;
+  const absl::optional<FencedFrameConfig>& GetConfig() const;
+  bool ConfigObserved() const;
+
+ private:
+  raw_ptr<StoragePartition> storage_partition_;
+  std::unique_ptr<TestSelectURLFencedFrameConfigObserverImpl> impl_;
+};
+
+}  // namespace content
+
+#endif  // CONTENT_PUBLIC_TEST_TEST_SELECT_URL_FENCED_FRAME_CONFIG_OBSERVER_H_
diff --git a/content/services/auction_worklet/seller_worklet.cc b/content/services/auction_worklet/seller_worklet.cc
index dfbaa5a1..0de4af1 100644
--- a/content/services/auction_worklet/seller_worklet.cc
+++ b/content/services/auction_worklet/seller_worklet.cc
@@ -72,6 +72,46 @@
   return v8_helper->InsertValue(key, v8_priority_signals, object);
 }
 
+// Attempts to create an v8 Object from `maybe_promise_buyer_timeouts`. On fatal
+// error, returns false. Otherwise, writes the result to
+// `out_per_buyer_timeouts`, which will be left unchanged if there are no times
+// to write to it.
+bool CreatePerBuyerTimeoutsObject(
+    v8::Isolate* isolate,
+    const blink::AuctionConfig::MaybePromiseBuyerTimeouts&
+        maybe_promise_buyer_timeouts,
+    v8::Local<v8::Object>& out_per_buyer_timeouts) {
+  DCHECK(!maybe_promise_buyer_timeouts.is_promise());
+
+  const blink::AuctionConfig::BuyerTimeouts& buyer_timeouts =
+      maybe_promise_buyer_timeouts.value();
+  // If there are no times, leave `out_per_buyer_timeouts` empty, and indicate
+  // success.
+  if (!buyer_timeouts.per_buyer_timeouts.has_value() &&
+      !buyer_timeouts.all_buyers_timeout.has_value()) {
+    return true;
+  }
+
+  out_per_buyer_timeouts = v8::Object::New(isolate);
+  gin::Dictionary per_buyer_timeouts_dict(isolate, out_per_buyer_timeouts);
+
+  if (buyer_timeouts.per_buyer_timeouts.has_value()) {
+    for (const auto& kv : buyer_timeouts.per_buyer_timeouts.value()) {
+      if (!per_buyer_timeouts_dict.Set(kv.first.Serialize(),
+                                       kv.second.InMilliseconds())) {
+        return false;
+      }
+    }
+  }
+  if (buyer_timeouts.all_buyers_timeout.has_value()) {
+    if (!per_buyer_timeouts_dict.Set(
+            "*", buyer_timeouts.all_buyers_timeout.value().InMilliseconds())) {
+      return false;
+    }
+  }
+  return true;
+}
+
 // Converts `auction_config` back to JSON format, and appends to args.
 // Returns true if conversion succeeded.
 //
@@ -172,30 +212,27 @@
   }
 
   v8::Local<v8::Object> per_buyer_timeouts;
-  DCHECK(!auction_ad_config_non_shared_params.buyer_timeouts.is_promise());
-  const blink::AuctionConfig::BuyerTimeouts& buyer_timeouts =
-      auction_ad_config_non_shared_params.buyer_timeouts.value();
-  if (buyer_timeouts.per_buyer_timeouts.has_value() ||
-      buyer_timeouts.all_buyers_timeout.has_value()) {
-    per_buyer_timeouts = v8::Object::New(isolate);
-    gin::Dictionary per_buyer_timeouts_dict(isolate, per_buyer_timeouts);
-    if (buyer_timeouts.per_buyer_timeouts.has_value()) {
-      for (const auto& kv : buyer_timeouts.per_buyer_timeouts.value()) {
-        if (!per_buyer_timeouts_dict.Set(kv.first.Serialize(),
-                                         kv.second.InMilliseconds())) {
-          return false;
-        }
-      }
-    }
-    if (buyer_timeouts.all_buyers_timeout.has_value()) {
-      if (!per_buyer_timeouts_dict.Set(
-              "*", buyer_timeouts.all_buyers_timeout->InMilliseconds())) {
-        return false;
-      }
-    }
+  if (!CreatePerBuyerTimeoutsObject(
+          isolate, auction_ad_config_non_shared_params.buyer_timeouts,
+          per_buyer_timeouts)) {
+    return false;
+  }
+  if (!per_buyer_timeouts.IsEmpty()) {
     auction_config_dict.Set("perBuyerTimeouts", per_buyer_timeouts);
   }
 
+  v8::Local<v8::Object> per_buyer_cumulative_timeouts;
+  if (!CreatePerBuyerTimeoutsObject(
+          isolate,
+          auction_ad_config_non_shared_params.buyer_cumulative_timeouts,
+          per_buyer_cumulative_timeouts)) {
+    return false;
+  }
+  if (!per_buyer_cumulative_timeouts.IsEmpty()) {
+    auction_config_dict.Set("perBuyerCumulativeTimeouts",
+                            per_buyer_cumulative_timeouts);
+  }
+
   if (auction_ad_config_non_shared_params.per_buyer_priority_signals ||
       auction_ad_config_non_shared_params.all_buyers_priority_signals) {
     v8::Local<v8::Object> per_buyer_priority_signals = v8::Object::New(isolate);
diff --git a/content/services/auction_worklet/seller_worklet_unittest.cc b/content/services/auction_worklet/seller_worklet_unittest.cc
index 65eb7f6..1cccd24 100644
--- a/content/services/auction_worklet/seller_worklet_unittest.cc
+++ b/content/services/auction_worklet/seller_worklet_unittest.cc
@@ -2521,6 +2521,16 @@
       blink::AuctionConfig::MaybePromiseBuyerTimeouts::FromValue(
           std::move(buyer_timeouts));
 
+  blink::AuctionConfig::BuyerTimeouts buyer_cumulative_timeouts;
+  buyer_cumulative_timeouts.per_buyer_timeouts.emplace();
+  buyer_cumulative_timeouts.per_buyer_timeouts
+      .value()[url::Origin::Create(GURL("https://a.com"))] =
+      base::Milliseconds(101);
+  buyer_cumulative_timeouts.all_buyers_timeout = base::Milliseconds(151);
+  auction_ad_config_non_shared_params_.buyer_cumulative_timeouts =
+      blink::AuctionConfig::MaybePromiseBuyerTimeouts::FromValue(
+          std::move(buyer_cumulative_timeouts));
+
   auction_ad_config_non_shared_params_.per_buyer_priority_signals = {
       {url::Origin::Create(GURL("https://a.com")), {{"signals_c", 0.5}}}};
   auction_ad_config_non_shared_params_.all_buyers_priority_signals = {
@@ -2538,6 +2548,7 @@
           "perBuyerSignals":{"https://a.com":{"signals_a":"A"},
                              "https://b.com":{"signals_b":"B"}},
           "perBuyerTimeouts":{"https://a.com":100,"*":150},
+          "perBuyerCumulativeTimeouts":{"https://a.com":101,"*":151},
           "perBuyerPrioritySignals":{"https://a.com":{"signals_c":0.5},
                                      "*":            {"signals_d":0}}
         })";
diff --git a/content/test/BUILD.gn b/content/test/BUILD.gn
index 36c9d85..380dcf1 100644
--- a/content/test/BUILD.gn
+++ b/content/test/BUILD.gn
@@ -142,8 +142,6 @@
     "../public/test/browser_test.h",
     "../public/test/browser_test_base.cc",
     "../public/test/browser_test_base.h",
-    "../public/test/browser_test_switches.cc",
-    "../public/test/browser_test_switches.h",
     "../public/test/browser_test_utils.cc",
     "../public/test/browser_test_utils.h",
     "../public/test/browsing_data_remover_test_util.cc",
@@ -296,6 +294,7 @@
     "../public/test/test_navigation_ui_data.h",
     "../public/test/test_renderer_host.cc",
     "../public/test/test_renderer_host.h",
+    "../public/test/test_select_url_fenced_frame_config_observer.h",
     "../public/test/test_storage_partition.cc",
     "../public/test/test_storage_partition.h",
     "../public/test/test_utils.cc",
@@ -434,6 +433,8 @@
     "test_render_widget_host.h",
     "test_render_widget_host_factory.cc",
     "test_render_widget_host_factory.h",
+    "test_select_url_fenced_frame_config_observer_impl.cc",
+    "test_select_url_fenced_frame_config_observer_impl.h",
     "test_web_contents.cc",
     "test_web_contents.h",
     "test_web_contents_factory.cc",
diff --git a/content/test/data/accessibility/aria/aria-treeitem-nested-in-lists-expected-android-external.txt b/content/test/data/accessibility/aria/aria-treeitem-nested-in-lists-expected-android-external.txt
index e75d0f8..8785e4b 100644
--- a/content/test/data/accessibility/aria/aria-treeitem-nested-in-lists-expected-android-external.txt
+++ b/content/test/data/accessibility/aria/aria-treeitem-nested-in-lists-expected-android-external.txt
@@ -4,4 +4,15 @@
 ++++View text:"treeitem 3 of 5, level 1" clickable CollectionItemInfo:[rowSpan=0, colSpan=0, rowIndex=2, colIndex=0] actions:[CLICK, AX_FOCUS, NEXT, PREVIOUS] bundle:[chromeRole="treeItem", roleDescription="tree item"]
 ++++View text:"treeitem 1 of 2, level 2" clickable CollectionItemInfo:[rowSpan=0, colSpan=0, rowIndex=0, colIndex=0] actions:[CLICK, AX_FOCUS, NEXT, PREVIOUS] bundle:[chromeRole="treeItem", roleDescription="tree item"]
 ++++View text:"treeitem 1 of 1, level 3" clickable CollectionItemInfo:[rowSpan=0, colSpan=0, rowIndex=0, colIndex=0] actions:[CLICK, AX_FOCUS, NEXT, PREVIOUS] bundle:[chromeRole="treeItem", roleDescription="tree item"]
-++++View text:"treeitem 2 of 2, level 2" clickable CollectionItemInfo:[rowSpan=0, colSpan=0, rowIndex=1, colIndex=0] actions:[CLICK, AX_FOCUS, NEXT, PREVIOUS] bundle:[chromeRole="treeItem", roleDescription="tree item"]
\ No newline at end of file
+++++View text:"treeitem 2 of 2, level 2" clickable CollectionItemInfo:[rowSpan=0, colSpan=0, rowIndex=1, colIndex=0] actions:[CLICK, AX_FOCUS, NEXT, PREVIOUS] bundle:[chromeRole="treeItem", roleDescription="tree item"]
+++View CollectionInfo:[hierarchical, rows=3, cols=0] actions:[AX_FOCUS] bundle:[chromeRole="tree", roleDescription="tree"]
+++++TextView text:"schedule" actions:[AX_FOCUS, NEXT, PREVIOUS] bundle:[chromeRole="staticText"]
+++++View text:"wake up" clickable CollectionItemInfo:[rowSpan=0, colSpan=0, rowIndex=0, colIndex=0] actions:[CLICK, AX_FOCUS, NEXT, PREVIOUS] bundle:[chromeRole="treeItem", roleDescription="tree item"]
+++++View text:"drink coffee" clickable CollectionItemInfo:[rowSpan=0, colSpan=0, rowIndex=1, colIndex=0] actions:[CLICK, AX_FOCUS, NEXT, PREVIOUS] bundle:[chromeRole="treeItem", roleDescription="tree item"]
+++++++TextView text:"• " actions:[AX_FOCUS, NEXT, PREVIOUS] bundle:[chromeRole="staticText"]
+++++++TextView text:"drink coffee" actions:[AX_FOCUS, NEXT, PREVIOUS] bundle:[chromeRole="staticText"]
+++++++View CollectionInfo:[hierarchical, rows=2, cols=0] actions:[AX_FOCUS] bundle:[chromeRole="tree", roleDescription="tree"]
+++++++++TextView text:"tasks" actions:[AX_FOCUS, NEXT, PREVIOUS] bundle:[chromeRole="staticText"]
+++++++++View text:"meeting" clickable CollectionItemInfo:[rowSpan=0, colSpan=0, rowIndex=0, colIndex=0] actions:[CLICK, AX_FOCUS, NEXT, PREVIOUS] bundle:[chromeRole="treeItem", roleDescription="tree item"]
+++++++++View text:"lunch" clickable CollectionItemInfo:[rowSpan=0, colSpan=0, rowIndex=1, colIndex=0] actions:[CLICK, AX_FOCUS, NEXT, PREVIOUS] bundle:[chromeRole="treeItem", roleDescription="tree item"]
+++++View text:"cook dinner" clickable CollectionItemInfo:[rowSpan=0, colSpan=0, rowIndex=2, colIndex=0] actions:[CLICK, AX_FOCUS, NEXT, PREVIOUS] bundle:[chromeRole="treeItem", roleDescription="tree item"]
diff --git a/content/test/data/accessibility/aria/aria-treeitem-nested-in-lists-expected-android.txt b/content/test/data/accessibility/aria/aria-treeitem-nested-in-lists-expected-android.txt
index 102a2e4..6a78b87 100644
--- a/content/test/data/accessibility/aria/aria-treeitem-nested-in-lists-expected-android.txt
+++ b/content/test/data/accessibility/aria/aria-treeitem-nested-in-lists-expected-android.txt
@@ -4,4 +4,15 @@
 ++++android.view.View role_description='tree item' clickable collection_item name='treeitem 3 of 5, level 1' item_index=2 row_index=2
 ++++android.view.View role_description='tree item' clickable collection_item name='treeitem 1 of 2, level 2'
 ++++android.view.View role_description='tree item' clickable collection_item name='treeitem 1 of 1, level 3'
-++++android.view.View role_description='tree item' clickable collection_item name='treeitem 2 of 2, level 2' item_index=1 row_index=1
\ No newline at end of file
+++++android.view.View role_description='tree item' clickable collection_item name='treeitem 2 of 2, level 2' item_index=1 row_index=1
+++android.view.View role_description='tree' collection hierarchical item_count=3 row_count=3
+++++android.widget.TextView name='schedule'
+++++android.view.View role_description='tree item' clickable collection_item name='wake up'
+++++android.view.View role_description='tree item' clickable collection_item name='drink coffee' item_index=1 row_index=1
+++++++android.widget.TextView name='%E2%80%A2 '
+++++++android.widget.TextView name='drink coffee'
+++++++android.view.View role_description='tree' collection hierarchical item_count=2 row_count=2
+++++++++android.widget.TextView name='tasks'
+++++++++android.view.View role_description='tree item' clickable collection_item name='meeting'
+++++++++android.view.View role_description='tree item' clickable collection_item name='lunch' item_index=1 row_index=1
+++++android.view.View role_description='tree item' clickable collection_item name='cook dinner' item_index=2 row_index=2
diff --git a/content/test/data/accessibility/aria/aria-treeitem-nested-in-lists-expected-auralinux.txt b/content/test/data/accessibility/aria/aria-treeitem-nested-in-lists-expected-auralinux.txt
index ef3384ed..db1e7af 100644
--- a/content/test/data/accessibility/aria/aria-treeitem-nested-in-lists-expected-auralinux.txt
+++ b/content/test/data/accessibility/aria/aria-treeitem-nested-in-lists-expected-auralinux.txt
@@ -10,3 +10,22 @@
 ++++++[static] name='treeitem 1 of 1, level 3'
 ++++[tree item] name='treeitem 2 of 2, level 2' selectable posinset:2 setsize:2
 ++++++[static] name='treeitem 2 of 2, level 2'
+++[tree] setsize:3
+++++[static] name='schedule'
+++++[tree item] name='wake up' selectable posinset:1 setsize:3
+++++++[static] name='%E2%80%A2 '
+++++++[static] name='wake up'
+++++[tree item] name='drink coffee' selectable posinset:2 setsize:3
+++++++[static] name='%E2%80%A2 '
+++++++[static] name='drink coffee'
+++++++[tree] setsize:2
+++++++++[static] name='tasks'
+++++++++[tree item] name='meeting' selectable posinset:1 setsize:2
+++++++++++[static] name='%E2%97%A6 '
+++++++++++[static] name='meeting'
+++++++++[tree item] name='lunch' selectable posinset:2 setsize:2
+++++++++++[static] name='%E2%97%A6 '
+++++++++++[static] name='lunch'
+++++[tree item] name='cook dinner' selectable posinset:3 setsize:3
+++++++[static] name='%E2%80%A2 '
+++++++[static] name='cook dinner'
\ No newline at end of file
diff --git a/content/test/data/accessibility/aria/aria-treeitem-nested-in-lists-expected-blink.txt b/content/test/data/accessibility/aria/aria-treeitem-nested-in-lists-expected-blink.txt
index 231d115..ce29886 100644
--- a/content/test/data/accessibility/aria/aria-treeitem-nested-in-lists-expected-blink.txt
+++ b/content/test/data/accessibility/aria/aria-treeitem-nested-in-lists-expected-blink.txt
@@ -1,19 +1,55 @@
-rootWebArea
-++genericContainer ignored
-++++genericContainer ignored
-++++++tree setSize=5
-++++++++treeItem name='treeitem 2 of 5, level 1' hierarchicalLevel=1 setSize=5 posInSet=2 selected=false
+rootWebArea isLineBreakingObject=true
+++genericContainer ignored isLineBreakingObject=true
+++++genericContainer ignored isLineBreakingObject=true
+++++++tree setSize=5 isLineBreakingObject=true
+++++++++treeItem name='treeitem 2 of 5, level 1' hierarchicalLevel=1 setSize=5 posInSet=2 selected=false isLineBreakingObject=true
 ++++++++++staticText name='treeitem 2 of 5, level 1'
 ++++++++++++inlineTextBox name='treeitem 2 of 5, level 1'
-++++++++treeItem name='treeitem 3 of 5, level 1' hierarchicalLevel=1 setSize=5 posInSet=3 selected=false
+++++++++treeItem name='treeitem 3 of 5, level 1' hierarchicalLevel=1 setSize=5 posInSet=3 selected=false isLineBreakingObject=true
 ++++++++++staticText name='treeitem 3 of 5, level 1'
 ++++++++++++inlineTextBox name='treeitem 3 of 5, level 1'
-++++++++treeItem name='treeitem 1 of 2, level 2' hierarchicalLevel=2 setSize=2 posInSet=1 selected=false
+++++++++treeItem name='treeitem 1 of 2, level 2' hierarchicalLevel=2 setSize=2 posInSet=1 selected=false isLineBreakingObject=true
 ++++++++++staticText name='treeitem 1 of 2, level 2'
 ++++++++++++inlineTextBox name='treeitem 1 of 2, level 2'
-++++++++treeItem name='treeitem 1 of 1, level 3' hierarchicalLevel=3 setSize=1 posInSet=1 selected=false
+++++++++treeItem name='treeitem 1 of 1, level 3' hierarchicalLevel=3 setSize=1 posInSet=1 selected=false isLineBreakingObject=true
 ++++++++++staticText name='treeitem 1 of 1, level 3'
 ++++++++++++inlineTextBox name='treeitem 1 of 1, level 3'
-++++++++treeItem name='treeitem 2 of 2, level 2' hierarchicalLevel=2 setSize=2 posInSet=2 selected=false
+++++++++treeItem name='treeitem 2 of 2, level 2' hierarchicalLevel=2 setSize=2 posInSet=2 selected=false isLineBreakingObject=true
 ++++++++++staticText name='treeitem 2 of 2, level 2'
 ++++++++++++inlineTextBox name='treeitem 2 of 2, level 2'
+++++++tree setSize=3 isLineBreakingObject=true
+++++++++staticText name='schedule'
+++++++++++inlineTextBox name='schedule'
+++++++++treeItem name='wake up' hierarchicalLevel=1 setSize=3 posInSet=1 selected=false isLineBreakingObject=true
+++++++++++none ignored
+++++++++++++staticText name='%E2%80%A2 ' nextOnLineId=inlineTextBox:"wake up"
+++++++++++++++inlineTextBox name='%E2%80%A2 ' nextOnLineId=inlineTextBox:"wake up"
+++++++++++staticText name='wake up' previousOnLineId=inlineTextBox:"%E2%80%A2 "
+++++++++++++inlineTextBox name='wake up' previousOnLineId=inlineTextBox:"%E2%80%A2 "
+++++++++treeItem name='drink coffee' hierarchicalLevel=1 setSize=3 posInSet=2 selected=false isLineBreakingObject=true
+++++++++++none ignored
+++++++++++++staticText name='%E2%80%A2 ' nextOnLineId=inlineTextBox:"drink coffee"
+++++++++++++++inlineTextBox name='%E2%80%A2 ' nextOnLineId=inlineTextBox:"drink coffee"
+++++++++++staticText name='drink coffee' previousOnLineId=inlineTextBox:"%E2%80%A2 "
+++++++++++++inlineTextBox name='drink coffee' previousOnLineId=inlineTextBox:"%E2%80%A2 "
+++++++++++tree setSize=2 isLineBreakingObject=true
+++++++++++++staticText name='tasks'
+++++++++++++++inlineTextBox name='tasks'
+++++++++++++treeItem name='meeting' hierarchicalLevel=2 setSize=2 posInSet=1 selected=false isLineBreakingObject=true
+++++++++++++++none ignored
+++++++++++++++++staticText name='%E2%97%A6 ' nextOnLineId=inlineTextBox:"meeting"
+++++++++++++++++++inlineTextBox name='%E2%97%A6 ' nextOnLineId=inlineTextBox:"meeting"
+++++++++++++++staticText name='meeting' previousOnLineId=inlineTextBox:"%E2%97%A6 "
+++++++++++++++++inlineTextBox name='meeting' previousOnLineId=inlineTextBox:"%E2%97%A6 "
+++++++++++++treeItem name='lunch' hierarchicalLevel=2 setSize=2 posInSet=2 selected=false isLineBreakingObject=true
+++++++++++++++none ignored
+++++++++++++++++staticText name='%E2%97%A6 ' nextOnLineId=inlineTextBox:"lunch"
+++++++++++++++++++inlineTextBox name='%E2%97%A6 ' nextOnLineId=inlineTextBox:"lunch"
+++++++++++++++staticText name='lunch' previousOnLineId=inlineTextBox:"%E2%97%A6 "
+++++++++++++++++inlineTextBox name='lunch' previousOnLineId=inlineTextBox:"%E2%97%A6 "
+++++++++treeItem name='cook dinner' hierarchicalLevel=1 setSize=3 posInSet=3 selected=false isLineBreakingObject=true
+++++++++++none ignored
+++++++++++++staticText name='%E2%80%A2 ' nextOnLineId=inlineTextBox:"cook dinner"
+++++++++++++++inlineTextBox name='%E2%80%A2 ' nextOnLineId=inlineTextBox:"cook dinner"
+++++++++++staticText name='cook dinner' previousOnLineId=inlineTextBox:"%E2%80%A2 "
+++++++++++++inlineTextBox name='cook dinner' previousOnLineId=inlineTextBox:"%E2%80%A2 "
diff --git a/content/test/data/accessibility/aria/aria-treeitem-nested-in-lists-expected-mac.txt b/content/test/data/accessibility/aria/aria-treeitem-nested-in-lists-expected-mac.txt
index d640a7d9..945877b 100644
--- a/content/test/data/accessibility/aria/aria-treeitem-nested-in-lists-expected-mac.txt
+++ b/content/test/data/accessibility/aria/aria-treeitem-nested-in-lists-expected-mac.txt
@@ -10,3 +10,22 @@
 ++++++AXStaticText AXRoleDescription='text' AXValue='treeitem 1 of 1, level 3'
 ++++AXRow AXSubrole=AXOutlineRow AXARIAPosInSet=2 AXARIASetSize=2 AXIndex=4 AXRoleDescription='outline row' AXTitle='treeitem 2 of 2, level 2'
 ++++++AXStaticText AXRoleDescription='text' AXValue='treeitem 2 of 2, level 2'
+++AXOutline AXARIASetSize=3 AXRoleDescription='outline'
+++++AXStaticText AXRoleDescription='text' AXValue='schedule'
+++++AXRow AXSubrole=AXOutlineRow AXARIAPosInSet=1 AXARIASetSize=3 AXIndex=0 AXRoleDescription='outline row' AXTitle='wake up'
+++++++AXStaticText AXRoleDescription='text' AXValue='%E2%80%A2 '
+++++++AXStaticText AXRoleDescription='text' AXValue='wake up'
+++++AXRow AXSubrole=AXOutlineRow AXARIAPosInSet=2 AXARIASetSize=3 AXIndex=1 AXRoleDescription='outline row' AXTitle='drink coffee'
+++++++AXStaticText AXRoleDescription='text' AXValue='%E2%80%A2 '
+++++++AXStaticText AXRoleDescription='text' AXValue='drink coffee'
+++++++AXOutline AXARIASetSize=2 AXRoleDescription='outline'
+++++++++AXStaticText AXRoleDescription='text' AXValue='tasks'
+++++++++AXRow AXSubrole=AXOutlineRow AXARIAPosInSet=1 AXARIASetSize=2 AXIndex=0 AXRoleDescription='outline row' AXTitle='meeting'
+++++++++++AXStaticText AXRoleDescription='text' AXValue='%E2%97%A6 '
+++++++++++AXStaticText AXRoleDescription='text' AXValue='meeting'
+++++++++AXRow AXSubrole=AXOutlineRow AXARIAPosInSet=2 AXARIASetSize=2 AXIndex=1 AXRoleDescription='outline row' AXTitle='lunch'
+++++++++++AXStaticText AXRoleDescription='text' AXValue='%E2%97%A6 '
+++++++++++AXStaticText AXRoleDescription='text' AXValue='lunch'
+++++AXRow AXSubrole=AXOutlineRow AXARIAPosInSet=3 AXARIASetSize=3 AXIndex=4 AXRoleDescription='outline row' AXTitle='cook dinner'
+++++++AXStaticText AXRoleDescription='text' AXValue='%E2%80%A2 '
+++++++AXStaticText AXRoleDescription='text' AXValue='cook dinner'
\ No newline at end of file
diff --git a/content/test/data/accessibility/aria/aria-treeitem-nested-in-lists-expected-uia-win.txt b/content/test/data/accessibility/aria/aria-treeitem-nested-in-lists-expected-uia-win.txt
new file mode 100644
index 0000000..adef7c6
--- /dev/null
+++ b/content/test/data/accessibility/aria/aria-treeitem-nested-in-lists-expected-uia-win.txt
@@ -0,0 +1,31 @@
+Document
+++Tree Selection.CanSelectMultiple=false Selection.IsSelectionRequired=false
+++++TreeItem Name='treeitem 2 of 5, level 1' ExpandCollapse.ExpandCollapseState='LeafNode' SelectionItem.IsSelected=false
+++++++Text Name='treeitem 2 of 5, level 1' IsControlElement=false
+++++TreeItem Name='treeitem 3 of 5, level 1' ExpandCollapse.ExpandCollapseState='LeafNode' SelectionItem.IsSelected=false
+++++++Text Name='treeitem 3 of 5, level 1' IsControlElement=false
+++++TreeItem Name='treeitem 1 of 2, level 2' ExpandCollapse.ExpandCollapseState='LeafNode' SelectionItem.IsSelected=false
+++++++Text Name='treeitem 1 of 2, level 2' IsControlElement=false
+++++TreeItem Name='treeitem 1 of 1, level 3' ExpandCollapse.ExpandCollapseState='LeafNode' SelectionItem.IsSelected=false
+++++++Text Name='treeitem 1 of 1, level 3' IsControlElement=false
+++++TreeItem Name='treeitem 2 of 2, level 2' ExpandCollapse.ExpandCollapseState='LeafNode' SelectionItem.IsSelected=false
+++++++Text Name='treeitem 2 of 2, level 2' IsControlElement=false
+++Tree Selection.CanSelectMultiple=false Selection.IsSelectionRequired=false
+++++Text Name='schedule'
+++++TreeItem Name='wake up' ExpandCollapse.ExpandCollapseState='LeafNode' SelectionItem.IsSelected=false
+++++++Text Name='%E2%80%A2 ' IsControlElement=false
+++++++Text Name='wake up' IsControlElement=false
+++++TreeItem Name='drink coffee' ExpandCollapse.ExpandCollapseState='LeafNode' SelectionItem.IsSelected=false
+++++++Text Name='%E2%80%A2 ' IsControlElement=false
+++++++Text Name='drink coffee' IsControlElement=false
+++++++Tree Selection.CanSelectMultiple=false Selection.IsSelectionRequired=false
+++++++++Text Name='tasks' IsControlElement=false
+++++++++TreeItem Name='meeting' ExpandCollapse.ExpandCollapseState='LeafNode' SelectionItem.IsSelected=false
+++++++++++Text Name='%E2%97%A6 ' IsControlElement=false
+++++++++++Text Name='meeting' IsControlElement=false
+++++++++TreeItem Name='lunch' ExpandCollapse.ExpandCollapseState='LeafNode' SelectionItem.IsSelected=false
+++++++++++Text Name='%E2%97%A6 ' IsControlElement=false
+++++++++++Text Name='lunch' IsControlElement=false
+++++TreeItem Name='cook dinner' ExpandCollapse.ExpandCollapseState='LeafNode' SelectionItem.IsSelected=false
+++++++Text Name='%E2%80%A2 ' IsControlElement=false
+++++++Text Name='cook dinner' IsControlElement=false
diff --git a/content/test/data/accessibility/aria/aria-treeitem-nested-in-lists-expected-win.txt b/content/test/data/accessibility/aria/aria-treeitem-nested-in-lists-expected-win.txt
index 0c13de1..3243af42 100644
--- a/content/test/data/accessibility/aria/aria-treeitem-nested-in-lists-expected-win.txt
+++ b/content/test/data/accessibility/aria/aria-treeitem-nested-in-lists-expected-win.txt
@@ -9,4 +9,23 @@
 ++++ROLE_SYSTEM_OUTLINEITEM name='treeitem 1 of 1, level 3' xml-roles:treeitem level:3 setsize:1 posinset:1
 ++++++ROLE_SYSTEM_STATICTEXT name='treeitem 1 of 1, level 3'
 ++++ROLE_SYSTEM_OUTLINEITEM name='treeitem 2 of 2, level 2' xml-roles:treeitem level:2 setsize:2 posinset:2
-++++++ROLE_SYSTEM_STATICTEXT name='treeitem 2 of 2, level 2'
\ No newline at end of file
+++++++ROLE_SYSTEM_STATICTEXT name='treeitem 2 of 2, level 2'
+++ROLE_SYSTEM_OUTLINE xml-roles:tree setsize:3
+++++ROLE_SYSTEM_STATICTEXT name='schedule'
+++++ROLE_SYSTEM_OUTLINEITEM name='wake up' xml-roles:treeitem level:1 setsize:3 posinset:1
+++++++ROLE_SYSTEM_STATICTEXT name='%E2%80%A2 '
+++++++ROLE_SYSTEM_STATICTEXT name='wake up'
+++++ROLE_SYSTEM_OUTLINEITEM name='drink coffee' xml-roles:treeitem level:1 setsize:3 posinset:2
+++++++ROLE_SYSTEM_STATICTEXT name='%E2%80%A2 '
+++++++ROLE_SYSTEM_STATICTEXT name='drink coffee'
+++++++ROLE_SYSTEM_OUTLINE xml-roles:tree setsize:2
+++++++++ROLE_SYSTEM_STATICTEXT name='tasks'
+++++++++ROLE_SYSTEM_OUTLINEITEM name='meeting' xml-roles:treeitem level:2 setsize:2 posinset:1
+++++++++++ROLE_SYSTEM_STATICTEXT name='%E2%97%A6 '
+++++++++++ROLE_SYSTEM_STATICTEXT name='meeting'
+++++++++ROLE_SYSTEM_OUTLINEITEM name='lunch' xml-roles:treeitem level:2 setsize:2 posinset:2
+++++++++++ROLE_SYSTEM_STATICTEXT name='%E2%97%A6 '
+++++++++++ROLE_SYSTEM_STATICTEXT name='lunch'
+++++ROLE_SYSTEM_OUTLINEITEM name='cook dinner' xml-roles:treeitem level:1 setsize:3 posinset:3
+++++++ROLE_SYSTEM_STATICTEXT name='%E2%80%A2 '
+++++++ROLE_SYSTEM_STATICTEXT name='cook dinner'
\ No newline at end of file
diff --git a/content/test/data/accessibility/aria/aria-treeitem-nested-in-lists.html b/content/test/data/accessibility/aria/aria-treeitem-nested-in-lists.html
index 38f77c6..143adb7 100644
--- a/content/test/data/accessibility/aria/aria-treeitem-nested-in-lists.html
+++ b/content/test/data/accessibility/aria/aria-treeitem-nested-in-lists.html
@@ -20,11 +20,21 @@
 @BLINK-ALLOW:posInSet*
 @BLINK-DENY:setSize=0
 @BLINK-DENY:posInSet=0
+@BLINK-ALLOW:next*
+@BLINK-ALLOW:prev*
+@BLINK-ALLOW:isLineBreakingObject=true
 -->
 <!DOCTYPE html>
 <html>
+<head>
+<style>
+  .no-bullets {
+    list-style-type: none;
+  }
+</style>
+</head>
 <body>
-    <ul role="tree">
+  <ul role="tree" class="no-bullets">
     <li>
       <div role="treeitem" aria-posinset="2" aria-setsize="5">treeitem 2 of 5, level 1</div>
     </li>
@@ -40,12 +50,16 @@
     <li>
       <div role="treeitem" aria-level="2">treeitem 2 of 2, level 2</div>
     </li>
-    </ul>
+  </ul>
 
-  <style>
-    li {
-      list-style-type: none;
-    }
-  </style>
+  <ul role="tree">schedule
+    <li role="treeitem">wake up
+    <li role="treeitem">drink coffee
+    <ul role="tree">tasks
+      <li role="treeitem" aria-level='2'>meeting
+      <li role="treeitem" aria-level='2'>lunch
+    </ul>
+    <li role="treeitem">cook dinner
+  </ul>
 </body>
 </html>
diff --git a/content/test/data/accessibility/css/content-visibility-auto-aria-hidden-expected-blink.txt b/content/test/data/accessibility/css/content-visibility-auto-aria-hidden-expected-blink.txt
index 5f95011..dfe3c5aa 100644
--- a/content/test/data/accessibility/css/content-visibility-auto-aria-hidden-expected-blink.txt
+++ b/content/test/data/accessibility/css/content-visibility-auto-aria-hidden-expected-blink.txt
@@ -4,5 +4,7 @@
 ++++++genericContainer
 ++++++++genericContainer ignored invisible
 ++++++++++staticText ignored invisible name='aria-hidden'
-++++++++genericContainer
-++++++++++staticText name='Not hidden'
+++++++++genericContainer ignored
+++++++++++staticText name='Unsemantic'
+++++++++heading name='Semantic'
+++++++++++staticText name='Semantic'
diff --git a/content/test/data/accessibility/css/content-visibility-auto-aria-hidden.html b/content/test/data/accessibility/css/content-visibility-auto-aria-hidden.html
index 4c5bfefa6..4e3fc7d 100644
--- a/content/test/data/accessibility/css/content-visibility-auto-aria-hidden.html
+++ b/content/test/data/accessibility/css/content-visibility-auto-aria-hidden.html
@@ -14,7 +14,7 @@
 </head>
 <body>
 <div class="push-content-down" role="none"></div>
-<div class="auto"><div aria-hidden="true">aria-hidden</div><div>Not hidden</div></div>
+<div class="auto"><div aria-hidden="true">aria-hidden</div><div>Unsemantic</div><h1>Semantic</h1></div>
 <script>
 function runTest() {
   document.title = 'Done';
diff --git a/content/test/data/accessibility/display-locking/activatable-expected-auralinux.txt b/content/test/data/accessibility/display-locking/activatable-expected-auralinux.txt
index 7385724..e8fb96d4 100644
--- a/content/test/data/accessibility/display-locking/activatable-expected-auralinux.txt
+++ b/content/test/data/accessibility/display-locking/activatable-expected-auralinux.txt
@@ -2,12 +2,11 @@
 ++[section]
 ++[section]
 ++++[static] name='<newline>    '
-++++[section]
-++++++[static] name='child'
+++++[static] name='child'
 ++++[static] name='<newline>    '
 ++++[section]
 ++++++[static] name='nested locked element!'
 ++++[static] name='<newline>    '
 ++++[section]
 ++++++[static] name='nested non activatable locked element'
-++++[static] name='<newline>  '
+++++[static] name='<newline>  '
\ No newline at end of file
diff --git a/content/test/data/accessibility/display-locking/activatable-expected-blink.txt b/content/test/data/accessibility/display-locking/activatable-expected-blink.txt
index 7a25ce8..fb2333c 100644
--- a/content/test/data/accessibility/display-locking/activatable-expected-blink.txt
+++ b/content/test/data/accessibility/display-locking/activatable-expected-blink.txt
@@ -5,7 +5,7 @@
 ++++++++genericContainer
 ++++++++genericContainer offscreen
 ++++++++++staticText offscreen name='<newline>    '
-++++++++++genericContainer offscreen
+++++++++++genericContainer ignored offscreen
 ++++++++++++staticText offscreen name='child'
 ++++++++++staticText offscreen name='<newline>    '
 ++++++++++genericContainer offscreen
diff --git a/content/test/data/accessibility/display-locking/activatable-expected-mac.txt b/content/test/data/accessibility/display-locking/activatable-expected-mac.txt
index cefb15b..2cdb544 100644
--- a/content/test/data/accessibility/display-locking/activatable-expected-mac.txt
+++ b/content/test/data/accessibility/display-locking/activatable-expected-mac.txt
@@ -2,12 +2,11 @@
 ++AXGroup
 ++AXGroup
 ++++AXStaticText AXValue='<newline>    '
-++++AXGroup
-++++++AXStaticText AXValue='child'
+++++AXStaticText AXValue='child'
 ++++AXStaticText AXValue='<newline>    '
 ++++AXGroup
 ++++++AXStaticText AXValue='nested locked element!'
 ++++AXStaticText AXValue='<newline>    '
 ++++AXGroup
 ++++++AXStaticText AXValue='nested non activatable locked element'
-++++AXStaticText AXValue='<newline>  '
+++++AXStaticText AXValue='<newline>  '
\ No newline at end of file
diff --git a/content/test/data/accessibility/display-locking/activatable-expected-win.txt b/content/test/data/accessibility/display-locking/activatable-expected-win.txt
index d6462e1..7eb9ee52 100644
--- a/content/test/data/accessibility/display-locking/activatable-expected-win.txt
+++ b/content/test/data/accessibility/display-locking/activatable-expected-win.txt
@@ -2,12 +2,11 @@
 ++IA2_ROLE_SECTION
 ++IA2_ROLE_SECTION
 ++++ROLE_SYSTEM_STATICTEXT name='<newline>    '
-++++IA2_ROLE_SECTION
-++++++ROLE_SYSTEM_STATICTEXT name='child'
+++++ROLE_SYSTEM_STATICTEXT name='child'
 ++++ROLE_SYSTEM_STATICTEXT name='<newline>    '
 ++++IA2_ROLE_SECTION
 ++++++ROLE_SYSTEM_STATICTEXT name='nested locked element!'
 ++++ROLE_SYSTEM_STATICTEXT name='<newline>    '
 ++++IA2_ROLE_SECTION
 ++++++ROLE_SYSTEM_STATICTEXT name='nested non activatable locked element'
-++++ROLE_SYSTEM_STATICTEXT name='<newline>  '
+++++ROLE_SYSTEM_STATICTEXT name='<newline>  '
\ No newline at end of file
diff --git a/content/test/data/accessibility/display-locking/all-expected-auralinux.txt b/content/test/data/accessibility/display-locking/all-expected-auralinux.txt
index 6437251..f5ae82e 100644
--- a/content/test/data/accessibility/display-locking/all-expected-auralinux.txt
+++ b/content/test/data/accessibility/display-locking/all-expected-auralinux.txt
@@ -3,8 +3,7 @@
 ++++[static] name='spacer so that everything below will be offscreen (and won't get viewport-activated)' visible
 ++[section] visible
 ++++[static] name='<newline>    ' visible
-++++[section] visible
-++++++[static] name='child text will be in AX tree but without layout' visible
+++++[static] name='child text will be in AX tree but without layout' visible
 ++++[static] name='<newline>    ' visible
 ++++[section] visible
 ++++++[static] name='<newline>      nested activatable locked element will be in AX tree but without layout<newline>    ' visible
@@ -13,4 +12,4 @@
 ++[section] visible
 ++[static] name='normal text 2' visible
 ++[section] visible
-++[static] name='normal text 3' visible
+++[static] name='normal text 3' visible
\ No newline at end of file
diff --git a/content/test/data/accessibility/display-locking/all-expected-blink.txt b/content/test/data/accessibility/display-locking/all-expected-blink.txt
index 1598c7d..cbc21ee 100644
--- a/content/test/data/accessibility/display-locking/all-expected-blink.txt
+++ b/content/test/data/accessibility/display-locking/all-expected-blink.txt
@@ -7,7 +7,7 @@
 ++++++++++++inlineTextBox name='spacer so that everything below will be offscreen (and won't get viewport-activated)'
 ++++++++genericContainer offscreen
 ++++++++++staticText offscreen name='<newline>    '
-++++++++++genericContainer offscreen
+++++++++++genericContainer ignored offscreen
 ++++++++++++staticText offscreen name='child text will be in AX tree but without layout'
 ++++++++++staticText offscreen name='<newline>    '
 ++++++++++genericContainer offscreen
diff --git a/content/test/data/accessibility/display-locking/all-expected-mac.txt b/content/test/data/accessibility/display-locking/all-expected-mac.txt
index aa332dd..2f2a52e1 100644
--- a/content/test/data/accessibility/display-locking/all-expected-mac.txt
+++ b/content/test/data/accessibility/display-locking/all-expected-mac.txt
@@ -3,8 +3,7 @@
 ++++AXStaticText AXValue='spacer so that everything below will be offscreen (and won't get viewport-activated)'
 ++AXGroup
 ++++AXStaticText AXValue='<newline>    '
-++++AXGroup
-++++++AXStaticText AXValue='child text will be in AX tree but without layout'
+++++AXStaticText AXValue='child text will be in AX tree but without layout'
 ++++AXStaticText AXValue='<newline>    '
 ++++AXGroup
 ++++++AXStaticText AXValue='<newline>      nested activatable locked element will be in AX tree but without layout<newline>    '
@@ -13,4 +12,4 @@
 ++AXGroup
 ++AXStaticText AXValue='normal text 2'
 ++AXGroup
-++AXStaticText AXValue='normal text 3'
+++AXStaticText AXValue='normal text 3'
\ No newline at end of file
diff --git a/content/test/data/accessibility/display-locking/all-expected-win.txt b/content/test/data/accessibility/display-locking/all-expected-win.txt
index afc58e0..12c77f15 100644
--- a/content/test/data/accessibility/display-locking/all-expected-win.txt
+++ b/content/test/data/accessibility/display-locking/all-expected-win.txt
@@ -3,8 +3,7 @@
 ++++ROLE_SYSTEM_STATICTEXT name='spacer so that everything below will be offscreen (and won't get viewport-activated)'
 ++IA2_ROLE_SECTION
 ++++ROLE_SYSTEM_STATICTEXT name='<newline>    '
-++++IA2_ROLE_SECTION
-++++++ROLE_SYSTEM_STATICTEXT name='child text will be in AX tree but without layout'
+++++ROLE_SYSTEM_STATICTEXT name='child text will be in AX tree but without layout'
 ++++ROLE_SYSTEM_STATICTEXT name='<newline>    '
 ++++IA2_ROLE_SECTION
 ++++++ROLE_SYSTEM_STATICTEXT name='<newline>      nested activatable locked element will be in AX tree but without layout<newline>    '
@@ -13,4 +12,4 @@
 ++IA2_ROLE_SECTION
 ++ROLE_SYSTEM_STATICTEXT name='normal text 2'
 ++IA2_ROLE_SECTION
-++ROLE_SYSTEM_STATICTEXT name='normal text 3'
+++ROLE_SYSTEM_STATICTEXT name='normal text 3'
\ No newline at end of file
diff --git a/content/test/data/accessibility/html/area-expected-android-external.txt b/content/test/data/accessibility/html/area-expected-android-external.txt
index 3f987614..21f26bb 100644
--- a/content/test/data/accessibility/html/area-expected-android-external.txt
+++ b/content/test/data/accessibility/html/area-expected-android-external.txt
@@ -2,4 +2,3 @@
 ++View actions:[AX_FOCUS] bundle:[chromeRole="genericContainer", hasImage="true"]
 ++++Image text:"pipe" actions:[AX_FOCUS, NEXT, PREVIOUS] bundle:[chromeRole="image", hasImage="true", roleDescription="graphic", targetUrl="file:///storage/emulated/0/chromium_tests_root/content/test/data/accessibility/html/pipe.jpg"]
 ++++++View text:"null" contentDescription:"pipe1" clickable focusable actions:[FOCUS, CLICK, AX_FOCUS, NEXT, PREVIOUS] bundle:[chromeRole="link", clickableScore="300", roleDescription="link", targetUrl="file:///storage/emulated/0/chromium_tests_root/content/test/data/accessibility/html/fake.htm"]
-++++++TextView text:"pipe2" clickable actions:[CLICK, AX_FOCUS, NEXT, PREVIOUS] bundle:[chromeRole="staticText", clickableScore="200"]
\ No newline at end of file
diff --git a/content/test/data/accessibility/html/area-expected-auralinux.txt b/content/test/data/accessibility/html/area-expected-auralinux.txt
index 9ff7ce9..8e1155f 100644
--- a/content/test/data/accessibility/html/area-expected-auralinux.txt
+++ b/content/test/data/accessibility/html/area-expected-auralinux.txt
@@ -1,5 +1,4 @@
 [document web]
 ++[section]
 ++++[image map] name='pipe'
-++++++[link] name='pipe1'
-++++++[static] name='pipe2'
+++++++[link] name='pipe1'
\ No newline at end of file
diff --git a/content/test/data/accessibility/html/area-expected-blink.txt b/content/test/data/accessibility/html/area-expected-blink.txt
index 8316198..1cc29aef 100644
--- a/content/test/data/accessibility/html/area-expected-blink.txt
+++ b/content/test/data/accessibility/html/area-expected-blink.txt
@@ -3,4 +3,4 @@
 ++++genericContainer
 ++++++image name='pipe'
 ++++++++link name='pipe1'
-++++++++staticText name='pipe2'
+++++++++staticText ignored name='pipe2'
diff --git a/content/test/data/accessibility/html/area-expected-mac.txt b/content/test/data/accessibility/html/area-expected-mac.txt
index b778f4c..b6a1482 100644
--- a/content/test/data/accessibility/html/area-expected-mac.txt
+++ b/content/test/data/accessibility/html/area-expected-mac.txt
@@ -1,5 +1,4 @@
 AXWebArea AXRoleDescription='HTML content'
 ++AXGroup AXRoleDescription='group'
 ++++AXGroup AXDescription='pipe' AXRoleDescription='group'
-++++++AXLink AXDescription='pipe1' AXRoleDescription='link'
-++++++AXStaticText AXRoleDescription='text' AXValue='pipe2'
+++++++AXLink AXDescription='pipe1' AXRoleDescription='link'
\ No newline at end of file
diff --git a/content/test/data/accessibility/html/area-expected-uia-win.txt b/content/test/data/accessibility/html/area-expected-uia-win.txt
index 435a60028..02590411 100644
--- a/content/test/data/accessibility/html/area-expected-uia-win.txt
+++ b/content/test/data/accessibility/html/area-expected-uia-win.txt
@@ -1,5 +1,4 @@
 Document
 ++Group IsControlElement=false
 ++++Document Name='pipe'
-++++++Hyperlink Name='pipe1'
-++++++Text Name='pipe2'
+++++++Hyperlink Name='pipe1'
\ No newline at end of file
diff --git a/content/test/data/accessibility/html/area-expected-win.txt b/content/test/data/accessibility/html/area-expected-win.txt
index 7f58f8ed..3900f98 100644
--- a/content/test/data/accessibility/html/area-expected-win.txt
+++ b/content/test/data/accessibility/html/area-expected-win.txt
@@ -1,5 +1,4 @@
 ROLE_SYSTEM_DOCUMENT READONLY FOCUSABLE
 ++IA2_ROLE_SECTION
 ++++IA2_ROLE_IMAGE_MAP name='pipe' READONLY
-++++++ROLE_SYSTEM_LINK name='pipe1' FOCUSABLE LINKED
-++++++ROLE_SYSTEM_STATICTEXT name='pipe2' LINKED
+++++++ROLE_SYSTEM_LINK name='pipe1' FOCUSABLE LINKED
\ No newline at end of file
diff --git a/content/test/data/accessibility/html/canvas-fallback-expected-android-external.txt b/content/test/data/accessibility/html/canvas-fallback-expected-android-external.txt
index 36672ba..8256ed0 100644
--- a/content/test/data/accessibility/html/canvas-fallback-expected-android-external.txt
+++ b/content/test/data/accessibility/html/canvas-fallback-expected-android-external.txt
@@ -7,9 +7,6 @@
 ++++++TextView text:"\n    " actions:[AX_FOCUS, NEXT, PREVIOUS] bundle:[chromeRole="staticText"]
 ++++++TextView text:"This is another paragraph in fallback" hint:"Visibility hidden paragraph in fallback content" viewIdResName:"p2" actions:[AX_FOCUS, NEXT, PREVIOUS] bundle:[chromeRole="paragraph", hint="Visibility hidden paragraph in fallback content"]
 ++++++TextView text:"\n    " actions:[AX_FOCUS, NEXT, PREVIOUS] bundle:[chromeRole="staticText"]
-++++++TextView text:"Aria hidden paragraph in fallback content" viewIdResName:"h1" notVisibleToUser actions:[AX_FOCUS, NEXT, PREVIOUS] bundle:[chromeRole="heading", roleDescription="heading 1"]
 ++++++TextView text:"\n    " actions:[AX_FOCUS, NEXT, PREVIOUS] bundle:[chromeRole="staticText"]
-++++++TextView text:"Display none text in fallback content " notVisibleToUser actions:[AX_FOCUS, NEXT, PREVIOUS] bundle:[chromeRole="heading", roleDescription="heading 1"]
 ++++++TextView text:"\n    " actions:[AX_FOCUS, NEXT, PREVIOUS] bundle:[chromeRole="staticText"]
-++++++TextView text:"Visibility hidden paragraph in fallback content" viewIdResName:"h2" notVisibleToUser actions:[AX_FOCUS, NEXT, PREVIOUS] bundle:[chromeRole="heading", roleDescription="heading 2"]
-++++++TextView text:"\n  " actions:[AX_FOCUS, NEXT, PREVIOUS] bundle:[chromeRole="staticText"]
\ No newline at end of file
+++++++TextView text:"\n  " actions:[AX_FOCUS, NEXT, PREVIOUS] bundle:[chromeRole="staticText"]
diff --git a/content/test/data/accessibility/html/canvas-fallback-expected-android.txt b/content/test/data/accessibility/html/canvas-fallback-expected-android.txt
index d7cc3bb..d417c89 100644
--- a/content/test/data/accessibility/html/canvas-fallback-expected-android.txt
+++ b/content/test/data/accessibility/html/canvas-fallback-expected-android.txt
@@ -7,9 +7,6 @@
 ++++++android.widget.TextView name='<newline>    '
 ++++++android.widget.TextView name='This is another paragraph in fallback' hint='Visibility hidden paragraph in fallback content'
 ++++++android.widget.TextView name='<newline>    '
-++++++android.widget.TextView role_description='heading 1' heading invisible name='Aria hidden paragraph in fallback content'
 ++++++android.widget.TextView name='<newline>    '
-++++++android.widget.TextView role_description='heading 1' heading invisible name='Display none text in fallback content '
 ++++++android.widget.TextView name='<newline>    '
-++++++android.widget.TextView role_description='heading 2' heading invisible name='Visibility hidden paragraph in fallback content'
-++++++android.widget.TextView name='<newline>  '
\ No newline at end of file
+++++++android.widget.TextView name='<newline>  '
diff --git a/content/test/data/accessibility/html/canvas-fallback-expected-auralinux.txt b/content/test/data/accessibility/html/canvas-fallback-expected-auralinux.txt
index f171c61..4d0ffb2 100644
--- a/content/test/data/accessibility/html/canvas-fallback-expected-auralinux.txt
+++ b/content/test/data/accessibility/html/canvas-fallback-expected-auralinux.txt
@@ -4,18 +4,12 @@
 ++++++[static] name='Static fallback'
 ++++[canvas]
 ++++++[static] name='<newline>    '
-++++++[paragraph] description='Aria hidden paragraph in fallback content' described-by
+++++++[paragraph] description='Aria hidden paragraph in fallback content'
 ++++++++[static] name='Line breaking content in a fallback'
 ++++++[static] name='<newline>    '
-++++++[paragraph] description='Visibility hidden paragraph in fallback content' described-by
+++++++[paragraph] description='Visibility hidden paragraph in fallback content'
 ++++++++[static] name='This is another paragraph in fallback'
 ++++++[static] name='<newline>    '
-++++++[heading] description-for
-++++++++[static] name='Aria hidden paragraph in fallback content'
 ++++++[static] name='<newline>    '
-++++++[heading]
-++++++++[static] name='Display none text in fallback content '
 ++++++[static] name='<newline>    '
-++++++[heading] description-for
-++++++++[static] name='Visibility hidden paragraph in fallback content'
-++++++[static] name='<newline>  '
+++++++[static] name='<newline>  '
\ No newline at end of file
diff --git a/content/test/data/accessibility/html/canvas-fallback-expected-blink.txt b/content/test/data/accessibility/html/canvas-fallback-expected-blink.txt
index 9373113..e7caa453 100644
--- a/content/test/data/accessibility/html/canvas-fallback-expected-blink.txt
+++ b/content/test/data/accessibility/html/canvas-fallback-expected-blink.txt
@@ -11,12 +11,12 @@
 ++++++++paragraph description='Visibility hidden paragraph in fallback content'
 ++++++++++staticText name='This is another paragraph in fallback'
 ++++++++staticText name='<newline>    '
-++++++++heading invisible hierarchicalLevel=1
-++++++++++staticText invisible name='Aria hidden paragraph in fallback content'
+++++++++heading ignored invisible
+++++++++++staticText ignored invisible name='Aria hidden paragraph in fallback content'
 ++++++++staticText name='<newline>    '
-++++++++heading invisible hierarchicalLevel=1
-++++++++++staticText invisible name='Display none text in fallback content '
+++++++++heading ignored invisible
+++++++++++staticText ignored invisible name='Display none text in fallback content '
 ++++++++staticText name='<newline>    '
-++++++++heading invisible hierarchicalLevel=2
-++++++++++staticText invisible name='Visibility hidden paragraph in fallback content'
-++++++++staticText name='<newline>  '
\ No newline at end of file
+++++++++heading ignored invisible
+++++++++++staticText ignored invisible name='Visibility hidden paragraph in fallback content'
+++++++++staticText name='<newline>  '
diff --git a/content/test/data/accessibility/html/canvas-fallback-expected-uia-win.txt b/content/test/data/accessibility/html/canvas-fallback-expected-uia-win.txt
index 7fab11d..022f6c5 100644
--- a/content/test/data/accessibility/html/canvas-fallback-expected-uia-win.txt
+++ b/content/test/data/accessibility/html/canvas-fallback-expected-uia-win.txt
@@ -10,12 +10,6 @@
 ++++++Group
 ++++++++Text Name='This is another paragraph in fallback'
 ++++++Text Name='<newline>    '
-++++++Text IsControlElement=false
-++++++++Text Name='Aria hidden paragraph in fallback content' IsControlElement=false
 ++++++Text Name='<newline>    '
-++++++Text IsControlElement=false
-++++++++Text Name='Display none text in fallback content ' IsControlElement=false
 ++++++Text Name='<newline>    '
-++++++Text IsControlElement=false
-++++++++Text Name='Visibility hidden paragraph in fallback content' IsControlElement=false
-++++++Text Name='<newline>  '
+++++++Text Name='<newline>  '
\ No newline at end of file
diff --git a/content/test/data/accessibility/html/canvas-fallback-expected-win.txt b/content/test/data/accessibility/html/canvas-fallback-expected-win.txt
index c032886..50daa2c 100644
--- a/content/test/data/accessibility/html/canvas-fallback-expected-win.txt
+++ b/content/test/data/accessibility/html/canvas-fallback-expected-win.txt
@@ -10,12 +10,6 @@
 ++++++IA2_ROLE_PARAGRAPH description='Visibility hidden paragraph in fallback content'
 ++++++++ROLE_SYSTEM_STATICTEXT
 ++++++ROLE_SYSTEM_STATICTEXT
-++++++IA2_ROLE_HEADING INVISIBLE
-++++++++ROLE_SYSTEM_STATICTEXT INVISIBLE
 ++++++ROLE_SYSTEM_STATICTEXT
-++++++IA2_ROLE_HEADING INVISIBLE
-++++++++ROLE_SYSTEM_STATICTEXT INVISIBLE
 ++++++ROLE_SYSTEM_STATICTEXT
-++++++IA2_ROLE_HEADING INVISIBLE
-++++++++ROLE_SYSTEM_STATICTEXT INVISIBLE
 ++++++ROLE_SYSTEM_STATICTEXT
\ No newline at end of file
diff --git a/content/test/data/accessibility/html/inert-attribute-expected-blink.txt b/content/test/data/accessibility/html/inert-attribute-expected-blink.txt
index 2d312bd..ba0cc6c 100644
--- a/content/test/data/accessibility/html/inert-attribute-expected-blink.txt
+++ b/content/test/data/accessibility/html/inert-attribute-expected-blink.txt
@@ -17,12 +17,12 @@
 ++++++genericContainer focusable htmlTag='div' name=' consectetur adipiscing tempor '
 ++++++++staticText name='<newline>  consectetur<newline>  '
 ++++++++genericContainer ignored invisible htmlTag='span'
-++++++++++staticText invisible name='adipiscing'
+++++++++++staticText ignored invisible name='adipiscing'
 ++++++++staticText name='<newline>  '
-++++++++genericContainer htmlTag='div'
+++++++++genericContainer ignored htmlTag='div'
 ++++++++++staticText name='<newline>    '
 ++++++++++genericContainer ignored invisible htmlTag='span'
-++++++++++++staticText invisible name='tempor'
+++++++++++++staticText ignored invisible name='tempor'
 ++++++++++staticText name='<newline>  '
 ++++++++staticText name='<newline>'
 ++++++canvas focusable htmlTag='canvas' name=' sed do eiusmod tempor '
@@ -39,11 +39,11 @@
 ++++++++++++staticText invisible name='tempor'
 ++++++++++staticText name='<newline>  '
 ++++++++staticText name='<newline>  '
-++++++++genericContainer invisible htmlTag='div'
-++++++++++staticText invisible name='<newline>    '
+++++++++genericContainer ignored invisible htmlTag='div'
+++++++++++staticText ignored invisible name='<newline>    '
 ++++++++++genericContainer ignored invisible htmlTag='span'
-++++++++++++staticText invisible name='incididunt'
-++++++++++staticText invisible name='<newline>  '
+++++++++++++staticText ignored invisible name='incididunt'
+++++++++++staticText ignored invisible name='<newline>  '
 ++++++++staticText name='<newline>'
 ++++++iframe htmlTag='iframe'
 ++++++++rootWebArea focusable htmlTag='#document'
@@ -81,4 +81,4 @@
 ++++++++++++++++++genericContainer ignored invisible htmlTag='html'
 ++++++++++++++++++++genericContainer ignored invisible htmlTag='body'
 ++++++++++++++++++++++genericContainer ignored invisible htmlTag='div'
-++++++++++++++++++++++++staticText ignored invisible name='Sandboxed frame nested in sandboxed inert frame'
\ No newline at end of file
+++++++++++++++++++++++++staticText ignored invisible name='Sandboxed frame nested in sandboxed inert frame'
diff --git a/content/test/data/accessibility/html/map-any-contents-expected-android-external.txt b/content/test/data/accessibility/html/map-any-contents-expected-android-external.txt
index c0df62c..a77a1a9 100644
--- a/content/test/data/accessibility/html/map-any-contents-expected-android-external.txt
+++ b/content/test/data/accessibility/html/map-any-contents-expected-android-external.txt
@@ -7,5 +7,4 @@
 ++++++++TextView text:"So are " actions:[AX_FOCUS, NEXT, PREVIOUS] bundle:[chromeRole="staticText"]
 ++++++++View text:"other elements" actions:[AX_FOCUS, NEXT, PREVIOUS] bundle:[chromeRole="mark", roleDescription="highlight"]
 ++++++++TextView text:"!" actions:[AX_FOCUS, NEXT, PREVIOUS] bundle:[chromeRole="staticText"]
-++++++TextView text:"pipe2" clickable actions:[CLICK, AX_FOCUS, NEXT, PREVIOUS] bundle:[chromeRole="staticText", clickableScore="200"]
-++++++Button text:"Even a button" clickable focusable actions:[FOCUS, CLICK, AX_FOCUS, NEXT, PREVIOUS] bundle:[chromeRole="button", clickableScore="300", roleDescription="button"]
\ No newline at end of file
+++++++Button text:"Even a button" clickable focusable actions:[FOCUS, CLICK, AX_FOCUS, NEXT, PREVIOUS] bundle:[chromeRole="button", clickableScore="300", roleDescription="button"]
diff --git a/content/test/data/accessibility/html/map-any-contents-expected-auralinux.txt b/content/test/data/accessibility/html/map-any-contents-expected-auralinux.txt
index 59d1f39d..0483293 100644
--- a/content/test/data/accessibility/html/map-any-contents-expected-auralinux.txt
+++ b/content/test/data/accessibility/html/map-any-contents-expected-auralinux.txt
@@ -9,5 +9,4 @@
 ++++++++[static]
 ++++++++++[static] name='other elements'
 ++++++++[static] name='!'
-++++++[static] name='pipe2'
 ++++++[push button] name='Even a button'
\ No newline at end of file
diff --git a/content/test/data/accessibility/html/map-any-contents-expected-blink.txt b/content/test/data/accessibility/html/map-any-contents-expected-blink.txt
index f88a81d..62dc040 100644
--- a/content/test/data/accessibility/html/map-any-contents-expected-blink.txt
+++ b/content/test/data/accessibility/html/map-any-contents-expected-blink.txt
@@ -14,7 +14,7 @@
 ++++++++++++++inlineTextBox name='other elements'
 ++++++++++staticText name='!'
 ++++++++++++inlineTextBox name='!'
-++++++++staticText name='pipe2'
+++++++++staticText ignored name='pipe2'
 ++++++++button name='Even a button'
 ++++++++++staticText name='Even a button'
-++++++++++++inlineTextBox name='Even a button'
\ No newline at end of file
+++++++++++++inlineTextBox name='Even a button'
diff --git a/content/test/data/direct_sockets/udp.js b/content/test/data/direct_sockets/udp.js
index a2cbb669..a01cd991 100644
--- a/content/test/data/direct_sockets/udp.js
+++ b/content/test/data/direct_sockets/udp.js
@@ -6,10 +6,37 @@
   }
 };
 
-async function sendLoop(writer, requiredBytes) {
+async function launchUdpEchoServer(server, requiredBytes, clientAddress, clientPort) {
+  let bytesEchoed = 0;
+
+  const { readable, writable } = await server.opened;
+  const reader = readable.getReader();
+  const writer = writable.getWriter();
+
+  while (bytesEchoed < requiredBytes) {
+    const { value: { data, remoteAddress, remotePort }, done } = await reader.read();
+    assertEq(done, false);
+    assertEq(remoteAddress, clientAddress);
+    assertEq(remotePort, clientPort);
+    for (let index = 0; index < data.length; index++) {
+      assertEq(data[index], bytesEchoed % 256);
+      bytesEchoed++;
+    }
+    await writer.write({ data, remoteAddress, remotePort });
+  }
+
+  assertEq(bytesEchoed, requiredBytes);
+  reader.releaseLock();
+  writer.releaseLock();
+}
+
+async function sendLoop(socket, requiredBytes) {
   let bytesWritten = 0;
   let chunkLength = 0;
 
+  const { writable } = await socket.opened;
+  const writer = writable.getWriter();
+
   while (bytesWritten < requiredBytes) {
     chunkLength = Math.min(chunkLength + 1,
                            requiredBytes - bytesWritten);
@@ -21,41 +48,28 @@
     await writer.ready;
     await writer.write({ data: chunk });
   }
-  return 'send succeeded';
+  assertEq(bytesWritten, requiredBytes);
+
+  writer.releaseLock();
 }
 
-async function readLoop(reader, requiredBytes) {
+async function readLoop(socket, requiredBytes) {
   let bytesRead = 0;
+
+  const { readable } = await socket.opened;
+  const reader = readable.getReader();
+
   while (bytesRead < requiredBytes) {
-    const { value, done } = await reader.read();
-    if (done) {
-      return 'readLoop failed: stream closed prematurely.';
-    }
-
-    const { data } = value;
-    if (!data || data.length === 0) {
-      return 'readLoop failed: no data returned.';
-    }
-
+    const { value: { data }, done } = await reader.read();
+    assertEq(done, false);
     for (let index = 0; index < data.length; index++) {
-      if (data[index] != bytesRead % 256) {
-        console.log(`Expected ${bytesRead % 256}, received ${data[index]}`);
-        return 'readLoop failed: bad data.';
-      }
+      assertEq(data[index], bytesRead % 256);
       bytesRead++;
     }
   }
-  return 'readLoop succeeded.';
-}
+  assertEq(bytesRead, requiredBytes);
 
-async function sendUdp(options, requiredBytes) {
-  try {
-    let udpSocket = new UDPSocket(options);
-    let { writable } = await udpSocket.opened;
-    return await sendLoop(writable.getWriter(), requiredBytes);
-  } catch (error) {
-    return ('sendUdp failed: ' + error);
-  }
+  reader.releaseLock();
 }
 
 async function closeUdp(options) {
@@ -75,8 +89,7 @@
     let { writable } = await udpSocket.opened;
     await udpSocket.close();
 
-    const writer = writable.getWriter();
-    return await sendLoop(writer, requiredBytes);
+    return await sendLoop(udpSocket, requiredBytes);
   } catch (error) {
     return ('send failed: ' + error);
   }
@@ -169,8 +182,10 @@
   }
 }
 
-async function exchangeSingleUdpPacketBetweenClientAndServer() {
-  const kPacket = "I'm a UDP packet. Meow-meow!";
+async function exchangeUdpPacketsBetweenClientAndServer() {
+  const kRequiredDatagrams = 35;
+  const kRequiredBytes =
+    kRequiredDatagrams * (kRequiredDatagrams + 1) / 2;
 
   try {
     // |localPort| is intentionally omitted so that the OS will pick one itself.
@@ -182,96 +197,18 @@
       remoteAddress: "127.0.0.1",
       remotePort: serverSocketPort
     });
+    const { localAddress: clientLocalAddress, localPort: clientLocalPort } = await clientSocket.opened;
 
-    const encoder = new TextEncoder();
-    const decoder = new TextDecoder();
-
-    // Waits for a packet to arrive and then echoes it back to
-    // the original sender.
-    async function serverEcho() {
-      const {
-        readable: serverReadable,
-        writable: serverWritable,
-      } = await serverSocket.opened;
-
-      const reader = serverReadable.getReader();
-      const writer = serverWritable.getWriter();
-
-      const { value: {
-        data,
-        remoteAddress: packetRemoteAddress,
-        remotePort: packetRemotePort
-      }, done } = await reader.read();
-
-      // Stream shouldn't be exhausted yet.
-      assertEq(done, false);
-
-      // Data should match.
-      assertEq(kPacket, decoder.decode(data));
-
-      const {
-        localAddress: clientLocalAddr,
-        localPort: clientLocalPort
-      } = await clientSocket.opened;
-
-      // Check that the packet arrived from the client.
-      assertEq(clientLocalAddr, packetRemoteAddress);
-      assertEq(clientLocalPort, packetRemotePort);
-
-      // Echo back.
-      await writer.ready;
-      writer.write({
-        data,
-        remoteAddress: clientLocalAddr,
-        remotePort: clientLocalPort
-      });
-
-      reader.releaseLock();
-      writer.releaseLock();
-    }
-
-    serverEcho();
-
-    // Sends the initial packet from client to server.
-    async function clientSend() {
-      const { writable: clientWritable } = await clientSocket.opened;
-      const writer = clientWritable.getWriter();
-      writer.write({ data: encoder.encode(kPacket) });
-      writer.releaseLock();
-    }
-
-    clientSend();
-
-    // Waits for the server to respond and verifies that the packet received
-    // is indeed the original one.
-    async function clientReceive() {
-      const { readable: clientReadable } = await clientSocket.opened;
-      const reader = clientReadable.getReader();
-
-      const { value: {
-        data, remoteAddress, remotePort
-      }, done } = await reader.read();
-      reader.releaseLock();
-
-      // Stream shouldn't be exhausted yet.
-      assertEq(done, false);
-
-      // Data should match.
-      assertEq(kPacket, decoder.decode(data));
-
-      // In connected mode remoteAddress/remotePort should be undefined.
-      assertEq(remoteAddress, undefined);
-      assertEq(remotePort, undefined);
-    }
-
-    await clientReceive();
+    launchUdpEchoServer(serverSocket, kRequiredBytes, clientLocalAddress, clientLocalPort);
+    sendLoop(clientSocket, kRequiredBytes);
+    await readLoop(clientSocket, kRequiredBytes);
 
     await clientSocket.close();
     await serverSocket.close();
 
-    return "exchangeSingleUdpPacketBetweenClientAndServer succeeded.";
+    return "exchangeUdpPacketsBetweenClientAndServer succeeded.";
   } catch (error) {
-    return "exchangeSingleUdpPacketBetweenClientAndServer failed: " + error;
+    return "exchangeUdpPacketsBetweenClientAndServer failed: " + error;
   }
 }
 
diff --git a/content/test/data/interest_group/component_auction_component_decision_argument_validator.js b/content/test/data/interest_group/component_auction_component_decision_argument_validator.js
index 6c189579..9c7c7fe 100644
--- a/content/test/data/interest_group/component_auction_component_decision_argument_validator.js
+++ b/content/test/data/interest_group/component_auction_component_decision_argument_validator.js
@@ -38,7 +38,7 @@
 }
 
 function validateAuctionConfig(auctionConfig) {
-  if (Object.keys(auctionConfig).length !== 10) {
+  if (Object.keys(auctionConfig).length !== 11) {
     throw 'Wrong number of auctionConfig fields ' +
         JSON.stringify(auctionConfig);
   }
@@ -87,6 +87,11 @@
         JSON.stringify(auctionConfig.perBuyerTimeouts);
   }
 
+  if (auctionConfig.perBuyerCumulativeTimeouts[buyerOrigin] !== 201) {
+    throw 'Wrong perBuyerCumulativeTimeouts ' +
+        JSON.stringify(auctionConfig.perBuyerCumulativeTimeouts);
+  }
+
   const perBuyerPrioritySignals = auctionConfig.perBuyerPrioritySignals;
   if (Object.keys(perBuyerPrioritySignals).length !== 2 ||
       JSON.stringify(perBuyerPrioritySignals[buyerOrigin]) !==
diff --git a/content/test/data/interest_group/component_auction_top_level_decision_argument_validator.js b/content/test/data/interest_group/component_auction_top_level_decision_argument_validator.js
index 46fd97ae..0dc46876 100644
--- a/content/test/data/interest_group/component_auction_top_level_decision_argument_validator.js
+++ b/content/test/data/interest_group/component_auction_top_level_decision_argument_validator.js
@@ -39,7 +39,7 @@
 }
 
 function validateAuctionConfig(auctionConfig) {
-  if (Object.keys(auctionConfig).length !== 10) {
+  if (Object.keys(auctionConfig).length !== 11) {
     throw 'Wrong number of auctionConfig fields ' +
         JSON.stringify(auctionConfig);
   }
@@ -87,6 +87,14 @@
     throw 'Wrong perBuyerTimeouts ' + perBuyerTimeoutsJson;
   }
 
+  const perBuyerCumulativeTimeoutsJson =
+      JSON.stringify(auctionConfig.perBuyerCumulativeTimeouts);
+  if (!perBuyerCumulativeTimeoutsJson.includes('a.test') ||
+      !perBuyerCumulativeTimeoutsJson.includes('111') ||
+      auctionConfig.perBuyerCumulativeTimeouts['*'] != 151) {
+    throw 'Wrong perBuyerCumulativeTimeouts ' + perBuyerCumulativeTimeoutsJson;
+  }
+
   const perBuyerPrioritySignalsJson =
       JSON.stringify(auctionConfig.perBuyerPrioritySignals);
   if (Object.keys(auctionConfig.perBuyerPrioritySignals).length !== 1 ||
diff --git a/content/test/data/interest_group/decision_argument_validator.js b/content/test/data/interest_group/decision_argument_validator.js
index 852775a..bd8f3a82 100644
--- a/content/test/data/interest_group/decision_argument_validator.js
+++ b/content/test/data/interest_group/decision_argument_validator.js
@@ -27,7 +27,7 @@
 }
 
 function validateAuctionConfig(auctionConfig) {
-  if (Object.keys(auctionConfig).length !== 10) {
+  if (Object.keys(auctionConfig).length !== 11) {
     throw 'Wrong number of auctionConfig fields ' +
         JSON.stringify(auctionConfig);
   }
@@ -87,6 +87,13 @@
         JSON.stringify(auctionConfig.perBuyerTimeouts);
   }
 
+  if (auctionConfig.perBuyerCumulativeTimeouts[buyerAOrigin] !== 130 ||
+      auctionConfig.perBuyerCumulativeTimeouts[buyerBOrigin] !== 140 ||
+      auctionConfig.perBuyerCumulativeTimeouts['*'] !== 160) {
+    throw 'Wrong perBuyerCumulativeTimeouts ' +
+        JSON.stringify(auctionConfig.perBuyerCumulativeTimeouts);
+  }
+
   const perBuyerPrioritySignals = auctionConfig.perBuyerPrioritySignals;
   if (Object.keys(perBuyerPrioritySignals).length !== 2 ||
       JSON.stringify(perBuyerPrioritySignals[buyerAOrigin]) !==
diff --git a/content/test/test_select_url_fenced_frame_config_observer_impl.cc b/content/test/test_select_url_fenced_frame_config_observer_impl.cc
new file mode 100644
index 0000000..e80571f
--- /dev/null
+++ b/content/test/test_select_url_fenced_frame_config_observer_impl.cc
@@ -0,0 +1,96 @@
+// 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 "content/test/test_select_url_fenced_frame_config_observer_impl.h"
+
+#include "content/public/browser/storage_partition.h"
+#include "content/public/test/shared_storage_test_utils.h"
+#include "content/public/test/test_select_url_fenced_frame_config_observer.h"
+#include "url/gurl.h"
+
+namespace content {
+
+TestSelectURLFencedFrameConfigObserverImpl::
+    TestSelectURLFencedFrameConfigObserverImpl() = default;
+TestSelectURLFencedFrameConfigObserverImpl::
+    ~TestSelectURLFencedFrameConfigObserverImpl() = default;
+
+void TestSelectURLFencedFrameConfigObserverImpl::OnSharedStorageAccessed(
+    const base::Time& access_time,
+    AccessType type,
+    const std::string& main_frame_id,
+    const std::string& owner_origin,
+    const SharedStorageEventParams& params) {}
+
+void TestSelectURLFencedFrameConfigObserverImpl::OnUrnUuidGenerated(
+    const GURL& urn_uuid) {
+  if (urn_uuid_.has_value()) {
+    // This observer has already observed an urn::uuid.
+    return;
+  }
+  urn_uuid_ = urn_uuid;
+}
+
+void TestSelectURLFencedFrameConfigObserverImpl::OnConfigPopulated(
+    const absl::optional<FencedFrameConfig>& config) {
+  if (config_observed_ || !urn_uuid_.has_value() || !config.has_value() ||
+      (urn_uuid_.value() != config->urn_uuid_)) {
+    // 1. This observer has already observed a config.
+    // 2. This observer hasn't observed an urn::uuid yet.
+    // 3. The given config is `absl::nullopt`.
+    // 4. The given config does not correspond to the observed urn::uuid.
+    return;
+  }
+  config_observed_ = true;
+  config_ = config;
+}
+
+const absl::optional<GURL>&
+TestSelectURLFencedFrameConfigObserverImpl::GetUrnUuid() const {
+  return urn_uuid_;
+}
+
+const absl::optional<FencedFrameConfig>&
+TestSelectURLFencedFrameConfigObserverImpl::GetConfig() const {
+  return config_;
+}
+
+bool TestSelectURLFencedFrameConfigObserverImpl::ConfigObserved() const {
+  return config_observed_;
+}
+
+TestSelectURLFencedFrameConfigObserver::TestSelectURLFencedFrameConfigObserver(
+    StoragePartition* storage_partition)
+    : storage_partition_(storage_partition),
+      impl_(std::make_unique<TestSelectURLFencedFrameConfigObserverImpl>()) {
+  SharedStorageWorkletHostManager* manager =
+      GetSharedStorageWorkletHostManagerForStoragePartition(storage_partition_);
+  DCHECK(manager);
+
+  manager->AddSharedStorageObserver(impl_.get());
+}
+
+TestSelectURLFencedFrameConfigObserver::
+    ~TestSelectURLFencedFrameConfigObserver() {
+  SharedStorageWorkletHostManager* manager =
+      GetSharedStorageWorkletHostManagerForStoragePartition(storage_partition_);
+
+  manager->RemoveSharedStorageObserver(impl_.get());
+}
+
+const absl::optional<GURL>& TestSelectURLFencedFrameConfigObserver::GetUrnUuid()
+    const {
+  return impl_->GetUrnUuid();
+}
+
+const absl::optional<FencedFrameConfig>&
+TestSelectURLFencedFrameConfigObserver::GetConfig() const {
+  return impl_->GetConfig();
+}
+
+bool TestSelectURLFencedFrameConfigObserver::ConfigObserved() const {
+  return impl_->ConfigObserved();
+}
+
+}  // namespace content
\ No newline at end of file
diff --git a/content/test/test_select_url_fenced_frame_config_observer_impl.h b/content/test/test_select_url_fenced_frame_config_observer_impl.h
new file mode 100644
index 0000000..7f46a9f
--- /dev/null
+++ b/content/test/test_select_url_fenced_frame_config_observer_impl.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 CONTENT_TEST_TEST_SELECT_URL_FENCED_FRAME_CONFIG_OBSERVER_IMPL_H_
+#define CONTENT_TEST_TEST_SELECT_URL_FENCED_FRAME_CONFIG_OBSERVER_IMPL_H_
+
+#include "content/browser/fenced_frame/fenced_frame_config.h"
+#include "content/browser/shared_storage/shared_storage_worklet_host_manager.h"
+
+class GURL;
+
+namespace content {
+
+class TestSelectURLFencedFrameConfigObserverImpl
+    : public SharedStorageWorkletHostManager::SharedStorageObserverInterface {
+ public:
+  TestSelectURLFencedFrameConfigObserverImpl();
+  ~TestSelectURLFencedFrameConfigObserverImpl() override;
+
+  void OnSharedStorageAccessed(const base::Time& access_time,
+                               AccessType type,
+                               const std::string& main_frame_id,
+                               const std::string& owner_origin,
+                               const SharedStorageEventParams& params) override;
+  void OnUrnUuidGenerated(const GURL& urn_uuid) override;
+  void OnConfigPopulated(
+      const absl::optional<FencedFrameConfig>& config) override;
+
+  const absl::optional<GURL>& GetUrnUuid() const;
+  const absl::optional<FencedFrameConfig>& GetConfig() const;
+  bool ConfigObserved() const;
+
+ private:
+  absl::optional<GURL> urn_uuid_;
+  absl::optional<FencedFrameConfig> config_;
+  bool config_observed_ = false;
+};
+
+}  // namespace content
+
+#endif  // CONTENT_TEST_TEST_SELECT_URL_FENCED_FRAME_CONFIG_OBSERVER_IMPL_H_
\ No newline at end of file
diff --git a/device/fido/fido_request_handler_base.h b/device/fido/fido_request_handler_base.h
index 8160380b..07dcf89 100644
--- a/device/fido/fido_request_handler_base.h
+++ b/device/fido/fido_request_handler_base.h
@@ -70,6 +70,10 @@
     // list.
     bool has_empty_allow_list = false;
 
+    // is_only_hybrid_or_internal is true if credentials in the allow-list only
+    // contain the hybrid or internal transports.
+    bool is_only_hybrid_or_internal = false;
+
     // The intersection of transports supported by the client and allowed by the
     // relying party.
     base::flat_set<FidoTransportProtocol> available_transports;
diff --git a/device/fido/get_assertion_handler_unittest.cc b/device/fido/get_assertion_handler_unittest.cc
index a68d6ab..0ddc8df 100644
--- a/device/fido/get_assertion_handler_unittest.cc
+++ b/device/fido/get_assertion_handler_unittest.cc
@@ -30,6 +30,7 @@
 #include "device/fido/hid/fake_hid_impl_for_testing.h"
 #include "device/fido/make_credential_task.h"
 #include "device/fido/mock_fido_device.h"
+#include "device/fido/public_key_credential_descriptor.h"
 #include "device/fido/test_callback_receiver.h"
 #include "device/fido/u2f_command_constructor.h"
 #include "device/fido/virtual_fido_device_factory.h"
@@ -127,6 +128,19 @@
     return handler;
   }
 
+  std::unique_ptr<GetAssertionRequestHandler>
+  CreateGetAssertionHandlerWithRequestedTransports(
+      std::vector<std::vector<FidoTransportProtocol>> transports) {
+    CtapGetAssertionRequest request(test_data::kRelyingPartyId,
+                                    test_data::kClientDataJson);
+    for (uint8_t i = 0; i < transports.size(); ++i) {
+      request.allow_list.emplace_back(CredentialType::kPublicKey,
+                                      std::vector<uint8_t>{i});
+      request.allow_list.back().transports = transports[i];
+    }
+    return CreateGetAssertionHandlerWithRequest(std::move(request));
+  }
+
   void ExpectAllowedTransportsForRequestAre(
       GetAssertionRequestHandler* request_handler,
       base::flat_set<FidoTransportProtocol> transports) {
@@ -205,12 +219,73 @@
 };
 
 TEST_F(FidoGetAssertionHandlerTest, TransportAvailabilityInfo) {
-  auto request_handler =
-      CreateGetAssertionHandlerWithRequest(CtapGetAssertionRequest(
-          test_data::kRelyingPartyId, test_data::kClientDataJson));
-
-  EXPECT_EQ(FidoRequestType::kGetAssertion,
-            request_handler->transport_availability_info().request_type);
+  {
+    // Empty allow list.
+    auto request_handler = CreateGetAssertionHandlerWithRequestedTransports({});
+    EXPECT_EQ(FidoRequestType::kGetAssertion,
+              request_handler->transport_availability_info().request_type);
+    EXPECT_FALSE(request_handler->transport_availability_info()
+                     .transport_list_did_include_internal);
+    EXPECT_TRUE(
+        request_handler->transport_availability_info().has_empty_allow_list);
+    EXPECT_FALSE(request_handler->transport_availability_info()
+                     .is_only_hybrid_or_internal);
+  }
+  {
+    // Internal and a phone.
+    auto request_handler = CreateGetAssertionHandlerWithRequestedTransports(
+        {{FidoTransportProtocol::kInternal},
+         {FidoTransportProtocol::kInternal, FidoTransportProtocol::kHybrid}});
+    EXPECT_EQ(FidoRequestType::kGetAssertion,
+              request_handler->transport_availability_info().request_type);
+    EXPECT_TRUE(request_handler->transport_availability_info()
+                    .transport_list_did_include_internal);
+    EXPECT_FALSE(
+        request_handler->transport_availability_info().has_empty_allow_list);
+    EXPECT_TRUE(request_handler->transport_availability_info()
+                    .is_only_hybrid_or_internal);
+  }
+  {
+    // Internal, a phone, and USB.
+    auto request_handler = CreateGetAssertionHandlerWithRequestedTransports(
+        {{FidoTransportProtocol::kUsbHumanInterfaceDevice},
+         {FidoTransportProtocol::kInternal},
+         {FidoTransportProtocol::kInternal, FidoTransportProtocol::kHybrid}});
+    EXPECT_EQ(FidoRequestType::kGetAssertion,
+              request_handler->transport_availability_info().request_type);
+    EXPECT_TRUE(request_handler->transport_availability_info()
+                    .transport_list_did_include_internal);
+    EXPECT_FALSE(
+        request_handler->transport_availability_info().has_empty_allow_list);
+    EXPECT_FALSE(request_handler->transport_availability_info()
+                     .is_only_hybrid_or_internal);
+  }
+  {
+    // Only USB.
+    auto request_handler = CreateGetAssertionHandlerWithRequestedTransports(
+        {{FidoTransportProtocol::kUsbHumanInterfaceDevice}});
+    EXPECT_EQ(FidoRequestType::kGetAssertion,
+              request_handler->transport_availability_info().request_type);
+    EXPECT_FALSE(request_handler->transport_availability_info()
+                     .transport_list_did_include_internal);
+    EXPECT_FALSE(
+        request_handler->transport_availability_info().has_empty_allow_list);
+    EXPECT_FALSE(request_handler->transport_availability_info()
+                     .is_only_hybrid_or_internal);
+  }
+  {
+    // A phone and an unknown (empty) transport credential.
+    auto request_handler = CreateGetAssertionHandlerWithRequestedTransports(
+        {{}, {FidoTransportProtocol::kHybrid}});
+    EXPECT_EQ(FidoRequestType::kGetAssertion,
+              request_handler->transport_availability_info().request_type);
+    EXPECT_TRUE(request_handler->transport_availability_info()
+                    .transport_list_did_include_internal);
+    EXPECT_FALSE(
+        request_handler->transport_availability_info().has_empty_allow_list);
+    EXPECT_FALSE(request_handler->transport_availability_info()
+                     .is_only_hybrid_or_internal);
+  }
 }
 
 TEST_F(FidoGetAssertionHandlerTest, CtapRequestOnSingleDevice) {
diff --git a/device/fido/get_assertion_request_handler.cc b/device/fido/get_assertion_request_handler.cc
index c46f345..fd1d11c 100644
--- a/device/fido/get_assertion_request_handler.cc
+++ b/device/fido/get_assertion_request_handler.cc
@@ -27,8 +27,10 @@
 #include "device/fido/fido_authenticator.h"
 #include "device/fido/fido_discovery_factory.h"
 #include "device/fido/fido_parsing_utils.h"
+#include "device/fido/fido_transport_protocol.h"
 #include "device/fido/filter.h"
 #include "device/fido/pin.h"
+#include "device/fido/public_key_credential_descriptor.h"
 
 #if BUILDFLAG(IS_MAC)
 #include "device/fido/mac/authenticator.h"
@@ -326,6 +328,21 @@
   return request;
 }
 
+bool IsOnlyHybridOrInternal(const PublicKeyCredentialDescriptor& credential) {
+  if (credential.transports.empty()) {
+    return false;
+  }
+  return base::ranges::all_of(credential.transports, [](const auto& transport) {
+    return transport == FidoTransportProtocol::kHybrid ||
+           transport == FidoTransportProtocol::kInternal;
+  });
+}
+
+bool AllowListOnlyHybridOrInternal(const CtapGetAssertionRequest& request) {
+  return !request.allow_list.empty() &&
+         base::ranges::all_of(request.allow_list, &IsOnlyHybridOrInternal);
+}
+
 }  // namespace
 
 GetAssertionRequestHandler::GetAssertionRequestHandler(
@@ -347,6 +364,8 @@
   transport_availability_info().request_type = FidoRequestType::kGetAssertion;
   transport_availability_info().has_empty_allow_list =
       request_.allow_list.empty();
+  transport_availability_info().is_only_hybrid_or_internal =
+      AllowListOnlyHybridOrInternal(request_);
   transport_availability_info().is_off_the_record_context =
       options_.is_off_the_record_context;
   transport_availability_info().transport_list_did_include_internal =
diff --git a/extensions/browser/api/bluetooth_socket/bluetooth_socket_api.cc b/extensions/browser/api/bluetooth_socket/bluetooth_socket_api.cc
index e0ab89f..bcccf01 100644
--- a/extensions/browser/api/bluetooth_socket/bluetooth_socket_api.cc
+++ b/extensions/browser/api/bluetooth_socket/bluetooth_socket_api.cc
@@ -123,9 +123,9 @@
 
 }  // namespace
 
-BluetoothSocketAsyncApiFunction::BluetoothSocketAsyncApiFunction() {}
+BluetoothSocketAsyncApiFunction::BluetoothSocketAsyncApiFunction() = default;
 
-BluetoothSocketAsyncApiFunction::~BluetoothSocketAsyncApiFunction() {}
+BluetoothSocketAsyncApiFunction::~BluetoothSocketAsyncApiFunction() = default;
 
 bool BluetoothSocketAsyncApiFunction::PreRunValidation(std::string* error) {
   if (!ExtensionFunction::PreRunValidation(error))
@@ -172,9 +172,9 @@
   return manager_->GetResourceIds(extension_id());
 }
 
-BluetoothSocketCreateFunction::BluetoothSocketCreateFunction() {}
+BluetoothSocketCreateFunction::BluetoothSocketCreateFunction() = default;
 
-BluetoothSocketCreateFunction::~BluetoothSocketCreateFunction() {}
+BluetoothSocketCreateFunction::~BluetoothSocketCreateFunction() = default;
 
 ExtensionFunction::ResponseAction BluetoothSocketCreateFunction::Run() {
   DCHECK_CURRENTLY_ON(work_thread_id());
@@ -195,9 +195,9 @@
       ArgumentList(bluetooth_socket::Create::Results::Create(create_info)));
 }
 
-BluetoothSocketUpdateFunction::BluetoothSocketUpdateFunction() {}
+BluetoothSocketUpdateFunction::BluetoothSocketUpdateFunction() = default;
 
-BluetoothSocketUpdateFunction::~BluetoothSocketUpdateFunction() {}
+BluetoothSocketUpdateFunction::~BluetoothSocketUpdateFunction() = default;
 
 ExtensionFunction::ResponseAction BluetoothSocketUpdateFunction::Run() {
   auto params = bluetooth_socket::Update::Params::Create(args());
@@ -211,9 +211,9 @@
   return RespondNow(ArgumentList(bluetooth_socket::Update::Results::Create()));
 }
 
-BluetoothSocketSetPausedFunction::BluetoothSocketSetPausedFunction() {}
+BluetoothSocketSetPausedFunction::BluetoothSocketSetPausedFunction() = default;
 
-BluetoothSocketSetPausedFunction::~BluetoothSocketSetPausedFunction() {}
+BluetoothSocketSetPausedFunction::~BluetoothSocketSetPausedFunction() = default;
 
 ExtensionFunction::ResponseAction BluetoothSocketSetPausedFunction::Run() {
   auto params = bluetooth_socket::SetPaused::Params::Create(args());
@@ -240,9 +240,9 @@
       ArgumentList(bluetooth_socket::SetPaused::Results::Create()));
 }
 
-BluetoothSocketListenFunction::BluetoothSocketListenFunction() {}
+BluetoothSocketListenFunction::BluetoothSocketListenFunction() = default;
 
-BluetoothSocketListenFunction::~BluetoothSocketListenFunction() {}
+BluetoothSocketListenFunction::~BluetoothSocketListenFunction() = default;
 
 bool BluetoothSocketListenFunction::PreRunValidation(std::string* error) {
   if (!BluetoothSocketAsyncApiFunction::PreRunValidation(error))
@@ -489,9 +489,9 @@
   Respond(Error(message));
 }
 
-BluetoothSocketConnectFunction::BluetoothSocketConnectFunction() {}
+BluetoothSocketConnectFunction::BluetoothSocketConnectFunction() = default;
 
-BluetoothSocketConnectFunction::~BluetoothSocketConnectFunction() {}
+BluetoothSocketConnectFunction::~BluetoothSocketConnectFunction() = default;
 
 void BluetoothSocketConnectFunction::ConnectToService(
     device::BluetoothDevice* device,
@@ -501,9 +501,11 @@
       base::BindOnce(&BluetoothSocketConnectFunction::OnConnectError, this));
 }
 
-BluetoothSocketDisconnectFunction::BluetoothSocketDisconnectFunction() {}
+BluetoothSocketDisconnectFunction::BluetoothSocketDisconnectFunction() =
+    default;
 
-BluetoothSocketDisconnectFunction::~BluetoothSocketDisconnectFunction() {}
+BluetoothSocketDisconnectFunction::~BluetoothSocketDisconnectFunction() =
+    default;
 
 ExtensionFunction::ResponseAction BluetoothSocketDisconnectFunction::Run() {
   DCHECK_CURRENTLY_ON(work_thread_id());
@@ -525,7 +527,7 @@
   Respond(ArgumentList(bluetooth_socket::Disconnect::Results::Create()));
 }
 
-BluetoothSocketCloseFunction::BluetoothSocketCloseFunction() {}
+BluetoothSocketCloseFunction::BluetoothSocketCloseFunction() = default;
 
 BluetoothSocketCloseFunction::~BluetoothSocketCloseFunction() = default;
 
@@ -543,7 +545,7 @@
 BluetoothSocketSendFunction::BluetoothSocketSendFunction()
     : io_buffer_size_(0) {}
 
-BluetoothSocketSendFunction::~BluetoothSocketSendFunction() {}
+BluetoothSocketSendFunction::~BluetoothSocketSendFunction() = default;
 
 ExtensionFunction::ResponseAction BluetoothSocketSendFunction::Run() {
   DCHECK_CURRENTLY_ON(work_thread_id());
@@ -577,9 +579,9 @@
   Respond(Error(message));
 }
 
-BluetoothSocketGetInfoFunction::BluetoothSocketGetInfoFunction() {}
+BluetoothSocketGetInfoFunction::BluetoothSocketGetInfoFunction() = default;
 
-BluetoothSocketGetInfoFunction::~BluetoothSocketGetInfoFunction() {}
+BluetoothSocketGetInfoFunction::~BluetoothSocketGetInfoFunction() = default;
 
 ExtensionFunction::ResponseAction BluetoothSocketGetInfoFunction::Run() {
   auto params = bluetooth_socket::GetInfo::Params::Create(args());
@@ -593,9 +595,11 @@
       CreateSocketInfo(params->socket_id, socket))));
 }
 
-BluetoothSocketGetSocketsFunction::BluetoothSocketGetSocketsFunction() {}
+BluetoothSocketGetSocketsFunction::BluetoothSocketGetSocketsFunction() =
+    default;
 
-BluetoothSocketGetSocketsFunction::~BluetoothSocketGetSocketsFunction() {}
+BluetoothSocketGetSocketsFunction::~BluetoothSocketGetSocketsFunction() =
+    default;
 
 ExtensionFunction::ResponseAction BluetoothSocketGetSocketsFunction::Run() {
   std::vector<bluetooth_socket::SocketInfo> socket_infos;
diff --git a/extensions/browser/api/bluetooth_socket/bluetooth_socket_event_dispatcher.cc b/extensions/browser/api/bluetooth_socket/bluetooth_socket_event_dispatcher.cc
index fdb6637b..155e6e2 100644
--- a/extensions/browser/api/bluetooth_socket/bluetooth_socket_event_dispatcher.cc
+++ b/extensions/browser/api/bluetooth_socket/bluetooth_socket_event_dispatcher.cc
@@ -95,14 +95,14 @@
   sockets_ = manager->data_;
 }
 
-BluetoothSocketEventDispatcher::~BluetoothSocketEventDispatcher() {}
+BluetoothSocketEventDispatcher::~BluetoothSocketEventDispatcher() = default;
 
-BluetoothSocketEventDispatcher::SocketParams::SocketParams() {}
+BluetoothSocketEventDispatcher::SocketParams::SocketParams() = default;
 
 BluetoothSocketEventDispatcher::SocketParams::SocketParams(
     const SocketParams& other) = default;
 
-BluetoothSocketEventDispatcher::SocketParams::~SocketParams() {}
+BluetoothSocketEventDispatcher::SocketParams::~SocketParams() = default;
 
 void BluetoothSocketEventDispatcher::OnSocketConnect(
     const std::string& extension_id,
diff --git a/extensions/browser/api/printer_provider/printer_provider_internal_api.cc b/extensions/browser/api/printer_provider/printer_provider_internal_api.cc
index 0769f1d..4aedb8e 100644
--- a/extensions/browser/api/printer_provider/printer_provider_internal_api.cc
+++ b/extensions/browser/api/printer_provider/printer_provider_internal_api.cc
@@ -50,7 +50,7 @@
 PrinterProviderInternalAPI::PrinterProviderInternalAPI(
     content::BrowserContext* browser_context) {}
 
-PrinterProviderInternalAPI::~PrinterProviderInternalAPI() {}
+PrinterProviderInternalAPI::~PrinterProviderInternalAPI() = default;
 
 void PrinterProviderInternalAPI::AddObserver(
     PrinterProviderInternalAPIObserver* observer) {
diff --git a/extensions/shell/browser/shell_desktop_controller_aura_unittest.cc b/extensions/shell/browser/shell_desktop_controller_aura_unittest.cc
index fa32ce1..fe80c65 100644
--- a/extensions/shell/browser/shell_desktop_controller_aura_unittest.cc
+++ b/extensions/shell/browser/shell_desktop_controller_aura_unittest.cc
@@ -63,8 +63,8 @@
     screen_->display_list().AddDisplay(
         display::Display(200, gfx::Rect(1920, 1080, 800, 600)),
         display::DisplayList::Type::NOT_PRIMARY);
-    screen_override_ =
-        std::make_unique<display::test::ScopedScreenOverride>(screen_.get());
+    display::Screen::SetScreenInstance(screen_.get());
+
     ShellTestBaseAura::SetUp();
 
 #if BUILDFLAG(IS_CHROMEOS_ASH)
@@ -81,7 +81,7 @@
     chromeos::PowerManagerClient::Shutdown();
 #endif
     ShellTestBaseAura::TearDown();
-    screen_override_.reset();
+    display::Screen::SetScreenInstance(nullptr);
     screen_.reset();
   }
 
diff --git a/google_apis/BUILD.gn b/google_apis/BUILD.gn
index e644779..cb48db2 100644
--- a/google_apis/BUILD.gn
+++ b/google_apis/BUILD.gn
@@ -115,8 +115,6 @@
 template("google_apis_tmpl") {
   source_set(target_name) {
     sources = [
-      "credentials_mode.cc",
-      "credentials_mode.h",
       "gaia/core_account_id.cc",
       "gaia/core_account_id.h",
       "gaia/gaia_access_token_fetcher.cc",
@@ -168,7 +166,6 @@
     public_deps = [
       ":buildflags",
       "//build:chromeos_buildflags",
-      "//services/network/public/cpp",
     ]
 
     deps = [
@@ -179,6 +176,7 @@
       "//build:chromeos_buildflags",
       "//crypto",
       "//mojo/public/cpp/bindings:struct_traits",
+      "//services/network/public/cpp",
     ]
 
     if (_use_official_google_keys_and_generate_metrics_key_header) {
diff --git a/google_apis/common/base_requests.cc b/google_apis/common/base_requests.cc
index 741aa2fd..c37149b 100644
--- a/google_apis/common/base_requests.cc
+++ b/google_apis/common/base_requests.cc
@@ -20,7 +20,6 @@
 #include "base/values.h"
 #include "google_apis/common/request_sender.h"
 #include "google_apis/common/task_util.h"
-#include "google_apis/credentials_mode.h"
 #include "net/base/load_flags.h"
 #include "net/http/http_util.h"
 #include "services/network/public/cpp/resource_request.h"
@@ -175,7 +174,7 @@
   request->url = url;
   request->method = GetRequestType();
   request->load_flags = net::LOAD_DISABLE_CACHE;
-  request->credentials_mode = GetOmitCredentialsModeForGaiaRequests();
+  request->credentials_mode = network::mojom::CredentialsMode::kOmit;
 
   // Add request headers.
   // Note that SetHeader clears the current headers and sets it to the passed-in
diff --git a/google_apis/credentials_mode.cc b/google_apis/credentials_mode.cc
deleted file mode 100644
index 54cf67c4..0000000
--- a/google_apis/credentials_mode.cc
+++ /dev/null
@@ -1,27 +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 "google_apis/credentials_mode.h"
-
-#include "base/feature_list.h"
-#include "services/network/public/mojom/fetch_api.mojom.h"
-
-namespace google_apis {
-
-namespace {
-
-BASE_FEATURE(kGaiaCredentialsModeOmitBug_775438_Workaround,
-             "GaiaCredentialsModeOmitBug_775438_Workaround",
-             base::FEATURE_ENABLED_BY_DEFAULT);
-
-}  // namespace
-
-network::mojom::CredentialsMode GetOmitCredentialsModeForGaiaRequests() {
-  return base::FeatureList::IsEnabled(
-             kGaiaCredentialsModeOmitBug_775438_Workaround)
-             ? network::mojom::CredentialsMode::kOmitBug_775438_Workaround
-             : network::mojom::CredentialsMode::kOmit;
-}
-
-}  // namespace google_apis
diff --git a/google_apis/credentials_mode.h b/google_apis/credentials_mode.h
deleted file mode 100644
index 3aa7f9f..0000000
--- a/google_apis/credentials_mode.h
+++ /dev/null
@@ -1,19 +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 GOOGLE_APIS_CREDENTIALS_MODE_H_
-#define GOOGLE_APIS_CREDENTIALS_MODE_H_
-
-#include "services/network/public/mojom/fetch_api.mojom-forward.h"
-
-namespace google_apis {
-
-// Returns the CredentialsMode that should be used for requests to Gaia. It
-// returns kCredentialsMode::kOmit or kOmitBug_775438_Workaround depending on a
-// variations-controlled feature toggle.
-network::mojom::CredentialsMode GetOmitCredentialsModeForGaiaRequests();
-
-}  // namespace google_apis
-
-#endif  // GOOGLE_APIS_CREDENTIALS_MODE_H_
diff --git a/google_apis/gaia/gaia_auth_fetcher.cc b/google_apis/gaia/gaia_auth_fetcher.cc
index 24f3b12..51faad8 100644
--- a/google_apis/gaia/gaia_auth_fetcher.cc
+++ b/google_apis/gaia/gaia_auth_fetcher.cc
@@ -22,7 +22,6 @@
 #include "base/system/sys_info.h"
 #include "base/types/optional_util.h"
 #include "base/values.h"
-#include "google_apis/credentials_mode.h"
 #include "google_apis/gaia/gaia_auth_consumer.h"
 #include "google_apis/gaia/gaia_auth_util.h"
 #include "google_apis/gaia/gaia_constants.h"
@@ -267,9 +266,7 @@
   resource_request->url = gaia_gurl;
   original_url_ = gaia_gurl;
 
-  if (credentials_mode != network::mojom::CredentialsMode::kOmit &&
-      credentials_mode !=
-          network::mojom::CredentialsMode::kOmitBug_775438_Workaround) {
+  if (credentials_mode != network::mojom::CredentialsMode::kOmit) {
     CHECK(gaia::HasGaiaSchemeHostPort(gaia_gurl)) << gaia_gurl;
 
     url::Origin origin = GaiaUrls::GetInstance()->gaia_origin();
@@ -413,10 +410,10 @@
             }
           }
         })");
-  CreateAndStartGaiaFetcher(
-      request_body_, kFormEncodedContentType, std::string(),
-      oauth2_revoke_gurl_, google_apis::GetOmitCredentialsModeForGaiaRequests(),
-      traffic_annotation);
+  CreateAndStartGaiaFetcher(request_body_, kFormEncodedContentType,
+                            std::string(), oauth2_revoke_gurl_,
+                            network::mojom::CredentialsMode::kOmit,
+                            traffic_annotation);
 }
 
 void GaiaAuthFetcher::StartAuthCodeForOAuth2TokenExchange(
@@ -461,7 +458,7 @@
         })");
   CreateAndStartGaiaFetcher(
       request_body_, kFormEncodedContentType, std::string(), oauth2_token_gurl_,
-      google_apis::GetOmitCredentialsModeForGaiaRequests(), traffic_annotation);
+      network::mojom::CredentialsMode::kOmit, traffic_annotation);
 }
 
 void GaiaAuthFetcher::StartMergeSession(const std::string& uber_token,
@@ -552,7 +549,7 @@
         })");
   CreateAndStartGaiaFetcher(
       std::string(), std::string(), authentication_header, uberauth_token_gurl_,
-      google_apis::GetOmitCredentialsModeForGaiaRequests(), traffic_annotation);
+      network::mojom::CredentialsMode::kOmit, traffic_annotation);
 }
 
 void GaiaAuthFetcher::StartListAccounts() {
@@ -750,9 +747,9 @@
   DCHECK(reauth_url.is_valid());
 
   // Start the request.
-  CreateAndStartGaiaFetcher(
-      post_body, kJsonContentType, headers, reauth_url,
-      google_apis::GetOmitCredentialsModeForGaiaRequests(), traffic_annotation);
+  CreateAndStartGaiaFetcher(post_body, kJsonContentType, headers, reauth_url,
+                            network::mojom::CredentialsMode::kOmit,
+                            traffic_annotation);
 }
 
 void GaiaAuthFetcher::StartGetCheckConnectionInfo() {
@@ -783,10 +780,10 @@
             }
           }
         })");
-  CreateAndStartGaiaFetcher(
-      std::string(), std::string(), std::string(),
-      get_check_connection_info_url_,
-      google_apis::GetOmitCredentialsModeForGaiaRequests(), traffic_annotation);
+  CreateAndStartGaiaFetcher(std::string(), std::string(), std::string(),
+                            get_check_connection_info_url_,
+                            network::mojom::CredentialsMode::kOmit,
+                            traffic_annotation);
 }
 
 // static
diff --git a/google_apis/gaia/gaia_auth_fetcher_unittest.cc b/google_apis/gaia/gaia_auth_fetcher_unittest.cc
index 43e264e..e7d9e62 100644
--- a/google_apis/gaia/gaia_auth_fetcher_unittest.cc
+++ b/google_apis/gaia/gaia_auth_fetcher_unittest.cc
@@ -17,7 +17,6 @@
 #include "base/test/task_environment.h"
 #include "base/values.h"
 #include "build/build_config.h"
-#include "google_apis/credentials_mode.h"
 #include "google_apis/gaia/gaia_auth_consumer.h"
 #include "google_apis/gaia/gaia_constants.h"
 #include "google_apis/gaia/gaia_urls.h"
@@ -256,7 +255,7 @@
   TestGaiaAuthFetcher auth(&consumer, GetURLLoaderFactory());
   auth.StartAuthCodeForOAuth2TokenExchange("auth_code");
   ASSERT_EQ(received_requests_.size(), 1U);
-  EXPECT_EQ(google_apis::GetOmitCredentialsModeForGaiaRequests(),
+  EXPECT_EQ(network::mojom::CredentialsMode::kOmit,
             received_requests_.at(0).credentials_mode);
   std::string body = GetRequestBodyAsString(&received_requests_.at(0));
   EXPECT_EQ(std::string::npos, body.find("device_type=chrome"));
@@ -275,7 +274,7 @@
                                                        "device_ABCDE_1");
 
   ASSERT_EQ(1U, received_requests_.size());
-  EXPECT_EQ(google_apis::GetOmitCredentialsModeForGaiaRequests(),
+  EXPECT_EQ(network::mojom::CredentialsMode::kOmit,
             received_requests_.at(0).credentials_mode);
   std::string body = GetRequestBodyAsString(&received_requests_.at(0));
   EXPECT_NE(std::string::npos, body.find("device_type=chrome"));
@@ -626,8 +625,7 @@
   TestGaiaAuthFetcher auth(&consumer, GetURLLoaderFactory());
   auth.CreateAndStartGaiaFetcherForTesting(
       /*body=*/"", /*headers=*/"", GaiaUrls::GetInstance()->reauth_api_url(),
-      google_apis::GetOmitCredentialsModeForGaiaRequests(),
-      TRAFFIC_ANNOTATION_FOR_TESTS);
+      network::mojom::CredentialsMode ::kOmit, TRAFFIC_ANNOTATION_FOR_TESTS);
   auth.TestOnURLLoadCompleteInternal(net::OK, net::HTTP_OK, data);
 }
 
@@ -642,8 +640,7 @@
   TestGaiaAuthFetcher auth(&consumer, GetURLLoaderFactory());
   auth.CreateAndStartGaiaFetcherForTesting(
       /*body=*/"", /*headers=*/"", GaiaUrls::GetInstance()->reauth_api_url(),
-      google_apis::GetOmitCredentialsModeForGaiaRequests(),
-      TRAFFIC_ANNOTATION_FOR_TESTS);
+      network::mojom::CredentialsMode ::kOmit, TRAFFIC_ANNOTATION_FOR_TESTS);
   auth.TestOnURLLoadCompleteInternal(net::OK, net::HTTP_BAD_REQUEST, data);
 }
 
@@ -658,8 +655,7 @@
   TestGaiaAuthFetcher auth(&consumer, GetURLLoaderFactory());
   auth.CreateAndStartGaiaFetcherForTesting(
       /*body=*/"", /*headers=*/"", GaiaUrls::GetInstance()->reauth_api_url(),
-      google_apis::GetOmitCredentialsModeForGaiaRequests(),
-      TRAFFIC_ANNOTATION_FOR_TESTS);
+      network::mojom::CredentialsMode ::kOmit, TRAFFIC_ANNOTATION_FOR_TESTS);
   auth.TestOnURLLoadCompleteInternal(net::OK, net::HTTP_BAD_REQUEST, data);
 }
 
@@ -675,8 +671,7 @@
   TestGaiaAuthFetcher auth(&consumer, GetURLLoaderFactory());
   auth.CreateAndStartGaiaFetcherForTesting(
       /*body=*/"", /*headers=*/"", GaiaUrls::GetInstance()->reauth_api_url(),
-      google_apis::GetOmitCredentialsModeForGaiaRequests(),
-      TRAFFIC_ANNOTATION_FOR_TESTS);
+      network::mojom::CredentialsMode ::kOmit, TRAFFIC_ANNOTATION_FOR_TESTS);
   auth.TestOnURLLoadCompleteInternal(net::OK, net::HTTP_FORBIDDEN, data);
 }
 
@@ -691,8 +686,7 @@
   TestGaiaAuthFetcher auth(&consumer, GetURLLoaderFactory());
   auth.CreateAndStartGaiaFetcherForTesting(
       /*body=*/"", /*headers=*/"", GaiaUrls::GetInstance()->reauth_api_url(),
-      google_apis::GetOmitCredentialsModeForGaiaRequests(),
-      TRAFFIC_ANNOTATION_FOR_TESTS);
+      network::mojom::CredentialsMode ::kOmit, TRAFFIC_ANNOTATION_FOR_TESTS);
   auth.TestOnURLLoadCompleteInternal(net::OK, net::HTTP_FORBIDDEN, data);
 }
 
@@ -707,7 +701,6 @@
   TestGaiaAuthFetcher auth(&consumer, GetURLLoaderFactory());
   auth.CreateAndStartGaiaFetcherForTesting(
       /*body=*/"", /*headers=*/"", GaiaUrls::GetInstance()->reauth_api_url(),
-      google_apis::GetOmitCredentialsModeForGaiaRequests(),
-      TRAFFIC_ANNOTATION_FOR_TESTS);
+      network::mojom::CredentialsMode ::kOmit, TRAFFIC_ANNOTATION_FOR_TESTS);
   auth.TestOnURLLoadCompleteInternal(net::OK, net::HTTP_FORBIDDEN, data);
 }
diff --git a/google_apis/gaia/gaia_oauth_client.cc b/google_apis/gaia/gaia_oauth_client.cc
index 4f64f84..502ba94 100644
--- a/google_apis/gaia/gaia_oauth_client.cc
+++ b/google_apis/gaia/gaia_oauth_client.cc
@@ -19,7 +19,6 @@
 #include "base/strings/string_util.h"
 #include "base/task/single_thread_task_runner.h"
 #include "base/values.h"
-#include "google_apis/credentials_mode.h"
 #include "google_apis/gaia/gaia_auth_util.h"
 #include "google_apis/gaia/gaia_urls.h"
 #include "net/base/backoff_entry.h"
@@ -461,8 +460,7 @@
   auto resource_request = std::make_unique<network::ResourceRequest>();
   resource_request->url = url_;
   resource_request->method = post_body_.empty() ? "GET" : "POST";
-  resource_request->credentials_mode =
-      google_apis::GetOmitCredentialsModeForGaiaRequests();
+  resource_request->credentials_mode = network::mojom::CredentialsMode::kOmit;
   if (!authorization_header_.empty())
     resource_request->headers.SetHeader("Authorization", authorization_header_);
   if (!http_method_override_header_.empty()) {
diff --git a/google_apis/gaia/oauth2_access_token_fetcher_impl.cc b/google_apis/gaia/oauth2_access_token_fetcher_impl.cc
index c3fcb30..4a3c42a 100644
--- a/google_apis/gaia/oauth2_access_token_fetcher_impl.cc
+++ b/google_apis/gaia/oauth2_access_token_fetcher_impl.cc
@@ -16,7 +16,6 @@
 #include "base/strings/stringprintf.h"
 #include "base/time/time.h"
 #include "base/values.h"
-#include "google_apis/credentials_mode.h"
 #include "google_apis/gaia/gaia_auth_util.h"
 #include "google_apis/gaia/google_service_auth_error.h"
 #include "net/http/http_status_code.h"
@@ -96,8 +95,7 @@
     const net::NetworkTrafficAnnotationTag& traffic_annotation) {
   auto resource_request = std::make_unique<network::ResourceRequest>();
   resource_request->url = url;
-  resource_request->credentials_mode =
-      google_apis::GetOmitCredentialsModeForGaiaRequests();
+  resource_request->credentials_mode = network::mojom::CredentialsMode::kOmit;
   if (!body.empty())
     resource_request->method = "POST";
 
diff --git a/google_apis/gaia/oauth2_api_call_flow.cc b/google_apis/gaia/oauth2_api_call_flow.cc
index b9f28b94..37a1b34 100644
--- a/google_apis/gaia/oauth2_api_call_flow.cc
+++ b/google_apis/gaia/oauth2_api_call_flow.cc
@@ -10,7 +10,6 @@
 #include "base/functional/bind.h"
 #include "base/strings/escape.h"
 #include "base/strings/stringprintf.h"
-#include "google_apis/credentials_mode.h"
 #include "google_apis/gaia/gaia_auth_util.h"
 #include "google_apis/gaia/gaia_urls.h"
 #include "net/base/load_flags.h"
@@ -99,8 +98,7 @@
   auto request = std::make_unique<network::ResourceRequest>();
   request->url = CreateApiCallUrl();
   request->method = request_type;
-  request->credentials_mode =
-      google_apis::GetOmitCredentialsModeForGaiaRequests();
+  request->credentials_mode = network::mojom::CredentialsMode::kOmit;
   request->headers = CreateApiCallHeaders();
   request->headers.SetHeader("Authorization",
                              MakeAuthorizationValue(access_token));
diff --git a/google_apis/gcm/engine/checkin_request.cc b/google_apis/gcm/engine/checkin_request.cc
index ff40cfa..544bdca 100644
--- a/google_apis/gcm/engine/checkin_request.cc
+++ b/google_apis/gcm/engine/checkin_request.cc
@@ -9,7 +9,6 @@
 #include "base/metrics/histogram_functions.h"
 #include "base/task/sequenced_task_runner.h"
 #include "build/chromeos_buildflags.h"
-#include "google_apis/credentials_mode.h"
 #include "google_apis/gcm/monitoring/gcm_stats_recorder.h"
 #include "google_apis/gcm/protocol/checkin.pb.h"
 #include "net/base/load_flags.h"
@@ -194,8 +193,7 @@
   auto resource_request = std::make_unique<network::ResourceRequest>();
   resource_request->url = checkin_url_;
   resource_request->method = "POST";
-  resource_request->credentials_mode =
-      google_apis::GetOmitCredentialsModeForGaiaRequests();
+  resource_request->credentials_mode = network::mojom::CredentialsMode::kOmit;
 
   DVLOG(1) << "Performing check-in request with android id: "
            << request_info_.android_id
diff --git a/google_apis/gcm/engine/registration_request.cc b/google_apis/gcm/engine/registration_request.cc
index 36adf30..50d754d9 100644
--- a/google_apis/gcm/engine/registration_request.cc
+++ b/google_apis/gcm/engine/registration_request.cc
@@ -14,7 +14,6 @@
 #include "base/strings/string_number_conversions.h"
 #include "base/task/sequenced_task_runner.h"
 #include "base/values.h"
-#include "google_apis/credentials_mode.h"
 #include "google_apis/gcm/base/gcm_util.h"
 #include "google_apis/gcm/monitoring/gcm_stats_recorder.h"
 #include "net/base/load_flags.h"
@@ -177,8 +176,7 @@
   auto request = std::make_unique<network::ResourceRequest>();
   request->url = registration_url_;
   request->method = "POST";
-  request->credentials_mode =
-      google_apis::GetOmitCredentialsModeForGaiaRequests();
+  request->credentials_mode = network::mojom::CredentialsMode::kOmit;
   BuildRequestHeaders(&request->headers);
 
   std::string body;
diff --git a/google_apis/gcm/engine/registration_request_unittest.cc b/google_apis/gcm/engine/registration_request_unittest.cc
index ec5ce5b..7dc1cb8 100644
--- a/google_apis/gcm/engine/registration_request_unittest.cc
+++ b/google_apis/gcm/engine/registration_request_unittest.cc
@@ -15,7 +15,6 @@
 #include "base/strings/string_number_conversions.h"
 #include "base/strings/string_tokenizer.h"
 #include "base/task/single_thread_task_runner.h"
-#include "google_apis/credentials_mode.h"
 #include "google_apis/gcm/engine/gcm_registration_request_handler.h"
 #include "google_apis/gcm/engine/gcm_request_test_base.h"
 #include "google_apis/gcm/engine/instance_id_get_token_request_handler.h"
@@ -460,7 +459,7 @@
   const network::ResourceRequest* pending_request;
   ASSERT_TRUE(
       test_url_loader_factory()->IsPending(kRegistrationURL, &pending_request));
-  EXPECT_EQ(google_apis::GetOmitCredentialsModeForGaiaRequests(),
+  EXPECT_EQ(network::mojom::CredentialsMode::kOmit,
             pending_request->credentials_mode);
 
   // Verify that authorization header was put together properly.
diff --git a/google_apis/gcm/engine/unregistration_request.cc b/google_apis/gcm/engine/unregistration_request.cc
index 195f5a97..3782675 100644
--- a/google_apis/gcm/engine/unregistration_request.cc
+++ b/google_apis/gcm/engine/unregistration_request.cc
@@ -13,7 +13,6 @@
 #include "base/strings/string_piece.h"
 #include "base/task/sequenced_task_runner.h"
 #include "base/values.h"
-#include "google_apis/credentials_mode.h"
 #include "google_apis/gcm/base/gcm_util.h"
 #include "google_apis/gcm/monitoring/gcm_stats_recorder.h"
 #include "net/base/load_flags.h"
@@ -164,8 +163,7 @@
   auto request = std::make_unique<network::ResourceRequest>();
   request->url = registration_url_;
   request->method = "POST";
-  request->credentials_mode =
-      google_apis::GetOmitCredentialsModeForGaiaRequests();
+  request->credentials_mode = network::mojom::CredentialsMode::kOmit;
   BuildRequestHeaders(&request->headers);
 
   std::string body;
diff --git a/google_apis/gcm/engine/unregistration_request_unittest.cc b/google_apis/gcm/engine/unregistration_request_unittest.cc
index d4637128..760ea34 100644
--- a/google_apis/gcm/engine/unregistration_request_unittest.cc
+++ b/google_apis/gcm/engine/unregistration_request_unittest.cc
@@ -14,7 +14,6 @@
 #include "base/strings/string_number_conversions.h"
 #include "base/strings/string_tokenizer.h"
 #include "base/task/single_thread_task_runner.h"
-#include "google_apis/credentials_mode.h"
 #include "google_apis/gcm/engine/gcm_request_test_base.h"
 #include "google_apis/gcm/engine/gcm_unregistration_request_handler.h"
 #include "google_apis/gcm/engine/instance_id_delete_token_request_handler.h"
@@ -119,7 +118,7 @@
   const network::ResourceRequest* pending_request;
   ASSERT_TRUE(
       test_url_loader_factory()->IsPending(kRegistrationURL, &pending_request));
-  EXPECT_EQ(google_apis::GetOmitCredentialsModeForGaiaRequests(),
+  EXPECT_EQ(network::mojom::CredentialsMode::kOmit,
             pending_request->credentials_mode);
 
   // Verify that authorization header was put together properly.
diff --git a/infra/config/generated/builders/ci/ios-blink-dbg-fyi/properties.json b/infra/config/generated/builders/ci/ios-blink-dbg-fyi/properties.json
new file mode 100644
index 0000000..99728d81d
--- /dev/null
+++ b/infra/config/generated/builders/ci/ios-blink-dbg-fyi/properties.json
@@ -0,0 +1,64 @@
+{
+  "$build/chromium_tests_builder_config": {
+    "builder_config": {
+      "builder_db": {
+        "entries": [
+          {
+            "builder_id": {
+              "bucket": "ci",
+              "builder": "ios-blink-dbg-fyi",
+              "project": "chromium"
+            },
+            "builder_spec": {
+              "build_gs_bucket": "chromium-fyi-archive",
+              "builder_group": "chromium.fyi",
+              "execution_mode": "COMPILE_AND_TEST",
+              "legacy_chromium_config": {
+                "apply_configs": [
+                  "mb",
+                  "mac_toolchain"
+                ],
+                "build_config": "Debug",
+                "config": "chromium",
+                "target_bits": 64,
+                "target_platform": "ios"
+              },
+              "legacy_gclient_config": {
+                "config": "ios"
+              }
+            }
+          }
+        ]
+      },
+      "builder_ids": [
+        {
+          "bucket": "ci",
+          "builder": "ios-blink-dbg-fyi",
+          "project": "chromium"
+        }
+      ],
+      "mirroring_builder_group_and_names": [
+        {
+          "builder": "ios-blink-dbg-fyi",
+          "group": "tryserver.chromium.mac"
+        }
+      ]
+    }
+  },
+  "$build/reclient": {
+    "instance": "rbe-chromium-trusted",
+    "jobs": 250,
+    "metrics_project": "chromium-reclient-metrics",
+    "scandeps_server": true
+  },
+  "$recipe_engine/resultdb/test_presentation": {
+    "column_keys": [],
+    "grouping_keys": [
+      "status",
+      "v.test_suite"
+    ]
+  },
+  "builder_group": "chromium.fyi",
+  "recipe": "chromium",
+  "xcode_build_version": "14c18"
+}
\ No newline at end of file
diff --git a/infra/config/generated/builders/try/ios-blink-dbg-fyi/properties.json b/infra/config/generated/builders/try/ios-blink-dbg-fyi/properties.json
new file mode 100644
index 0000000..b7e5f60
--- /dev/null
+++ b/infra/config/generated/builders/try/ios-blink-dbg-fyi/properties.json
@@ -0,0 +1,56 @@
+{
+  "$build/chromium_tests_builder_config": {
+    "builder_config": {
+      "builder_db": {
+        "entries": [
+          {
+            "builder_id": {
+              "bucket": "ci",
+              "builder": "ios-blink-dbg-fyi",
+              "project": "chromium"
+            },
+            "builder_spec": {
+              "build_gs_bucket": "chromium-fyi-archive",
+              "builder_group": "chromium.fyi",
+              "execution_mode": "COMPILE_AND_TEST",
+              "legacy_chromium_config": {
+                "apply_configs": [
+                  "mb",
+                  "mac_toolchain"
+                ],
+                "build_config": "Debug",
+                "config": "chromium",
+                "target_bits": 64,
+                "target_platform": "ios"
+              },
+              "legacy_gclient_config": {
+                "config": "ios"
+              }
+            }
+          }
+        ]
+      },
+      "builder_ids": [
+        {
+          "bucket": "ci",
+          "builder": "ios-blink-dbg-fyi",
+          "project": "chromium"
+        }
+      ]
+    }
+  },
+  "$build/reclient": {
+    "instance": "rbe-chromium-untrusted",
+    "metrics_project": "chromium-reclient-metrics"
+  },
+  "$recipe_engine/resultdb/test_presentation": {
+    "column_keys": [],
+    "grouping_keys": [
+      "status",
+      "v.test_suite"
+    ]
+  },
+  "builder_group": "tryserver.chromium.mac",
+  "recipe": "chromium_trybot",
+  "xcode_build_version": "14c18"
+}
\ No newline at end of file
diff --git a/infra/config/generated/luci/commit-queue.cfg b/infra/config/generated/luci/commit-queue.cfg
index bbdb0d43..5de8d8a 100644
--- a/infra/config/generated/luci/commit-queue.cfg
+++ b/infra/config/generated/luci/commit-queue.cfg
@@ -1992,6 +1992,10 @@
         includable_only: true
       }
       builders {
+        name: "chromium/try/ios-blink-dbg-fyi"
+        includable_only: true
+      }
+      builders {
         name: "chromium/try/ios-catalyst"
         includable_only: true
       }
diff --git a/infra/config/generated/luci/cr-buildbucket.cfg b/infra/config/generated/luci/cr-buildbucket.cfg
index 9d5b374..04ac6a0 100644
--- a/infra/config/generated/luci/cr-buildbucket.cfg
+++ b/infra/config/generated/luci/cr-buildbucket.cfg
@@ -31622,6 +31622,91 @@
       }
     }
     builders {
+      name: "ios-blink-dbg-fyi"
+      swarming_host: "chromium-swarm.appspot.com"
+      dimensions: "builder:ios-blink-dbg-fyi"
+      dimensions: "cpu:x86-64"
+      dimensions: "pool:luci.chromium.ci"
+      exe {
+        cipd_package: "infra/chromium/bootstrapper/${platform}"
+        cipd_version: "latest"
+        cmd: "bootstrapper"
+      }
+      properties:
+        '{'
+        '  "$bootstrap/exe": {'
+        '    "exe": {'
+        '      "cipd_package": "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build",'
+        '      "cipd_version": "refs/heads/main",'
+        '      "cmd": ['
+        '        "luciexe"'
+        '      ]'
+        '    }'
+        '  },'
+        '  "$bootstrap/properties": {'
+        '    "properties_file": "infra/config/generated/builders/ci/ios-blink-dbg-fyi/properties.json",'
+        '    "top_level_project": {'
+        '      "ref": "refs/heads/main",'
+        '      "repo": {'
+        '        "host": "chromium.googlesource.com",'
+        '        "project": "chromium/src"'
+        '      }'
+        '    }'
+        '  },'
+        '  "builder_group": "chromium.fyi",'
+        '  "led_builder_is_bootstrapped": true,'
+        '  "recipe": "chromium"'
+        '}'
+      priority: 35
+      execution_timeout_secs: 36000
+      caches {
+        name: "xcode_ios_14c18"
+        path: "xcode_ios_14c18.app"
+      }
+      build_numbers: YES
+      service_account: "chromium-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
+      experiments {
+        key: "chromium_swarming.expose_merge_script_failures"
+        value: 100
+      }
+      experiments {
+        key: "luci.recipes.use_python3"
+        value: 100
+      }
+      resultdb {
+        enable: true
+        bq_exports {
+          project: "chrome-luci-data"
+          dataset: "chromium"
+          table: "ci_test_results"
+          test_results {}
+        }
+        bq_exports {
+          project: "chrome-luci-data"
+          dataset: "chromium"
+          table: "gpu_ci_test_results"
+          test_results {
+            predicate {
+              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+            }
+          }
+        }
+        bq_exports {
+          project: "chrome-luci-data"
+          dataset: "chromium"
+          table: "blink_web_tests_ci_test_results"
+          test_results {
+            predicate {
+              test_id_regexp: "(ninja://[^/]*blink_web_tests/.+)|(ninja://[^/]*blink_wpt_tests/.+)"
+            }
+          }
+        }
+        history_options {
+          use_invocation_timestamp: true
+        }
+      }
+    }
+    builders {
       name: "ios-catalyst"
       swarming_host: "chromium-swarm.appspot.com"
       dimensions: "builder:ios-catalyst"
@@ -68834,6 +68919,102 @@
       }
     }
     builders {
+      name: "ios-blink-dbg-fyi"
+      swarming_host: "chromium-swarm.appspot.com"
+      dimensions: "builder:ios-blink-dbg-fyi"
+      dimensions: "cpu:x86-64"
+      dimensions: "os:Mac-12"
+      dimensions: "pool:luci.chromium.try"
+      exe {
+        cipd_package: "infra/chromium/bootstrapper/${platform}"
+        cipd_version: "latest"
+        cmd: "bootstrapper"
+      }
+      properties:
+        '{'
+        '  "$bootstrap/exe": {'
+        '    "exe": {'
+        '      "cipd_package": "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build",'
+        '      "cipd_version": "refs/heads/main",'
+        '      "cmd": ['
+        '        "luciexe"'
+        '      ]'
+        '    }'
+        '  },'
+        '  "$bootstrap/properties": {'
+        '    "properties_file": "infra/config/generated/builders/try/ios-blink-dbg-fyi/properties.json",'
+        '    "top_level_project": {'
+        '      "ref": "refs/heads/main",'
+        '      "repo": {'
+        '        "host": "chromium.googlesource.com",'
+        '        "project": "chromium/src"'
+        '      }'
+        '    }'
+        '  },'
+        '  "builder_group": "tryserver.chromium.mac",'
+        '  "led_builder_is_bootstrapped": true,'
+        '  "recipe": "chromium_trybot"'
+        '}'
+      execution_timeout_secs: 14400
+      expiration_secs: 7200
+      grace_period {
+        seconds: 120
+      }
+      caches {
+        name: "win_toolchain"
+        path: "win_toolchain"
+      }
+      caches {
+        name: "xcode_ios_14c18"
+        path: "xcode_ios_14c18.app"
+      }
+      build_numbers: YES
+      service_account: "chromium-try-builder@chops-service-accounts.iam.gserviceaccount.com"
+      task_template_canary_percentage {
+        value: 5
+      }
+      experiments {
+        key: "chromium_swarming.expose_merge_script_failures"
+        value: 100
+      }
+      experiments {
+        key: "luci.recipes.use_python3"
+        value: 100
+      }
+      resultdb {
+        enable: true
+        bq_exports {
+          project: "chrome-luci-data"
+          dataset: "chromium"
+          table: "try_test_results"
+          test_results {}
+        }
+        bq_exports {
+          project: "chrome-luci-data"
+          dataset: "chromium"
+          table: "gpu_try_test_results"
+          test_results {
+            predicate {
+              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+            }
+          }
+        }
+        bq_exports {
+          project: "chrome-luci-data"
+          dataset: "chromium"
+          table: "blink_web_tests_try_test_results"
+          test_results {
+            predicate {
+              test_id_regexp: "(ninja://[^/]*blink_web_tests/.+)|(ninja://[^/]*blink_wpt_tests/.+)"
+            }
+          }
+        }
+        history_options {
+          use_invocation_timestamp: true
+        }
+      }
+    }
+    builders {
       name: "ios-catalyst"
       swarming_host: "chromium-swarm.appspot.com"
       dimensions: "builder:ios-catalyst"
diff --git a/infra/config/generated/luci/luci-milo.cfg b/infra/config/generated/luci/luci-milo.cfg
index b6a1fbd..64d10d9 100644
--- a/infra/config/generated/luci/luci-milo.cfg
+++ b/infra/config/generated/luci/luci-milo.cfg
@@ -8969,6 +8969,11 @@
     short_name: "dbg"
   }
   builders {
+    name: "buildbucket/luci.chromium.ci/ios-blink-dbg-fyi"
+    category: "iOS"
+    short_name: "ios-blk"
+  }
+  builders {
     name: "buildbucket/luci.chromium.ci/ios-simulator-multi-window"
     category: "iOS"
     short_name: "mwd"
@@ -17406,6 +17411,9 @@
     name: "buildbucket/luci.chromium.try/ios-asan"
   }
   builders {
+    name: "buildbucket/luci.chromium.try/ios-blink-dbg-fyi"
+  }
+  builders {
     name: "buildbucket/luci.chromium.try/ios-catalyst"
   }
   builders {
@@ -18864,6 +18872,9 @@
     name: "buildbucket/luci.chromium.try/ios-asan"
   }
   builders {
+    name: "buildbucket/luci.chromium.try/ios-blink-dbg-fyi"
+  }
+  builders {
     name: "buildbucket/luci.chromium.try/ios-catalyst"
   }
   builders {
diff --git a/infra/config/generated/luci/luci-scheduler.cfg b/infra/config/generated/luci/luci-scheduler.cfg
index 2b67f9d2..c9a670cd 100644
--- a/infra/config/generated/luci/luci-scheduler.cfg
+++ b/infra/config/generated/luci/luci-scheduler.cfg
@@ -4236,6 +4236,15 @@
   }
 }
 job {
+  id: "ios-blink-dbg-fyi"
+  realm: "ci"
+  buildbucket {
+    server: "cr-buildbucket.appspot.com"
+    bucket: "ci"
+    builder: "ios-blink-dbg-fyi"
+  }
+}
+job {
   id: "ios-catalyst"
   realm: "ci"
   buildbucket {
@@ -6405,6 +6414,7 @@
   triggers: "fuchsia-x64-rel"
   triggers: "ios-angle-builder"
   triggers: "ios-asan"
+  triggers: "ios-blink-dbg-fyi"
   triggers: "ios-catalyst"
   triggers: "ios-device"
   triggers: "ios-fieldtrial-rel"
diff --git a/infra/config/subprojects/chromium/ci/chromium.fyi.star b/infra/config/subprojects/chromium/ci/chromium.fyi.star
index dcbf70a..e9163f5 100644
--- a/infra/config/subprojects/chromium/ci/chromium.fyi.star
+++ b/infra/config/subprojects/chromium/ci/chromium.fyi.star
@@ -2089,6 +2089,30 @@
 )
 
 fyi_ios_builder(
+    name = "ios-blink-dbg-fyi",
+    builder_spec = builder_config.builder_spec(
+        gclient_config = builder_config.gclient_config(
+            config = "ios",
+        ),
+        chromium_config = builder_config.chromium_config(
+            config = "chromium",
+            apply_configs = [
+                "mb",
+                "mac_toolchain",
+            ],
+            build_config = builder_config.build_config.DEBUG,
+            target_bits = 64,
+            target_platform = builder_config.target_platform.IOS,
+        ),
+        build_gs_bucket = "chromium-fyi-archive",
+    ),
+    console_view_entry = consoles.console_view_entry(
+        category = "iOS",
+        short_name = "ios-blk",
+    ),
+)
+
+fyi_ios_builder(
     name = "ios-simulator-cronet",
     branch_selector = branches.selector.IOS_BRANCHES,
     builder_spec = builder_config.builder_spec(
diff --git a/infra/config/subprojects/chromium/try/tryserver.chromium.mac.star b/infra/config/subprojects/chromium/try/tryserver.chromium.mac.star
index 17947a9d..40c9c1a 100644
--- a/infra/config/subprojects/chromium/try/tryserver.chromium.mac.star
+++ b/infra/config/subprojects/chromium/try/tryserver.chromium.mac.star
@@ -347,6 +347,13 @@
 )
 
 ios_builder(
+    name = "ios-blink-dbg-fyi",
+    mirrors = [
+        "ci/ios-blink-dbg-fyi",
+    ],
+)
+
+ios_builder(
     name = "ios-catalyst",
     mirrors = [
         "ci/ios-catalyst",
diff --git a/ios/chrome/browser/autofill/autofill_controller_unittest.mm b/ios/chrome/browser/autofill/autofill_controller_unittest.mm
index 91cfb83..6cca443 100644
--- a/ios/chrome/browser/autofill/autofill_controller_unittest.mm
+++ b/ios/chrome/browser/autofill/autofill_controller_unittest.mm
@@ -247,9 +247,8 @@
     TestAutofillManagerWaiter& waiter() { return waiter_; }
 
    private:
-    TestAutofillManagerWaiter waiter_{
-        *this,
-        {&AutofillManager::Observer::OnAfterFormsSeen}};
+    TestAutofillManagerWaiter waiter_{*this,
+                                      {AutofillManagerEvent::kFormsSeen}};
   };
 
   void SetUp() override;
diff --git a/ios/chrome/browser/autofill/form_structure_browsertest.mm b/ios/chrome/browser/autofill/form_structure_browsertest.mm
index a58252ca..1f58c73 100644
--- a/ios/chrome/browser/autofill/form_structure_browsertest.mm
+++ b/ios/chrome/browser/autofill/form_structure_browsertest.mm
@@ -144,9 +144,8 @@
     TestAutofillManagerWaiter& waiter() { return waiter_; }
 
    private:
-    TestAutofillManagerWaiter waiter_{
-        *this,
-        {&AutofillManager::Observer::OnAfterFormsSeen}};
+    TestAutofillManagerWaiter waiter_{*this,
+                                      {AutofillManagerEvent::kFormsSeen}};
   };
 
   FormStructureBrowserTest();
diff --git a/ios/chrome/browser/discover_feed/BUILD.gn b/ios/chrome/browser/discover_feed/BUILD.gn
index fe8785ab..07452ec 100644
--- a/ios/chrome/browser/discover_feed/BUILD.gn
+++ b/ios/chrome/browser/discover_feed/BUILD.gn
@@ -28,6 +28,7 @@
   ]
   public_deps = [
     ":constants",
+    ":discover_feed_refresher",
     "//base",
   ]
   frameworks = [ "UIKit.framework" ]
@@ -63,3 +64,7 @@
     "feed_constants.mm",
   ]
 }
+
+source_set("discover_feed_refresher") {
+  sources = [ "discover_feed_refresher.h" ]
+}
diff --git a/ios/chrome/browser/discover_feed/discover_feed_refresher.h b/ios/chrome/browser/discover_feed/discover_feed_refresher.h
new file mode 100644
index 0000000..1817c4b5e
--- /dev/null
+++ b/ios/chrome/browser/discover_feed/discover_feed_refresher.h
@@ -0,0 +1,25 @@
+// 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_CHROME_BROWSER_DISCOVER_FEED_DISCOVER_FEED_REFRESHER_H_
+#define IOS_CHROME_BROWSER_DISCOVER_FEED_DISCOVER_FEED_REFRESHER_H_
+
+// An interface to refresh the Discover Feed.
+class DiscoverFeedRefresher {
+ public:
+  // Refreshes the Discover Feed.
+  // DEPRECATED: use `RefreshFeed(bool feed_visible)`.
+  virtual void RefreshFeed() = 0;
+
+  // Refreshes the Discover Feed, indicating whether the feed is visible at the
+  // time of the request.
+  virtual void RefreshFeed(bool feed_visible) {}
+
+  // Refreshes the Discover Feed if needed. The implementer decides if a refresh
+  // is needed or not. This should only be called when the feed is visible to
+  // the user.
+  virtual void RefreshFeedIfNeeded() = 0;
+};
+
+#endif  // IOS_CHROME_BROWSER_DISCOVER_FEED_DISCOVER_FEED_REFRESHER_H_
diff --git a/ios/chrome/browser/discover_feed/discover_feed_service.h b/ios/chrome/browser/discover_feed/discover_feed_service.h
index 76106032..7ff41b2 100644
--- a/ios/chrome/browser/discover_feed/discover_feed_service.h
+++ b/ios/chrome/browser/discover_feed/discover_feed_service.h
@@ -9,6 +9,7 @@
 
 #include "components/keyed_service/core/keyed_service.h"
 #include "ios/chrome/browser/discover_feed/discover_feed_observer.h"
+#include "ios/chrome/browser/discover_feed/discover_feed_refresher.h"
 #include "ios/chrome/browser/discover_feed/discover_feed_view_controller_configuration.h"
 #include "ios/chrome/browser/discover_feed/feed_constants.h"
 #include "ios/chrome/browser/discover_feed/feed_model_configuration.h"
@@ -17,7 +18,7 @@
 
 // A browser-context keyed service that is used to keep the Discover Feed data
 // up to date.
-class DiscoverFeedService : public KeyedService {
+class DiscoverFeedService : public DiscoverFeedRefresher, public KeyedService {
  public:
   DiscoverFeedService();
   ~DiscoverFeedService() override;
@@ -61,14 +62,6 @@
   // Updates the feed's theme to match the user's theme (light/dark).
   virtual void UpdateTheme() = 0;
 
-  // Refreshes the Discover Feed if needed. The provider decides if a refresh is
-  // needed or not.
-  virtual void RefreshFeedIfNeeded() = 0;
-
-  // Refreshes the Discover Feed. Once the Feed model is refreshed it will
-  // update all ViewControllers returned by NewFeedViewController.
-  virtual void RefreshFeed() = 0;
-
   // Performs a background refresh for the feed. `completion` is called
   // after success, failure, or timeout. The BOOL argument indicates whether the
   // refresh was successful or a failure.
diff --git a/ios/chrome/browser/prefs/pref_names.cc b/ios/chrome/browser/prefs/pref_names.cc
index d227142e..a6887021 100644
--- a/ios/chrome/browser/prefs/pref_names.cc
+++ b/ios/chrome/browser/prefs/pref_names.cc
@@ -96,10 +96,16 @@
 const char kIosCredentialProviderPromoStopPromo[] =
     "ios.credential_provider_promo.stop_promo";
 
-// The time when the DiscoverFeed was last refreshed.
+// The time when the DiscoverFeed was last refreshed while the feed was visible
+// to the user.
 const char kIosDiscoverFeedLastRefreshTime[] =
     "ios.discover_feed.last_refresh_time";
 
+// The time when the DiscoverFeed was last refreshed while the feed was not
+// visible to the user.
+const char kIosDiscoverFeedLastUnseenRefreshTime[] =
+    "ios.discover_feed.last_unseen_refresh_time";
+
 // The user's account info from before a device restore.
 const char kIosPreRestoreAccountInfo[] = "ios.pre_restore_account_info";
 
diff --git a/ios/chrome/browser/prefs/pref_names.h b/ios/chrome/browser/prefs/pref_names.h
index f91dd9c..af281506 100644
--- a/ios/chrome/browser/prefs/pref_names.h
+++ b/ios/chrome/browser/prefs/pref_names.h
@@ -33,6 +33,7 @@
 extern const char kIosShareChromeCount[];
 extern const char kIosShareChromeLastShare[];
 extern const char kIosDiscoverFeedLastRefreshTime[];
+extern const char kIosDiscoverFeedLastUnseenRefreshTime[];
 extern const char kIosPreRestoreAccountInfo[];
 extern const char kIosPromosManagerActivePromos[];
 extern const char kIosPromosManagerImpressions[];
diff --git a/ios/chrome/browser/providers/signin/chromium_trusted_vault.mm b/ios/chrome/browser/providers/signin/chromium_trusted_vault.mm
index 5981d45..1eeb1ea 100644
--- a/ios/chrome/browser/providers/signin/chromium_trusted_vault.mm
+++ b/ios/chrome/browser/providers/signin/chromium_trusted_vault.mm
@@ -24,6 +24,8 @@
   // TrustedVaultClientBackend implementation.
   void AddObserver(Observer* observer) final;
   void RemoveObserver(Observer* observer) final;
+  void SetDeviceRegistrationPublicKeyVerifierForUMA(
+      VerifierCallback verifier) final;
   void FetchKeys(id<SystemIdentity> identity,
                  KeyFetchedCallback callback) final;
   void MarkLocalKeysAsStale(id<SystemIdentity> identity,
@@ -38,6 +40,8 @@
                                  UIViewController* presenting_view_controller,
                                  CompletionBlock callback) final;
   void CancelDialog(BOOL animated, ProceduralBlock callback) final;
+  void ClearLocalData(id<SystemIdentity> identity,
+                      base::OnceCallback<void(bool)> callback) final;
 };
 
 void ChromiumTrustedVaultClientBackend::AddObserver(Observer* observer) {
@@ -48,6 +52,11 @@
   // Do nothing.
 }
 
+void ChromiumTrustedVaultClientBackend::
+    SetDeviceRegistrationPublicKeyVerifierForUMA(VerifierCallback verifier) {
+  // Do nothing.
+}
+
 void ChromiumTrustedVaultClientBackend::FetchKeys(id<SystemIdentity> identity,
                                                   KeyFetchedCallback callback) {
   NOTREACHED();
@@ -84,6 +93,12 @@
   NOTREACHED();
 }
 
+void ChromiumTrustedVaultClientBackend::ClearLocalData(
+    id<SystemIdentity> identity,
+    base::OnceCallback<void(bool)> callback) {
+  NOTREACHED();
+}
+
 }  // anonymous namespace
 
 std::unique_ptr<TrustedVaultClientBackend> CreateTrustedVaultClientBackend(
diff --git a/ios/chrome/browser/signin/gaia_auth_fetcher_ios.mm b/ios/chrome/browser/signin/gaia_auth_fetcher_ios.mm
index 49ffe7f..2c42335d 100644
--- a/ios/chrome/browser/signin/gaia_auth_fetcher_ios.mm
+++ b/ios/chrome/browser/signin/gaia_auth_fetcher_ios.mm
@@ -37,10 +37,7 @@
   DCHECK(!HasPendingFetch()) << "Tried to fetch two things at once!";
 
   bool cookies_required =
-      credentials_mode != network::mojom::CredentialsMode::kOmit &&
-      credentials_mode !=
-          network::mojom::CredentialsMode::kOmitBug_775438_Workaround;
-
+      credentials_mode != network::mojom::CredentialsMode::kOmit;
   if (!cookies_required) {
     GaiaAuthFetcher::CreateAndStartGaiaFetcher(body, body_content_type, headers,
                                                gaia_gurl, credentials_mode,
diff --git a/ios/chrome/browser/signin/trusted_vault_client_backend.h b/ios/chrome/browser/signin/trusted_vault_client_backend.h
index d2aaaac..32eedb3 100644
--- a/ios/chrome/browser/signin/trusted_vault_client_backend.h
+++ b/ios/chrome/browser/signin/trusted_vault_client_backend.h
@@ -7,6 +7,7 @@
 
 #include <UIKit/UIKit.h>
 
+#include <string>
 #include <vector>
 
 #include "base/functional/callback_forward.h"
@@ -23,6 +24,9 @@
   using SharedKey = std::vector<uint8_t>;
   using SharedKeyList = std::vector<SharedKey>;
 
+  // A public key.
+  using PublicKey = std::vector<uint8_t>;
+
   // Represents the TrustedVaultClientBackend observers.
   using Observer = syncer::TrustedVaultClient::Observer;
 
@@ -30,6 +34,12 @@
   using KeyFetchedCallback = base::OnceCallback<void(const SharedKeyList&)>;
   using CompletionBlock = void (^)(BOOL success, NSError* error);
 
+  // Callback used to verify local device registration and log the result to
+  // UMA metrics. The first argument is the gaia ID and the second is the local
+  // client's public key.
+  using VerifierCallback =
+      base::OnceCallback<void(const std::string&, const PublicKey&)>;
+
   TrustedVaultClientBackend();
 
   TrustedVaultClientBackend(const TrustedVaultClientBackend&) = delete;
@@ -42,6 +52,12 @@
   virtual void AddObserver(Observer* observer) = 0;
   virtual void RemoveObserver(Observer* observer) = 0;
 
+  // Registers a delegate-like callback that implements device registration
+  // verification.
+  // TODO(crbug.com/1416626): Make abstract once all implementations land.
+  virtual void SetDeviceRegistrationPublicKeyVerifierForUMA(
+      VerifierCallback verifier);
+
   // Asynchronously fetches the shared keys for `identity` and invokes
   // `callback` with the fetched keys.
   virtual void FetchKeys(id<SystemIdentity> identity,
@@ -85,6 +101,12 @@
   // will not be called. If no reauthentication dialog is not present,
   // `callback` is called synchronously.
   virtual void CancelDialog(BOOL animated, ProceduralBlock callback) = 0;
+
+  // Clears local data belonging to `identity`, such as shared keys. This
+  // excludes the physical client's key pair, which remains unchanged.
+  // TODO(crbug.com/1416626): Make abstract once all implementations land.
+  virtual void ClearLocalData(id<SystemIdentity> identity,
+                              base::OnceCallback<void(bool)> callback);
 };
 
 #endif  // IOS_CHROME_BROWSER_SIGNIN_TRUSTED_VAULT_CLIENT_BACKEND_H_
diff --git a/ios/chrome/browser/signin/trusted_vault_client_backend.mm b/ios/chrome/browser/signin/trusted_vault_client_backend.mm
index 9964c875..6b744cb 100644
--- a/ios/chrome/browser/signin/trusted_vault_client_backend.mm
+++ b/ios/chrome/browser/signin/trusted_vault_client_backend.mm
@@ -4,6 +4,9 @@
 
 #import "ios/chrome/browser/signin/trusted_vault_client_backend.h"
 
+#import "base/functional/callback.h"
+#import "base/notreached.h"
+
 #if !defined(__has_feature) || !__has_feature(objc_arc)
 #error "This file requires ARC support."
 #endif
@@ -11,3 +14,14 @@
 TrustedVaultClientBackend::TrustedVaultClientBackend() = default;
 
 TrustedVaultClientBackend::~TrustedVaultClientBackend() = default;
+
+void TrustedVaultClientBackend::SetDeviceRegistrationPublicKeyVerifierForUMA(
+    VerifierCallback verifier) {
+  NOTREACHED();
+}
+
+void TrustedVaultClientBackend::ClearLocalData(
+    id<SystemIdentity> identity,
+    base::OnceCallback<void(bool)> callback) {
+  NOTREACHED();
+}
diff --git a/ios/chrome/browser/ui/content_suggestions/content_suggestions_collection_utils.h b/ios/chrome/browser/ui/content_suggestions/content_suggestions_collection_utils.h
index e98ee66e..9143cd6 100644
--- a/ios/chrome/browser/ui/content_suggestions/content_suggestions_collection_utils.h
+++ b/ios/chrome/browser/ui/content_suggestions/content_suggestions_collection_utils.h
@@ -39,6 +39,8 @@
 // Returns the bottom padding for the header. This represents the spacing
 // between the fake omnibox and the content suggestions tiles.
 CGFloat HeaderBottomPadding();
+// Creates a magnifying glass to be added to the fake omnibox.
+UIImageView* CreateMagnifyingGlassView();
 // Configure the `search_hint_label` for the fake omnibox.  `hintLabelContainer`
 // is added to the `search_tab_target` with autolayout and `search_hint_label`
 // is added to `hintLabelContainer` with autoresizing.  This is done due to the
diff --git a/ios/chrome/browser/ui/content_suggestions/content_suggestions_collection_utils.mm b/ios/chrome/browser/ui/content_suggestions/content_suggestions_collection_utils.mm
index ad61a44..f0d48297 100644
--- a/ios/chrome/browser/ui/content_suggestions/content_suggestions_collection_utils.mm
+++ b/ios/chrome/browser/ui/content_suggestions/content_suggestions_collection_utils.mm
@@ -157,6 +157,28 @@
              : kNTPSearchFieldBottomPadding;
 }
 
+UIImageView* CreateMagnifyingGlassView() {
+  UIImageView* image_view = [[UIImageView alloc] init];
+  image_view.translatesAutoresizingMaskIntoConstraints = NO;
+  image_view.contentMode = UIViewContentModeScaleAspectFit;
+  image_view.userInteractionEnabled = NO;
+
+  UIImage* magnifying_glass_image;
+  if (UseSymbols()) {
+    magnifying_glass_image = DefaultSymbolWithPointSize(
+        kMagnifyingglassSymbol, kSymbolContentSuggestionsPointSize);
+    image_view.tintColor = [UIColor colorNamed:kGrey500Color];
+  } else {
+    magnifying_glass_image =
+        [[UIImage imageNamed:@"location_bar_magnifyingglass"]
+            imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate];
+    image_view.tintColor = [UIColor colorNamed:kGrey500Color];
+  }
+
+  [image_view setImage:magnifying_glass_image];
+  return image_view;
+}
+
 void ConfigureSearchHintLabel(UILabel* search_hint_label,
                               UIView* search_tab_target) {
   [search_hint_label setTranslatesAutoresizingMaskIntoConstraints:NO];
@@ -218,7 +240,7 @@
       [camera_image imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate];
 
   [lens_button setImage:camera_image forState:UIControlStateNormal];
-  lens_button.tintColor = [UIColor colorNamed:kGrey500Color];
+  lens_button.tintColor = [UIColor colorNamed:kGrey600Color];
   lens_button.accessibilityLabel = l10n_util::GetNSString(IDS_IOS_ACCNAME_LENS);
   lens_button.accessibilityIdentifier = @"Lens";
 
diff --git a/ios/chrome/browser/ui/content_suggestions/content_suggestions_header_view.mm b/ios/chrome/browser/ui/content_suggestions/content_suggestions_header_view.mm
index 81b905db7..56261c7d 100644
--- a/ios/chrome/browser/ui/content_suggestions/content_suggestions_header_view.mm
+++ b/ios/chrome/browser/ui/content_suggestions/content_suggestions_header_view.mm
@@ -61,12 +61,20 @@
 const CGFloat kEndButtonFakeboxTrailingSpace = 12.0;
 const CGFloat kEndButtonOmniboxTrailingSpace = 7.0;
 
+// The constants for the constraints affecting the magnifying glass.
+const CGFloat kMagnifyingGlassFakeboxLeadingSpace = 12.0;
+const CGFloat kMagnifyingGlassOmniboxLeadingSpace = 7.0;
+
 // The constants for the constraints affecting the vertical separator that
 // appears between the Lens and Voice Search buttons.
 const CGFloat kLensButtonSeparatorWidth = 1;
 const CGFloat kLensButtonSeparatorHeight = 12;
-const CGFloat kLensButtonSeparatorLeftMargin = 8;
-const CGFloat kLensButtonSeparatorRightMargin = 13;
+const CGFloat kLensButtonSeparatorLeadingMargin = 8;
+const CGFloat kLensButtonSeparatorTrailingMargin = 10;
+
+// The constants for positioning the magnifying glass.
+const CGFloat kMagnifyingGlassTrailingMargin = 8;
+const CGFloat kMagnifyingGlassViewSize = 24;
 
 // Returns the height of the toolbar based on the preferred content size of the
 // application.
@@ -87,6 +95,9 @@
 // Lens is not available.
 @property(nonatomic, strong) UIView* lensButtonSeparator;
 
+// Image view that shows the magnifying glass.
+@property(nonatomic, strong) UIImageView* magnifyingGlassView;
+
 @property(nonatomic, strong, readwrite)
     ExtendedTouchTargetButton* voiceSearchButton;
 
@@ -106,6 +117,15 @@
 // Constraint for positioning the end button away from the fake box rounded
 // rectangle.
 @property(nonatomic, strong) NSLayoutConstraint* endButtonTrailingConstraint;
+// The magnifying glass should always be at least inside the fake omnibox.
+// When the fake omnibox is shrunk, the position from the leading side of
+// the search field should yield.
+@property(nonatomic, strong)
+    NSLayoutConstraint* magnifyingGlassLeadingMarginConstraint;
+// Constraint for positioning the magnifying glass away from the fake box
+// rounded rectangle.
+@property(nonatomic, strong)
+    NSLayoutConstraint* magnifyingGlassLeadingConstraint;
 // Layout constraint for the invisible button that is where the omnibox should
 // be and that focuses the omnibox when tapped.
 @property(nonatomic, strong) NSLayoutConstraint* invisibleOmniboxConstraint;
@@ -212,17 +232,39 @@
         constraintEqualToAnchor:self.fakeLocationBar.trailingAnchor],
   ]];
 
+  // Configure the magnifying glass icon.
+  UIImageView* magnifyingGlass =
+      content_suggestions::CreateMagnifyingGlassView();
+  [searchField addSubview:magnifyingGlass];
+  self.magnifyingGlassView = magnifyingGlass;
+
+  self.magnifyingGlassLeadingMarginConstraint = [magnifyingGlass.leadingAnchor
+      constraintEqualToAnchor:[searchField leadingAnchor]];
+  self.magnifyingGlassLeadingMarginConstraint.priority =
+      UILayoutPriorityDefaultHigh + 1;
+  self.magnifyingGlassLeadingConstraint = [magnifyingGlass.leadingAnchor
+      constraintGreaterThanOrEqualToAnchor:self.fakeLocationBar.leadingAnchor
+                                  constant:kMagnifyingGlassFakeboxLeadingSpace];
+  [NSLayoutConstraint activateConstraints:@[
+    self.magnifyingGlassLeadingMarginConstraint,
+    self.magnifyingGlassLeadingConstraint,
+    [magnifyingGlass.centerYAnchor
+        constraintEqualToAnchor:self.fakeLocationBar.centerYAnchor],
+    [magnifyingGlass.widthAnchor
+        constraintEqualToConstant:kMagnifyingGlassViewSize],
+    [magnifyingGlass.heightAnchor
+        constraintEqualToConstant:kMagnifyingGlassViewSize],
+  ]];
+
   // Hint label.
   self.searchHintLabel = [[UILabel alloc] init];
   content_suggestions::ConfigureSearchHintLabel(self.searchHintLabel,
                                                 searchField);
   self.searchHintLabel.font = [self hintLabelFont];
   self.hintLabelLeadingConstraint = [self.searchHintLabel.leadingAnchor
-      constraintGreaterThanOrEqualToAnchor:[searchField leadingAnchor]
-                                  constant:ntp_header::kHintLabelSidePadding];
+      constraintEqualToAnchor:magnifyingGlass.trailingAnchor
+                     constant:kMagnifyingGlassTrailingMargin];
   [NSLayoutConstraint activateConstraints:@[
-    [self.searchHintLabel.centerXAnchor
-        constraintEqualToAnchor:self.fakeLocationBar.centerXAnchor],
     self.hintLabelLeadingConstraint,
     [self.searchHintLabel.heightAnchor
         constraintEqualToAnchor:self.fakeLocationBar.heightAnchor
@@ -295,10 +337,10 @@
       // Separator constraints.
       [self.lensButtonSeparator.leadingAnchor
           constraintEqualToAnchor:self.voiceSearchButton.trailingAnchor
-                         constant:kLensButtonSeparatorLeftMargin],
+                         constant:kLensButtonSeparatorLeadingMargin],
       [self.lensButtonSeparator.trailingAnchor
           constraintEqualToAnchor:self.lensButton.leadingAnchor
-                         constant:-kLensButtonSeparatorRightMargin],
+                         constant:-kLensButtonSeparatorTrailingMargin],
       [self.lensButtonSeparator.centerYAnchor
           constraintEqualToAnchor:self.fakeLocationBar.centerYAnchor],
       [self.lensButtonSeparator.widthAnchor
@@ -422,15 +464,14 @@
         toolbarExpandedHeight - kFakeLocationBarHeightMargin;
     self.fakeLocationBar.layer.cornerRadius =
         self.fakeLocationBarHeightConstraint.constant / 2;
-    [self scaleHintLabelForPercent:percent];
+    [self scaleAndUpdateConstraintsForHintLabelWithPercent:percent];
     self.fakeToolbarTopConstraint.constant = 0;
 
     self.fakeLocationBarLeadingConstraint.constant = 0;
     self.fakeLocationBarTrailingConstraint.constant = 0;
     self.fakeLocationBarTopConstraint.constant = 0;
 
-    self.hintLabelLeadingConstraint.constant =
-        ntp_header::kHintLabelSidePadding;
+    self.magnifyingGlassLeadingMarginConstraint.constant = 0;
     self.endButtonTrailingMarginConstraint.constant = 0;
 
     self.separator.alpha = 0;
@@ -477,8 +518,8 @@
   self.fakeLocationBar.layer.cornerRadius =
       self.fakeLocationBarHeightConstraint.constant / 2;
 
-  // Scale the hintLabel, and make sure the frame stays left aligned.
-  [self scaleHintLabelForPercent:percent];
+  // Scale the hintLabel and update the horizontal constraint constant.
+  [self scaleAndUpdateConstraintsForHintLabelWithPercent:percent];
 
   // Adjust the position of the search field's subviews by adjusting their
   // constraint constant value.
@@ -491,8 +532,14 @@
       -kEndButtonFakeboxTrailingSpace +
       (kEndButtonFakeboxTrailingSpace - kEndButtonOmniboxTrailingSpace) *
           percent;
-  self.hintLabelLeadingConstraint.constant =
-      subviewsDiff + ntp_header::kHintLabelSidePadding;
+  // A similar scaling is applied to the magnifying glass icon on the other
+  // side of the fakebox.
+  self.magnifyingGlassLeadingMarginConstraint.constant = subviewsDiff;
+  self.magnifyingGlassLeadingConstraint.constant =
+      kMagnifyingGlassFakeboxLeadingSpace -
+      (kMagnifyingGlassFakeboxLeadingSpace -
+       kMagnifyingGlassOmniboxLeadingSpace) *
+          percent;
 }
 
 - (void)setFakeboxHighlighted:(BOOL)highlighted {
@@ -563,12 +610,24 @@
       self.traitCollection.preferredContentSizeCategory);
 }
 
-// Scale the the hint label down to at most content_suggestions::kHintTextScale.
-- (void)scaleHintLabelForPercent:(CGFloat)percent {
+// Scale the the hint label down to at most content_suggestions::kHintTextScale
+// and updates the horizontal constraint constant accordingly.
+- (void)scaleAndUpdateConstraintsForHintLabelWithPercent:(CGFloat)percent {
+  if (!self.searchHintLabel) {
+    return;
+  }
   CGFloat scaleValue =
       1 + (content_suggestions::kHintTextScale * (1 - percent));
   self.searchHintLabel.transform =
       CGAffineTransformMakeScale(scaleValue, scaleValue);
+
+  // Update the hint label constraint based with half of the change in width
+  // from the original scale, since constraints are calculated before
+  // transformations are applied.
+  self.hintLabelLeadingConstraint.constant =
+      kMagnifyingGlassTrailingMargin +
+      (1 - percent) * content_suggestions::kHintTextScale *
+          self.searchHintLabel.bounds.size.width * 0.5;
 }
 
 @end
diff --git a/ios/chrome/browser/ui/content_suggestions/content_suggestions_mediator.mm b/ios/chrome/browser/ui/content_suggestions/content_suggestions_mediator.mm
index 699be81..ac615bb 100644
--- a/ios/chrome/browser/ui/content_suggestions/content_suggestions_mediator.mm
+++ b/ios/chrome/browser/ui/content_suggestions/content_suggestions_mediator.mm
@@ -207,6 +207,7 @@
 
 + (void)registerBrowserStatePrefs:(user_prefs::PrefRegistrySyncable*)registry {
   registry->RegisterInt64Pref(prefs::kIosDiscoverFeedLastRefreshTime, 0);
+  registry->RegisterInt64Pref(prefs::kIosDiscoverFeedLastUnseenRefreshTime, 0);
 }
 
 - (void)disconnect {
diff --git a/ios/chrome/browser/ui/content_suggestions/ntp_home_egtest.mm b/ios/chrome/browser/ui/content_suggestions/ntp_home_egtest.mm
index 8ff2f4a..ced387a 100644
--- a/ios/chrome/browser/ui/content_suggestions/ntp_home_egtest.mm
+++ b/ios/chrome/browser/ui/content_suggestions/ntp_home_egtest.mm
@@ -110,7 +110,7 @@
 // visible, but not actually zero, probably due to some sort of floating
 // point calculation.
 id<GREYMatcher> notPracticallyVisible() {
-  return grey_not(grey_minimumVisiblePercent(0.001));
+  return grey_not(grey_minimumVisiblePercent(0.01));
 }
 }
 
diff --git a/ios/chrome/browser/ui/icons/BUILD.gn b/ios/chrome/browser/ui/icons/BUILD.gn
index c77ad4ba..c06c8c1e 100644
--- a/ios/chrome/browser/ui/icons/BUILD.gn
+++ b/ios/chrome/browser/ui/icons/BUILD.gn
@@ -62,6 +62,7 @@
     "//ios/chrome/browser/ui/icons/resources:camera_lens",
     "//ios/chrome/browser/ui/icons/resources:checkerboard_shield",
     "//ios/chrome/browser/ui/icons/resources:checkermark_shield",
+    "//ios/chrome/browser/ui/icons/resources:dino",
     "//ios/chrome/browser/ui/icons/resources:incognito",
     "//ios/chrome/browser/ui/icons/resources:incognito_circle_fill",
     "//ios/chrome/browser/ui/icons/resources:incognito_circle_fill_ios14",
@@ -95,6 +96,20 @@
   }
 }
 
+source_set("symbols_views") {
+  configs += [ "//build/config/compiler:enable_arc" ]
+  sources = [
+    "colorful_background_symbol_view.h",
+    "colorful_background_symbol_view.mm",
+  ]
+  deps = [
+    ":symbols",
+    "//ios/chrome/common/ui/colors",
+    "//ios/chrome/common/ui/table_view:cells_constants",
+    "//ios/chrome/common/ui/util",
+  ]
+}
+
 source_set("unit_tests") {
   testonly = true
   sources = [ "chrome_icon_unittest.mm" ]
diff --git a/ios/chrome/browser/ui/icons/colorful_background_symbol_view.h b/ios/chrome/browser/ui/icons/colorful_background_symbol_view.h
new file mode 100644
index 0000000..f81319d
--- /dev/null
+++ b/ios/chrome/browser/ui/icons/colorful_background_symbol_view.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 IOS_CHROME_BROWSER_UI_ICONS_COLORFUL_BACKGROUND_SYMBOL_VIEW_H_
+#define IOS_CHROME_BROWSER_UI_ICONS_COLORFUL_BACKGROUND_SYMBOL_VIEW_H_
+
+#import <UIKit/UIKit.h>
+
+// A view used to display a symbol with a colorful background.
+@interface ColorfulBackgroundSymbolView : UIView
+
+- (instancetype)init NS_DESIGNATED_INITIALIZER;
+
+- (instancetype)initWithFrame:(CGRect)frame NS_UNAVAILABLE;
+- (instancetype)initWithCoder:(NSCoder*)coder NS_UNAVAILABLE;
+
+// The color of the border. If nil, then there is no border (default is nil).
+@property(nonatomic, strong) UIColor* borderColor;
+
+// Sets the symbol to the symbol named `symbolName`, `systemSymbol` is used to
+// check if it is a symbol provided by the system or not. When using this
+// method, the default size is used.
+- (void)setSymbolName:(NSString*)symbolName systemSymbol:(BOOL)systemSymbol;
+
+// Sets the symbol.
+// @discussion
+// Use this setter when your symbol needs to be of a custom size.
+- (void)setSymbol:(UIImage*)symbol;
+
+// Sets the tint color of the symbol (default is white).
+- (void)setSymbolTintColor:(UIColor*)color;
+
+@end
+
+#endif  // IOS_CHROME_BROWSER_UI_ICONS_COLORFUL_BACKGROUND_SYMBOL_VIEW_H_
diff --git a/ios/chrome/browser/ui/icons/colorful_background_symbol_view.mm b/ios/chrome/browser/ui/icons/colorful_background_symbol_view.mm
new file mode 100644
index 0000000..6bf0f2d
--- /dev/null
+++ b/ios/chrome/browser/ui/icons/colorful_background_symbol_view.mm
@@ -0,0 +1,79 @@
+// 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/chrome/browser/ui/icons/colorful_background_symbol_view.h"
+
+#import "ios/chrome/browser/ui/icons/symbols.h"
+#import "ios/chrome/common/ui/table_view/table_view_cells_constants.h"
+#import "ios/chrome/common/ui/util/constraints_ui_util.h"
+
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
+namespace {
+const CGFloat kSymbolSize = 18;
+}  // namespace
+
+@implementation ColorfulBackgroundSymbolView {
+  UIImageView* _symbolView;
+}
+
+- (instancetype)init {
+  self = [super initWithFrame:CGRectMake(0, 0, kTableViewIconImageSize,
+                                         kTableViewIconImageSize)];
+  if (self) {
+    _symbolView = [[UIImageView alloc] init];
+    _symbolView.translatesAutoresizingMaskIntoConstraints = NO;
+    _symbolView.contentMode = UIViewContentModeCenter;
+    _symbolView.tintColor = UIColor.whiteColor;
+    [self addSubview:_symbolView];
+
+    self.layer.cornerRadius = kColorfulBackgroundSymbolCornerRadius;
+
+    AddSameConstraints(self, _symbolView);
+  }
+  return self;
+}
+
+#pragma mark - Accessors
+
+- (void)setBorderColor:(UIColor*)borderColor {
+  if (_borderColor == borderColor) {
+    return;
+  }
+
+  _borderColor = borderColor;
+
+  if (borderColor) {
+    self.layer.borderWidth = 1;
+  } else {
+    self.layer.borderWidth = 0;
+  }
+  self.layer.borderColor = [borderColor CGColor];
+}
+
+- (void)setSymbolName:(NSString*)symbolName systemSymbol:(BOOL)systemSymbol {
+  if (systemSymbol) {
+    _symbolView.image = DefaultSymbolWithPointSize(symbolName, kSymbolSize);
+  } else {
+    _symbolView.image = CustomSymbolWithPointSize(symbolName, kSymbolSize);
+  }
+}
+
+- (void)setSymbol:(UIImage*)symbol {
+  _symbolView.image = symbol;
+}
+
+- (void)setSymbolTintColor:(UIColor*)color {
+  _symbolView.tintColor = color;
+}
+
+#pragma mark - UIView
+
+- (CGSize)intrinsicContentSize {
+  return CGSizeMake(kTableViewIconImageSize, kTableViewIconImageSize);
+}
+
+@end
diff --git a/ios/chrome/browser/ui/icons/resources/BUILD.gn b/ios/chrome/browser/ui/icons/resources/BUILD.gn
index 9f2ff1a..40cd697 100644
--- a/ios/chrome/browser/ui/icons/resources/BUILD.gn
+++ b/ios/chrome/browser/ui/icons/resources/BUILD.gn
@@ -26,6 +26,13 @@
   ]
 }
 
+symbolset("dino") {
+  sources = [
+    "dino.symbolset/Contents.json",
+    "dino.symbolset/dino.cr.svg",
+  ]
+}
+
 symbolset("incognito") {
   sources = [
     "incognito.symbolset/Contents.json",
diff --git a/ios/chrome/browser/ui/icons/resources/dino.symbolset/Contents.json b/ios/chrome/browser/ui/icons/resources/dino.symbolset/Contents.json
new file mode 100644
index 0000000..d8ee87bd
--- /dev/null
+++ b/ios/chrome/browser/ui/icons/resources/dino.symbolset/Contents.json
@@ -0,0 +1,12 @@
+{
+  "info": {
+    "author": "xcode",
+    "version": 1
+  },
+  "symbols": [
+    {
+      "filename": "dino.cr.svg",
+      "idiom": "universal"
+    }
+  ]
+}
diff --git a/ios/chrome/browser/ui/icons/resources/dino.symbolset/dino.cr.svg b/ios/chrome/browser/ui/icons/resources/dino.symbolset/dino.cr.svg
new file mode 100644
index 0000000..e4c5fca7
--- /dev/null
+++ b/ios/chrome/browser/ui/icons/resources/dino.symbolset/dino.cr.svg
@@ -0,0 +1,161 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--Generator: Apple Native CoreSVG 175.5-->
+<!DOCTYPE svg
+PUBLIC "-//W3C//DTD SVG 1.1//EN"
+       "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="3300" height="2200">
+ <!--glyph: "", point size: 100.0, font version: "18.0d12e2", template writer version: "101"-->
+ <g id="Notes">
+  <rect height="2200" id="artboard" style="fill:white;opacity:1" width="3300" x="0" y="0"/>
+  <line style="fill:none;stroke:black;opacity:1;stroke-width:0.5;" x1="263" x2="3036" y1="292" y2="292"/>
+  <text style="stroke:none;fill:black;font-family:sans-serif;font-size:13;font-weight:bold;" transform="matrix(1 0 0 1 263 322)">Weight/Scale Variations</text>
+  <text style="stroke:none;fill:black;font-family:sans-serif;font-size:13;text-anchor:middle;" transform="matrix(1 0 0 1 559.711 322)">Ultralight</text>
+  <text style="stroke:none;fill:black;font-family:sans-serif;font-size:13;text-anchor:middle;" transform="matrix(1 0 0 1 856.422 322)">Thin</text>
+  <text style="stroke:none;fill:black;font-family:sans-serif;font-size:13;text-anchor:middle;" transform="matrix(1 0 0 1 1153.13 322)">Light</text>
+  <text style="stroke:none;fill:black;font-family:sans-serif;font-size:13;text-anchor:middle;" transform="matrix(1 0 0 1 1449.84 322)">Regular</text>
+  <text style="stroke:none;fill:black;font-family:sans-serif;font-size:13;text-anchor:middle;" transform="matrix(1 0 0 1 1746.56 322)">Medium</text>
+  <text style="stroke:none;fill:black;font-family:sans-serif;font-size:13;text-anchor:middle;" transform="matrix(1 0 0 1 2043.27 322)">Semibold</text>
+  <text style="stroke:none;fill:black;font-family:sans-serif;font-size:13;text-anchor:middle;" transform="matrix(1 0 0 1 2339.98 322)">Bold</text>
+  <text style="stroke:none;fill:black;font-family:sans-serif;font-size:13;text-anchor:middle;" transform="matrix(1 0 0 1 2636.69 322)">Heavy</text>
+  <text style="stroke:none;fill:black;font-family:sans-serif;font-size:13;text-anchor:middle;" transform="matrix(1 0 0 1 2933.4 322)">Black</text>
+  <line style="fill:none;stroke:black;opacity:1;stroke-width:0.5;" x1="263" x2="3036" y1="1903" y2="1903"/>
+  <g transform="matrix(1 0 0 1 263 1933)">
+   <path d="M9.24805 0.830078C13.5547 0.830078 17.1387-2.74414 17.1387-7.05078C17.1387-11.3574 13.5449-14.9316 9.23828-14.9316C4.94141-14.9316 1.36719-11.3574 1.36719-7.05078C1.36719-2.74414 4.95117 0.830078 9.24805 0.830078ZM9.24805-0.654297C5.70312-0.654297 2.87109-3.49609 2.87109-7.05078C2.87109-10.6055 5.69336-13.4473 9.23828-13.4473C12.793-13.4473 15.6445-10.6055 15.6445-7.05078C15.6445-3.49609 12.8027-0.654297 9.24805-0.654297ZM5.6543-7.05078C5.6543-6.62109 5.95703-6.32812 6.40625-6.32812L8.50586-6.32812L8.50586-4.20898C8.50586-3.76953 8.79883-3.4668 9.22852-3.4668C9.67773-3.4668 9.9707-3.76953 9.9707-4.20898L9.9707-6.32812L12.0898-6.32812C12.5293-6.32812 12.832-6.62109 12.832-7.05078C12.832-7.49023 12.5293-7.79297 12.0898-7.79297L9.9707-7.79297L9.9707-9.90234C9.9707-10.3516 9.67773-10.6543 9.22852-10.6543C8.79883-10.6543 8.50586-10.3516 8.50586-9.90234L8.50586-7.79297L6.40625-7.79297C5.95703-7.79297 5.6543-7.49023 5.6543-7.05078Z"/>
+  </g>
+  <g transform="matrix(1 0 0 1 281.867 1933)">
+   <path d="M11.709 2.91016C17.1582 2.91016 21.6699-1.61133 21.6699-7.05078C21.6699-12.5 17.1484-17.0117 11.6992-17.0117C6.25977-17.0117 1.74805-12.5 1.74805-7.05078C1.74805-1.61133 6.26953 2.91016 11.709 2.91016ZM11.709 1.25C7.09961 1.25 3.41797-2.44141 3.41797-7.05078C3.41797-11.6602 7.08984-15.3516 11.6992-15.3516C16.3086-15.3516 20.0098-11.6602 20.0098-7.05078C20.0098-2.44141 16.3184 1.25 11.709 1.25ZM7.17773-7.05078C7.17773-6.57227 7.50977-6.25 8.00781-6.25L10.8789-6.25L10.8789-3.36914C10.8789-2.88086 11.2109-2.53906 11.6895-2.53906C12.1777-2.53906 12.5195-2.87109 12.5195-3.36914L12.5195-6.25L15.4004-6.25C15.8887-6.25 16.2305-6.57227 16.2305-7.05078C16.2305-7.53906 15.8887-7.88086 15.4004-7.88086L12.5195-7.88086L12.5195-10.752C12.5195-11.25 12.1777-11.5918 11.6895-11.5918C11.2109-11.5918 10.8789-11.25 10.8789-10.752L10.8789-7.88086L8.00781-7.88086C7.50977-7.88086 7.17773-7.53906 7.17773-7.05078Z"/>
+  </g>
+  <g transform="matrix(1 0 0 1 305.646 1933)">
+   <path d="M14.9707 5.66406C21.9336 5.66406 27.6953-0.0976562 27.6953-7.05078C27.6953-14.0137 21.9238-19.7754 14.9609-19.7754C8.00781-19.7754 2.25586-14.0137 2.25586-7.05078C2.25586-0.0976562 8.01758 5.66406 14.9707 5.66406ZM14.9707 3.84766C8.93555 3.84766 4.08203-1.01562 4.08203-7.05078C4.08203-13.0957 8.92578-17.9492 14.9609-17.9492C21.0059-17.9492 25.8691-13.0957 25.8691-7.05078C25.8691-1.01562 21.0156 3.84766 14.9707 3.84766ZM9.19922-7.05078C9.19922-6.5332 9.57031-6.17188 10.1172-6.17188L14.0625-6.17188L14.0625-2.2168C14.0625-1.67969 14.4336-1.29883 14.9512-1.29883C15.4883-1.29883 15.8594-1.66992 15.8594-2.2168L15.8594-6.17188L19.8145-6.17188C20.3516-6.17188 20.7324-6.5332 20.7324-7.05078C20.7324-7.59766 20.3613-7.96875 19.8145-7.96875L15.8594-7.96875L15.8594-11.9141C15.8594-12.4609 15.4883-12.8418 14.9512-12.8418C14.4336-12.8418 14.0625-12.4609 14.0625-11.9141L14.0625-7.96875L10.1172-7.96875C9.57031-7.96875 9.19922-7.59766 9.19922-7.05078Z"/>
+  </g>
+  <text style="stroke:none;fill:black;font-family:sans-serif;font-size:13;font-weight:bold;" transform="matrix(1 0 0 1 263 1953)">Design Variations</text>
+  <text style="stroke:none;fill:black;font-family:sans-serif;font-size:13;" transform="matrix(1 0 0 1 263 1971)">Symbols are supported in up to nine weights and three scales.</text>
+  <text style="stroke:none;fill:black;font-family:sans-serif;font-size:13;" transform="matrix(1 0 0 1 263 1989)">For optimal layout with text and other symbols, vertically align</text>
+  <text style="stroke:none;fill:black;font-family:sans-serif;font-size:13;" transform="matrix(1 0 0 1 263 2007)">symbols with the adjacent text.</text>
+  <line style="fill:none;stroke:#00AEEF;stroke-width:0.5;opacity:1.0;" x1="776" x2="776" y1="1919" y2="1933"/>
+  <g transform="matrix(1 0 0 1 776 1933)">
+   <path d="M3.31055 0.15625C3.82812 0.15625 4.08203-0.0390625 4.26758-0.585938L5.52734-4.0332L11.2891-4.0332L12.5488-0.585938C12.7344-0.0390625 12.9883 0.15625 13.4961 0.15625C14.0137 0.15625 14.3457-0.15625 14.3457-0.644531C14.3457-0.810547 14.3164-0.966797 14.2383-1.17188L9.6582-13.3691C9.43359-13.9648 9.0332-14.2676 8.4082-14.2676C7.80273-14.2676 7.39258-13.9746 7.17773-13.3789L2.59766-1.16211C2.51953-0.957031 2.49023-0.800781 2.49023-0.634766C2.49023-0.146484 2.80273 0.15625 3.31055 0.15625ZM6.00586-5.51758L8.37891-12.0898L8.42773-12.0898L10.8008-5.51758Z"/>
+  </g>
+  <line style="fill:none;stroke:#00AEEF;stroke-width:0.5;opacity:1.0;" x1="793.197" x2="793.197" y1="1919" y2="1933"/>
+  <text style="stroke:none;fill:black;font-family:sans-serif;font-size:13;font-weight:bold;" transform="matrix(1 0 0 1 776 1953)">Margins</text>
+  <text style="stroke:none;fill:black;font-family:sans-serif;font-size:13;" transform="matrix(1 0 0 1 776 1971)">Leading and trailing margins on the left and right side of each symbol</text>
+  <text style="stroke:none;fill:black;font-family:sans-serif;font-size:13;" transform="matrix(1 0 0 1 776 1989)">can be adjusted by modifying the x-location of the margin guidelines.</text>
+  <text style="stroke:none;fill:black;font-family:sans-serif;font-size:13;" transform="matrix(1 0 0 1 776 2007)">Modifications are automatically applied proportionally to all</text>
+  <text style="stroke:none;fill:black;font-family:sans-serif;font-size:13;" transform="matrix(1 0 0 1 776 2025)">scales and weights.</text>
+  <g transform="matrix(1 0 0 1 1289 1933)">
+   <path d="M2.8418 1.86523L4.54102 3.57422C5.40039 4.44336 6.38672 4.38477 7.31445 3.35938L18.0078-8.42773L17.041-9.4043L6.42578 2.27539C6.07422 2.67578 5.74219 2.77344 5.27344 2.30469L4.10156 1.14258C3.63281 0.683594 3.74023 0.341797 4.14062-0.0195312L15.6152-10.8203L14.6387-11.7871L3.04688-0.898438C2.06055 0.0195312 1.98242 0.996094 2.8418 1.86523ZM9.25781-16.3281C8.83789-15.918 8.80859-15.3418 9.04297-14.9512C9.27734-14.5898 9.73633-14.3555 10.3809-14.5215C11.8457-14.8633 13.3691-14.9219 14.7949-13.9844L14.209-12.5293C13.8672-11.6992 14.043-11.1133 14.5801-10.5664L16.875-8.25195C17.3633-7.76367 17.7734-7.74414 18.3398-7.8418L19.4043-8.03711L20.0684-7.36328L20.0293-6.80664C19.9902-6.30859 20.1172-5.92773 20.6055-5.44922L21.3672-4.70703C21.8457-4.22852 22.4609-4.19922 22.9297-4.66797L25.8398-7.58789C26.3086-8.05664 26.2891-8.65234 25.8105-9.13086L25.0391-9.89258C24.5605-10.3711 24.1895-10.5273 23.7109-10.4883L23.1348-10.4395L22.4902-11.0742L22.7344-12.1973C22.8613-12.7637 22.7051-13.2031 22.1191-13.7891L19.9219-15.9766C16.582-19.2969 12.1484-19.2188 9.25781-16.3281ZM10.752-15.957C13.1836-17.7344 16.4746-17.4316 18.7012-15.2051L21.1328-12.793C21.3672-12.5586 21.4062-12.373 21.3379-12.0312L21.0156-10.5469L22.5195-9.0625L23.5059-9.12109C23.7598-9.13086 23.8379-9.11133 24.0332-8.91602L24.6094-8.33984L22.168-5.89844L21.5918-6.47461C21.3965-6.66992 21.3672-6.74805 21.377-7.01172L21.4453-7.98828L19.9512-9.47266L18.4277-9.21875C18.1055-9.15039 17.959-9.17969 17.7148-9.41406L15.7129-11.416C15.459-11.6504 15.4297-11.8164 15.5859-12.1875L16.4648-14.2773C14.9023-15.7324 12.8711-16.3574 10.8398-15.7617C10.6836-15.7227 10.625-15.8496 10.752-15.957Z"/>
+  </g>
+  <text style="stroke:none;fill:black;font-family:sans-serif;font-size:13;font-weight:bold;" transform="matrix(1 0 0 1 1289 1953)">Exporting</text>
+  <text style="stroke:none;fill:black;font-family:sans-serif;font-size:13;" transform="matrix(1 0 0 1 1289 1971)">Symbols should be outlined when exporting to ensure the</text>
+  <text style="stroke:none;fill:black;font-family:sans-serif;font-size:13;" transform="matrix(1 0 0 1 1289 1989)">design is preserved when submitting to Xcode.</text>
+  <text id="template-version" style="stroke:none;fill:black;font-family:sans-serif;font-size:13;text-anchor:end;" transform="matrix(1 0 0 1 3036 1933)">Template v.2.0</text>
+  <text style="stroke:none;fill:black;font-family:sans-serif;font-size:13;text-anchor:end;" transform="matrix(1 0 0 1 3036 1951)">Requires Xcode 12 or greater</text>
+  <text id="descriptive-name" style="stroke:none;fill:black;font-family:sans-serif;font-size:13;text-anchor:end;" transform="matrix(1 0 0 1 3036 1969)">Generated from dino.cr</text>
+  <text style="stroke:none;fill:black;font-family:sans-serif;font-size:13;text-anchor:end;" transform="matrix(1 0 0 1 3036 1987)">Typeset at 100 points</text>
+  <text style="stroke:none;fill:black;font-family:sans-serif;font-size:13;" transform="matrix(1 0 0 1 263 726)">Small</text>
+  <text style="stroke:none;fill:black;font-family:sans-serif;font-size:13;" transform="matrix(1 0 0 1 263 1156)">Medium</text>
+  <text style="stroke:none;fill:black;font-family:sans-serif;font-size:13;" transform="matrix(1 0 0 1 263 1586)">Large</text>
+ </g>
+ <g id="Guides">
+  <g id="H-reference" style="fill:#27AAE1;stroke:none;" transform="matrix(1 0 0 1 339 696)">
+   <path d="M0.993654 0L3.63775 0L29.3281-67.1323L30.0303-67.1323L30.0303-70.459L28.1226-70.459ZM11.6885-24.4799L46.9815-24.4799L46.2315-26.7285L12.4385-26.7285ZM55.1196 0L57.7637 0L30.6382-70.459L29.4326-70.459L29.4326-67.1323Z"/>
+  </g>
+  <line id="Baseline-S" style="fill:none;stroke:#27AAE1;opacity:1;stroke-width:0.5;" x1="263" x2="3036" y1="696" y2="696"/>
+  <line id="Capline-S" style="fill:none;stroke:#27AAE1;opacity:1;stroke-width:0.5;" x1="263" x2="3036" y1="625.541" y2="625.541"/>
+  <g id="H-reference" style="fill:#27AAE1;stroke:none;" transform="matrix(1 0 0 1 339 1126)">
+   <path d="M0.993654 0L3.63775 0L29.3281-67.1323L30.0303-67.1323L30.0303-70.459L28.1226-70.459ZM11.6885-24.4799L46.9815-24.4799L46.2315-26.7285L12.4385-26.7285ZM55.1196 0L57.7637 0L30.6382-70.459L29.4326-70.459L29.4326-67.1323Z"/>
+  </g>
+  <line id="Baseline-M" style="fill:none;stroke:#27AAE1;opacity:1;stroke-width:0.5;" x1="263" x2="3036" y1="1126" y2="1126"/>
+  <line id="Capline-M" style="fill:none;stroke:#27AAE1;opacity:1;stroke-width:0.5;" x1="263" x2="3036" y1="1055.54" y2="1055.54"/>
+  <g id="H-reference" style="fill:#27AAE1;stroke:none;" transform="matrix(1 0 0 1 339 1556)">
+   <path d="M0.993654 0L3.63775 0L29.3281-67.1323L30.0303-67.1323L30.0303-70.459L28.1226-70.459ZM11.6885-24.4799L46.9815-24.4799L46.2315-26.7285L12.4385-26.7285ZM55.1196 0L57.7637 0L30.6382-70.459L29.4326-70.459L29.4326-67.1323Z"/>
+  </g>
+  <line id="Baseline-L" style="fill:none;stroke:#27AAE1;opacity:1;stroke-width:0.5;" x1="263" x2="3036" y1="1556" y2="1556"/>
+  <line id="Capline-L" style="fill:none;stroke:#27AAE1;opacity:1;stroke-width:0.5;" x1="263" x2="3036" y1="1485.54" y2="1485.54"/>
+  <line id="left-margin" style="fill:none;stroke:#00AEEF;stroke-width:0.5;opacity:1.0;" x1="1389.97" x2="1389.97" y1="1030.79" y2="1150.12"/>
+  <line id="right-margin" style="fill:none;stroke:#00AEEF;stroke-width:0.5;opacity:1.0;" x1="1509.72" x2="1509.72" y1="1030.79" y2="1150.12"/>
+ </g>
+ <g id="Symbols">
+  <g id="Black-L" transform="matrix(1 0 0 1 2853.87 1556)">
+   <path d="M46.15 43.34L46.15 16.09L39.38 16.09L39.38 9.15L32.6 9.15L32.6 2.38L25.83 2.38L25.83-4.39L18.9-4.39L18.9-11.16L11.96-11.16L11.96-52.12L18.73-52.12L18.73-38.41L25.67-38.41L25.67-31.64L32.6-31.64L32.6-24.87L46.15-24.87L46.15-31.64L52.76-31.64L52.92-38.41L65.34-38.41L65.34-45.35L77.59-45.35L77.59-52.12L84.36-52.12L84.36-94.37L91.14-94.37L91.14-101.14L141.61-101.14L141.61-94.37L148.54-94.37L148.54-65.67L114.36-65.67L114.36-58.89L134.83-58.89L134.83-52.12L107.42-52.12L107.42-38.58L121.13-38.58L121.13-24.87L114.36-24.87L114.36-31.64L107.42-31.64L107.42-9.87L100.65-9.87L100.65-1.65L93.88-1.65L93.88 5.28L87.11 5.28L87.11 36.57L93.88 36.57L93.88 43.34L80.17 43.34L80.17 23.02L73.4 23.02L73.4 16.09L66.63 16.09L66.63 23.02L59.85 23.02L59.85 29.79L53.08 29.79L53.08 36.57L59.85 36.57L59.85 43.34L46.15 43.34ZM93.39-83.56L100.17-83.56L100.17-90.34L93.39-90.34L93.39-83.56ZM5.19 24.96L32.6 24.96L32.6 21.57L5.19 21.57L5.19 24.96ZM100.65 24.96L154.51 24.96L154.51 21.57L100.65 21.57L100.65 24.96ZM25.67 38.66L39.38 38.66L39.38 35.12L25.67 35.12L25.67 38.66ZM5.19 48.82L18.73 48.82L18.73 45.44L5.19 45.44L5.19 48.82ZM144.99 45.44L148.54 45.44L148.54 42.05L144.99 42.05L144.99 45.44Z"/>
+  </g>
+  <g id="Heavy-L" transform="matrix(1 0 0 1 2557.16 1556)">
+   <path d="M46.14 43.34L46.14 16.09L39.37 16.09L39.37 9.15L32.6 9.15L32.6 2.38L25.83 2.38L25.83-4.39L18.89-4.39L18.89-11.16L11.96-11.16L11.96-52.12L18.73-52.12L18.73-38.41L25.66-38.41L25.66-31.64L32.6-31.64L32.6-24.87L46.14-24.87L46.14-31.64L52.75-31.64L52.92-38.41L65.33-38.41L65.33-45.35L77.59-45.35L77.59-52.12L84.36-52.12L84.36-94.37L91.13-94.37L91.13-101.14L141.6-101.14L141.6-94.37L148.54-94.37L148.54-65.67L114.35-65.67L114.35-58.89L134.83-58.89L134.83-52.12L107.42-52.12L107.42-38.58L121.12-38.58L121.12-24.87L114.35-24.87L114.35-31.64L107.42-31.64L107.42-9.87L100.65-9.87L100.65-1.65L93.87-1.65L93.87 5.28L87.1 5.28L87.1 36.57L93.87 36.57L93.87 43.34L80.17 43.34L80.17 23.02L73.39 23.02L73.39 16.09L66.62 16.09L66.62 23.02L59.85 23.02L59.85 29.79L53.08 29.79L53.08 36.57L59.85 36.57L59.85 43.34L46.14 43.34ZM93.39-83.56L100.16-83.56L100.16-90.34L93.39-90.34L93.39-83.56ZM5.19 24.96L32.6 24.96L32.6 21.57L5.19 21.57L5.19 24.96ZM100.65 24.96L154.5 24.96L154.5 21.57L100.65 21.57L100.65 24.96ZM25.66 38.66L39.37 38.66L39.37 35.12L25.66 35.12L25.66 38.66ZM5.19 48.82L18.73 48.82L18.73 45.44L5.19 45.44L5.19 48.82ZM144.99 45.44L148.54 45.44L148.54 42.05L144.99 42.05L144.99 45.44Z"/>
+  </g>
+  <g id="Bold-L" transform="matrix(1 0 0 1 2260.45 1556)">
+   <path d="M46.14 41.34L46.14 14.09L39.37 14.09L39.37 7.15L32.6 7.15L32.6 0.38L25.83 0.38L25.83-6.39L18.89-6.39L18.89-13.16L11.96-13.16L11.96-54.12L18.73-54.12L18.73-40.41L25.66-40.41L25.66-33.64L32.6-33.64L32.6-26.87L46.14-26.87L46.14-33.64L52.75-33.64L52.92-40.41L65.33-40.41L65.33-47.35L77.59-47.35L77.59-54.12L84.36-54.12L84.36-96.37L91.13-96.37L91.13-103.14L141.6-103.14L141.6-96.37L148.54-96.37L148.54-67.67L114.35-67.67L114.35-60.89L134.83-60.89L134.83-54.12L107.42-54.12L107.42-40.58L121.12-40.58L121.12-26.87L114.35-26.87L114.35-33.64L107.42-33.64L107.42-11.87L100.65-11.87L100.65-3.65L93.87-3.65L93.87 3.28L87.1 3.28L87.1 34.57L93.87 34.57L93.87 41.34L80.17 41.34L80.17 21.02L73.39 21.02L73.39 14.09L66.62 14.09L66.62 21.02L59.85 21.02L59.85 27.79L53.08 27.79L53.08 34.57L59.85 34.57L59.85 41.34L46.14 41.34ZM93.39-85.56L100.16-85.56L100.16-92.34L93.39-92.34L93.39-85.56ZM5.19 22.96L32.6 22.96L32.6 19.57L5.19 19.57L5.19 22.96ZM100.65 22.96L154.5 22.96L154.5 19.57L100.65 19.57L100.65 22.96ZM25.66 36.66L39.37 36.66L39.37 33.12L25.66 33.12L25.66 36.66ZM5.19 46.82L18.73 46.82L18.73 43.44L5.19 43.44L5.19 46.82ZM144.99 43.44L148.54 43.44L148.54 40.05L144.99 40.05L144.99 43.44Z"/>
+  </g>
+  <g id="Semibold-L" transform="matrix(1 0 0 1 1965.35 1556)">
+   <path d="M45.18 40.69L45.18 14.08L38.57 14.08L38.57 7.31L31.96 7.31L31.96 0.7L25.18 0.7L25.18-5.92L18.57-5.92L18.57-12.69L11.8-12.69L11.8-52.52L18.41-52.52L18.41-39.29L25.18-39.29L25.18-32.52L31.8-32.52L31.8-25.91L45.18-25.91L45.18-32.52L51.63-32.52L51.79-39.29L62.92-39.29L62.92-45.91L74.2-45.91L74.2-52.52L80.82-52.52L80.82-94.76L87.43-94.76L87.43-101.38L138.38-101.38L138.38-94.76L145.16-94.76L145.16-65.9L111.78-65.9L111.78-59.29L131.77-59.29L131.77-52.52L105-52.52L105-39.29L118.39-39.29L118.39-25.91L111.78-25.91L111.78-32.52L105-32.52L105-10.43L98.39-10.43L98.39-1.56L91.78-1.56L91.78 5.05L85.17 5.05L85.17 34.07L91.78 34.07L91.78 40.69L78.4 40.69L78.4 20.69L71.79 20.69L71.79 14.08L65.18 14.08L65.18 20.69L58.56 20.69L58.56 27.3L51.79 27.3L51.79 34.07L58.56 34.07L58.56 40.69L45.18 40.69ZM91.46-84.44L98.07-84.44L98.07-91.06L91.46-91.06L91.46-84.44ZM5.19 21.82L31.96 21.82L31.96 18.43L5.19 18.43L5.19 21.82ZM98.39 21.82L151.28 21.82L151.28 18.43L98.39 18.43L98.39 21.82ZM25.18 35.2L38.57 35.2L38.57 31.82L25.18 31.82L25.18 35.2ZM5.19 45.2L18.41 45.2L18.41 41.82L5.19 41.82L5.19 45.2ZM141.77 41.82L145.16 41.82L145.16 38.43L141.77 38.43L141.77 41.82Z"/>
+  </g>
+  <g id="Medium-L" transform="matrix(1 0 0 1 1669.77 1556)">
+   <path d="M44.53 40.05L44.53 13.93L37.92 13.93L37.92 7.48L31.47 7.48L31.47 0.87L24.86 0.87L24.86-5.58L18.41-5.58L18.41-12.19L11.8-12.19L11.8-51.38L18.41-51.38L18.41-38.32L24.86-38.32L24.86-31.87L31.47-31.87L31.47-25.26L44.53-25.26L44.53-31.87L50.98-31.87L50.98-38.32L61.3-38.32L61.3-44.93L71.62-44.93L71.62-51.38L78.23-51.38L78.23-93.46L84.68-93.46L84.68-99.91L136.12-99.91L136.12-93.46L142.57-93.46L142.57-64.44L109.84-64.44L109.84-57.99L129.51-57.99L129.51-51.38L103.39-51.38L103.39-38.32L116.45-38.32L116.45-25.26L109.84-25.26L109.84-31.87L103.39-31.87L103.39-9.45L96.78-9.45L96.78-0.1L90.33-0.1L90.33 6.51L83.72 6.51L83.72 33.6L90.33 33.6L90.33 40.05L77.11 40.05L77.11 20.54L70.66 20.54L70.66 13.93L64.21 13.93L64.21 20.54L57.59 20.54L57.59 26.99L50.98 26.99L50.98 33.6L57.59 33.6L57.59 40.05L44.53 40.05ZM90.17-83.47L96.62-83.47L96.62-90.08L90.17-90.08L90.17-83.47ZM5.19 21.02L31.47 21.02L31.47 17.8L5.19 17.8L5.19 21.02ZM96.78 21.02L149.02 21.02L149.02 17.8L96.78 17.8L96.78 21.02ZM24.86 34.08L37.92 34.08L37.92 30.86L24.86 30.86L24.86 34.08ZM5.19 43.92L18.25 43.92L18.25 40.7L5.19 40.7L5.19 43.92ZM139.35 40.7L142.57 40.7L142.57 37.31L139.35 37.31L139.35 40.7Z"/>
+  </g>
+  <g id="Regular-L" transform="matrix(1 0 0 1 1374.02 1556)">
+   <path d="M43.89 39.72L43.89 13.92L37.44 13.92L37.44 7.47L30.99 7.47L30.99 1.02L24.54 1.02L24.54-5.43L18.09-5.43L18.09-11.88L11.64-11.88L11.64-50.58L18.09-50.58L18.09-37.68L24.54-37.68L24.54-31.23L30.99-31.23L30.99-24.78L43.89-24.78L43.89-31.23L50.34-31.23L50.34-37.68L60.01-37.68L60.01-44.13L69.69-44.13L69.69-50.58L76.14-50.58L76.14-92.5L82.59-92.5L82.59-98.95L134.19-98.95L134.19-92.5L140.64-92.5L140.64-63.48L108.39-63.48L108.39-57.03L127.74-57.03L127.74-50.58L101.94-50.58L101.94-37.68L114.84-37.68L114.84-24.78L108.39-24.78L108.39-31.23L101.94-31.23L101.94-8.65L95.49-8.65L95.49 1.02L89.04 1.02L89.04 7.47L82.59 7.47L82.59 33.27L89.04 33.27L89.04 39.72L76.14 39.72L76.14 20.37L69.69 20.37L69.69 13.92L63.24 13.92L63.24 20.37L56.79 20.37L56.79 26.82L50.34 26.82L50.34 33.27L56.79 33.27L56.79 39.72L43.89 39.72ZM89.04-82.83L95.49-82.83L95.49-89.28L89.04-89.28L89.04-82.83ZM5.19 20.37L30.99 20.37L30.99 17.15L5.19 17.15L5.19 20.37ZM95.49 20.37L147.09 20.37L147.09 17.15L95.49 17.15L95.49 20.37ZM24.54 33.27L37.44 33.27L37.44 30.05L24.54 30.05L24.54 33.27ZM5.19 42.95L18.09 42.95L18.09 39.72L5.19 39.72L5.19 42.95ZM137.41 39.72L140.64 39.72L140.64 36.5L137.41 36.5L137.41 39.72Z"/>
+  </g>
+  <g id="Light-L" transform="matrix(1 0 0 1 1082.79 1556)">
+   <path d="M40.82 34.56L40.82 10.7L34.86 10.7L34.86 4.73L29.05 4.73L29.05-1.23L23.09-1.23L23.09-7.2L17.12-7.2L17.12-13.17L11.15-13.17L11.15-48.8L17.12-48.8L17.12-36.87L23.09-36.87L23.09-30.9L29.05-30.9L29.05-25.1L40.82-25.1L40.82-30.9L46.79-30.9L46.79-36.87L55.82-36.87L55.82-42.84L64.69-42.84L64.69-48.8L70.65-48.8L70.65-87.5L76.62-87.5L76.62-93.47L124.19-93.47L124.19-87.5L130.16-87.5L130.16-60.73L100.49-60.73L100.49-54.77L118.22-54.77L118.22-48.8L94.52-48.8L94.52-36.87L106.45-36.87L106.45-25.1L100.49-25.1L100.49-30.9L94.52-30.9L94.52-10.1L88.55-10.1L88.55-1.23L82.59-1.23L82.59 4.73L76.62 4.73L76.62 28.6L82.59 28.6L82.59 34.56L70.65 34.56L70.65 16.67L64.69 16.67L64.69 10.7L58.72 10.7L58.72 16.67L52.76 16.67L52.76 22.63L46.79 22.63L46.79 28.6L52.76 28.6L52.76 34.56L40.82 34.56ZM82.59-78.63L88.55-78.63L88.55-84.6L82.59-84.6L82.59-78.63ZM5.19 16.67L29.05 16.67L29.05 13.6L5.19 13.6L5.19 16.67ZM88.55 16.67L136.12 16.67L136.12 13.6L88.55 13.6L88.55 16.67ZM23.09 28.6L34.86 28.6L34.86 25.53L23.09 25.53L23.09 28.6ZM5.19 37.47L17.12 37.47L17.12 34.56L5.19 34.56L5.19 37.47ZM127.25 34.56L130.16 34.56L130.16 31.5L127.25 31.5L127.25 34.56Z"/>
+  </g>
+  <g id="Thin-L" transform="matrix(1 0 0 1 791.562 1556)">
+   <path d="M37.924 29.24L37.924 7.48L32.441 7.48L32.441 1.99L26.959 1.99L26.959-3.49L21.476-3.49L21.476-8.97L15.994-8.97L15.994-14.29L10.673-14.29L10.673-47.03L15.994-47.03L15.994-36.22L21.476-36.22L21.476-30.74L26.959-30.74L26.959-25.26L37.924-25.26L37.924-30.74L43.406-30.74L43.406-36.22L51.469-36.22L51.469-41.71L59.693-41.71L59.693-47.03L65.175-47.03L65.175-82.5L70.658-82.5L70.658-87.98L114.356-87.98L114.356-82.5L119.678-82.5L119.678-57.99L92.426-57.99L92.426-52.51L108.874-52.51L108.874-47.03L86.944-47.03L86.944-36.22L97.909-36.22L97.909-25.26L92.426-25.26L92.426-30.74L86.944-30.74L86.944-11.55L81.623-11.55L81.623-3.49L76.14-3.49L76.14 1.99L70.658 1.99L70.658 23.92L76.14 23.92L76.14 29.24L65.175 29.24L65.175 12.96L59.693 12.96L59.693 7.48L54.21 7.48L54.21 12.96L48.728 12.96L48.728 18.44L43.406 18.44L43.406 23.92L48.728 23.92L48.728 29.24L37.924 29.24ZM76.14-74.44L81.623-74.44L81.623-79.92L76.14-79.92L76.14-74.44ZM5.19 12.96L26.959 12.96L26.959 10.22L5.19 10.22L5.19 12.96ZM81.623 12.96L125.16 12.96L125.16 10.22L81.623 10.22L81.623 12.96ZM21.476 23.92L32.441 23.92L32.441 21.18L21.476 21.18L21.476 23.92ZM5.19 31.99L15.994 31.99L15.994 29.24L5.19 29.24L5.19 31.99ZM117.098 29.24L119.678 29.24L119.678 26.5L117.098 26.5L117.098 29.24Z"/>
+  </g>
+  <g id="Ultralight-L" transform="matrix(1 0 0 1 498.076 1556)">
+   <path d="M36.15 26.18L36.15 5.54L30.99 5.54L30.99 0.38L25.83 0.38L25.83-4.78L20.67-4.78L20.67-9.94L15.51-9.94L15.51-15.1L10.35-15.1L10.35-46.06L15.51-46.06L15.51-35.74L20.67-35.74L20.67-30.58L25.83-30.58L25.83-25.42L36.15-25.42L36.15-30.58L41.31-30.58L41.31-35.74L49.05-35.74L49.05-40.9L56.79-40.9L56.79-46.06L61.95-46.06L61.95-79.6L67.11-79.6L67.11-84.76L108.39-84.76L108.39-79.6L113.55-79.6L113.55-56.38L87.75-56.38L87.75-51.22L103.23-51.22L103.23-46.06L82.59-46.06L82.59-35.74L92.91-35.74L92.91-25.42L87.75-25.42L87.75-30.58L82.59-30.58L82.59-12.52L77.43-12.52L77.43-4.78L72.27-4.78L72.27 0.38L67.11 0.38L67.11 21.02L72.27 21.02L72.27 26.18L61.95 26.18L61.95 10.7L56.79 10.7L56.79 5.54L51.63 5.54L51.63 10.7L46.47 10.7L46.47 15.86L41.31 15.86L41.31 21.02L46.47 21.02L46.47 26.18L36.15 26.18ZM72.27-71.86L77.43-71.86L77.43-77.02L72.27-77.02L72.27-71.86ZM5.19 10.7L25.83 10.7L25.83 8.12L5.19 8.12L5.19 10.7ZM77.43 10.7L118.71 10.7L118.71 8.12L77.43 8.12L77.43 10.7ZM20.67 21.02L30.99 21.02L30.99 18.44L20.67 18.44L20.67 21.02ZM5.19 28.76L15.51 28.76L15.51 26.18L5.19 26.18L5.19 28.76ZM110.97 26.18L113.55 26.18L113.55 23.6L110.97 23.6L110.97 26.18Z"/>
+  </g>
+  <g id="Black-M" transform="matrix(1 0 0 1 2869.65 1126)">
+   <path d="M37.57 21.52L37.57 0.02L32.19 0.02L32.19-5.48L26.81-5.48L26.81-10.86L21.44-10.86L21.44-16.23L16.07-16.23L16.07-21.61L10.57-21.61L10.57-53.86L15.94-53.86L15.94-43.11L21.44-43.11L21.44-37.73L26.81-37.73L26.81-32.36L37.57-32.36L37.57-37.73L42.69-37.73L42.94-43.11L53.19-43.11L53.19-48.48L63.31-48.48L63.31-53.86L68.69-53.86L68.69-86.73L74.07-86.73L74.07-92.11L112.94-92.11L112.94-86.73L118.31-86.73L118.31-64.61L91.31-64.61L91.31-59.23L107.57-59.23L107.57-53.86L85.94-53.86L85.94-43.11L96.69-43.11L96.69-32.36L91.31-32.36L91.31-37.73L85.94-37.73L85.94-20.98L80.57-20.98L80.57-14.98L75.19-14.98L75.19-9.61L69.81-9.61L69.81 16.14L75.19 16.14L75.19 21.52L64.44 21.52L64.44 5.39L59.07 5.39L59.07 0.02L53.69 0.02L53.69 5.39L48.31 5.39L48.31 10.77L42.94 10.77L42.94 16.14L48.31 16.14L48.31 21.52L37.57 21.52ZM74.69-77.98L80.07-77.98L80.07-83.36L74.69-83.36L74.69-77.98ZM5.19 7.39L26.81 7.39L26.81 4.77L5.19 4.77L5.19 7.39ZM80.57 7.39L122.94 7.39L122.94 4.77L80.57 4.77L80.57 7.39ZM21.44 18.27L32.19 18.27L32.19 15.52L21.44 15.52L21.44 18.27ZM5.19 26.27L15.94 26.27L15.94 23.64L5.19 23.64L5.19 26.27ZM115.57 23.64L118.31 23.64L118.31 20.89L115.57 20.89L115.57 23.64Z"/>
+  </g>
+  <g id="Heavy-M" transform="matrix(1 0 0 1 2572.94 1126)">
+   <path d="M37.57 21.52L37.57 0.02L32.19 0.02L32.19-5.48L26.81-5.48L26.81-10.86L21.44-10.86L21.44-16.23L16.07-16.23L16.07-21.61L10.57-21.61L10.57-53.86L15.94-53.86L15.94-43.11L21.44-43.11L21.44-37.73L26.81-37.73L26.81-32.36L37.57-32.36L37.57-37.73L42.69-37.73L42.94-43.11L53.19-43.11L53.19-48.48L63.31-48.48L63.31-53.86L68.69-53.86L68.69-86.73L74.07-86.73L74.07-92.11L112.94-92.11L112.94-86.73L118.31-86.73L118.31-64.61L91.31-64.61L91.31-59.23L107.57-59.23L107.57-53.86L85.94-53.86L85.94-43.11L96.69-43.11L96.69-32.36L91.31-32.36L91.31-37.73L85.94-37.73L85.94-20.98L80.57-20.98L80.57-14.98L75.19-14.98L75.19-9.61L69.81-9.61L69.81 16.14L75.19 16.14L75.19 21.52L64.44 21.52L64.44 5.39L59.07 5.39L59.07 0.02L53.69 0.02L53.69 5.39L48.31 5.39L48.31 10.77L42.94 10.77L42.94 16.14L48.31 16.14L48.31 21.52L37.57 21.52ZM74.69-77.98L80.07-77.98L80.07-83.36L74.69-83.36L74.69-77.98ZM5.19 7.39L26.81 7.39L26.81 4.77L5.19 4.77L5.19 7.39ZM80.57 7.39L122.94 7.39L122.94 4.77L80.57 4.77L80.57 7.39ZM21.44 18.27L32.19 18.27L32.19 15.52L21.44 15.52L21.44 18.27ZM5.19 26.27L15.94 26.27L15.94 23.64L5.19 23.64L5.19 26.27ZM115.57 23.64L118.31 23.64L118.31 20.89L115.57 20.89L115.57 23.64Z"/>
+  </g>
+  <g id="Bold-M" transform="matrix(1 0 0 1 2276.23 1126)">
+   <path d="M37.57 21.52L37.57 0.02L32.19 0.02L32.19-5.48L26.82-5.48L26.82-10.86L21.44-10.86L21.44-16.23L16.07-16.23L16.07-21.61L10.57-21.61L10.57-53.86L15.94-53.86L15.94-43.11L21.44-43.11L21.44-37.73L26.82-37.73L26.82-32.36L37.57-32.36L37.57-37.73L42.69-37.73L42.94-43.11L53.19-43.11L53.19-48.48L63.32-48.48L63.32-53.86L68.69-53.86L68.69-86.73L74.07-86.73L74.07-92.11L112.94-92.11L112.94-86.73L118.32-86.73L118.32-64.61L91.32-64.61L91.32-59.23L107.57-59.23L107.57-53.86L85.94-53.86L85.94-43.11L96.69-43.11L96.69-32.36L91.32-32.36L91.32-37.73L85.94-37.73L85.94-20.98L80.57-20.98L80.57-14.98L75.19-14.98L75.19-9.61L69.82-9.61L69.82 16.14L75.19 16.14L75.19 21.52L64.44 21.52L64.44 5.39L59.07 5.39L59.07 0.02L53.69 0.02L53.69 5.39L48.32 5.39L48.32 10.77L42.94 10.77L42.94 16.14L48.32 16.14L48.32 21.52L37.57 21.52ZM74.69-77.98L80.07-77.98L80.07-83.36L74.69-83.36L74.69-77.98ZM5.19 7.39L26.82 7.39L26.82 4.77L5.19 4.77L5.19 7.39ZM80.57 7.39L122.94 7.39L122.94 4.77L80.57 4.77L80.57 7.39ZM21.44 18.27L32.19 18.27L32.19 15.52L21.44 15.52L21.44 18.27ZM5.19 26.27L15.94 26.27L15.94 23.64L5.19 23.64L5.19 26.27ZM115.57 23.64L118.32 23.64L118.32 20.89L115.57 20.89L115.57 23.64Z"/>
+  </g>
+  <g id="Semibold-M" transform="matrix(1 0 0 1 1981.14 1126)">
+   <path d="M36.57 20.77L36.57-0.11L31.44-0.11L31.44-5.36L26.19-5.36L26.19-10.61L20.94-10.61L20.94-15.86L15.69-15.86L15.69-20.98L10.44-20.98L10.44-52.23L15.69-52.23L15.69-41.86L20.94-41.86L20.94-36.61L26.19-36.61L26.19-31.48L36.57-31.48L36.57-36.61L41.69-36.61L41.82-41.86L50.82-41.86L50.82-47.11L59.82-47.11L59.82-52.23L65.07-52.23L65.07-84.98L70.32-84.98L70.32-90.23L109.69-90.23L109.69-84.98L114.82-84.98L114.82-62.73L88.69-62.73L88.69-57.48L104.44-57.48L104.44-52.23L83.44-52.23L83.44-41.86L93.94-41.86L93.94-31.48L88.69-31.48L88.69-36.61L83.44-36.61L83.44-19.61L78.32-19.61L78.32-12.86L73.07-12.86L73.07-7.73L67.82-7.73L67.82 15.52L73.07 15.52L73.07 20.77L62.69 20.77L62.69 5.14L57.44 5.14L57.44-0.11L52.19-0.11L52.19 5.14L47.07 5.14L47.07 10.39L41.82 10.39L41.82 15.52L47.07 15.52L47.07 20.77L36.57 20.77ZM72.82-76.73L78.07-76.73L78.07-81.98L72.82-81.98L72.82-76.73ZM5.19 6.27L26.19 6.27L26.19 3.64L5.19 3.64L5.19 6.27ZM78.32 6.27L119.69 6.27L119.69 3.64L78.32 3.64L78.32 6.27ZM20.94 16.77L31.44 16.77L31.44 14.14L20.94 14.14L20.94 16.77ZM5.19 24.52L15.69 24.52L15.69 22.02L5.19 22.02L5.19 24.52ZM112.19 22.02L114.82 22.02L114.82 19.27L112.19 19.27L112.19 22.02Z"/>
+  </g>
+  <g id="Medium-M" transform="matrix(1 0 0 1 1685.81 1126)">
+   <path d="M35.69 20.14L35.69-0.23L30.57-0.23L30.57-5.23L25.57-5.23L25.57-10.36L20.44-10.36L20.44-15.48L15.32-15.48L15.32-20.61L10.19-20.61L10.19-51.11L15.32-51.11L15.32-40.98L20.44-40.98L20.44-35.86L25.57-35.86L25.57-30.73L35.69-30.73L35.69-35.86L40.69-35.86L40.82-40.98L48.94-40.98L48.94-45.98L57.07-45.98L57.07-51.11L62.19-51.11L62.19-83.73L67.32-83.73L67.32-88.73L106.94-88.73L106.94-83.73L112.07-83.73L112.07-61.23L86.57-61.23L86.57-56.23L101.94-56.23L101.94-51.11L81.57-51.11L81.57-40.98L91.69-40.98L91.69-30.73L86.57-30.73L86.57-35.86L81.57-35.86L81.57-18.48L76.44-18.48L76.44-11.36L71.32-11.36L71.32-6.23L66.19-6.23L66.19 15.14L71.32 15.14L71.32 20.14L61.19 20.14L61.19 4.89L56.07 4.89L56.07-0.23L50.94-0.23L50.94 4.89L45.94 4.89L45.94 10.02L40.82 10.02L40.82 15.14L45.94 15.14L45.94 20.14L35.69 20.14ZM71.19-75.86L76.32-75.86L76.32-80.98L71.19-80.98L71.19-75.86ZM5.19 5.39L25.57 5.39L25.57 2.89L5.19 2.89L5.19 5.39ZM76.44 5.39L116.94 5.39L116.94 2.89L76.44 2.89L76.44 5.39ZM20.44 15.64L30.57 15.64L30.57 13.02L20.44 13.02L20.44 15.64ZM5.19 23.27L15.32 23.27L15.32 20.64L5.19 20.64L5.19 23.27ZM109.57 20.64L112.07 20.64L112.07 18.14L109.57 18.14L109.57 20.64Z"/>
+  </g>
+  <g id="Regular-M" transform="matrix(1 0 0 1 1389.97 1126)">
+   <path d="M35.19 19.77L35.19-0.23L30.19-0.23L30.19-5.23L25.19-5.23L25.19-10.23L20.19-10.23L20.19-15.23L15.19-15.23L15.19-20.23L10.19-20.23L10.19-50.23L15.19-50.23L15.19-40.23L20.19-40.23L20.19-35.23L25.19-35.23L25.19-30.23L35.19-30.23L35.19-35.23L40.19-35.23L40.19-40.23L47.69-40.23L47.69-45.23L55.19-45.23L55.19-50.23L60.19-50.23L60.19-82.73L65.19-82.73L65.19-87.73L105.19-87.73L105.19-82.73L110.19-82.73L110.19-60.23L85.19-60.23L85.19-55.23L100.19-55.23L100.19-50.23L80.19-50.23L80.19-40.23L90.19-40.23L90.19-30.23L85.19-30.23L85.19-35.23L80.19-35.23L80.19-17.73L75.19-17.73L75.19-10.23L70.19-10.23L70.19-5.23L65.19-5.23L65.19 14.77L70.19 14.77L70.19 19.77L60.19 19.77L60.19 4.77L55.19 4.77L55.19-0.23L50.19-0.23L50.19 4.77L45.19 4.77L45.19 9.77L40.19 9.77L40.19 14.77L45.19 14.77L45.19 19.77L35.19 19.77ZM70.19-75.23L75.19-75.23L75.19-80.23L70.19-80.23L70.19-75.23ZM5.19 4.77L25.19 4.77L25.19 2.27L5.19 2.27L5.19 4.77ZM75.19 4.77L115.19 4.77L115.19 2.27L75.19 2.27L75.19 4.77ZM20.19 14.77L30.19 14.77L30.19 12.27L20.19 12.27L20.19 14.77ZM5.19 22.27L15.19 22.27L15.19 19.77L5.19 19.77L5.19 22.27ZM107.69 19.77L110.19 19.77L110.19 17.27L107.69 17.27L107.69 19.77Z"/>
+  </g>
+  <g id="Light-M" transform="matrix(1 0 0 1 1097.51 1126)">
+   <path d="M32.81 15.77L32.81-2.73L28.19-2.73L28.19-7.36L23.69-7.36L23.69-11.98L19.06-11.98L19.06-16.61L14.44-16.61L14.44-21.23L9.81-21.23L9.81-48.86L14.44-48.86L14.44-39.61L19.06-39.61L19.06-34.98L23.69-34.98L23.69-30.48L32.81-30.48L32.81-34.98L37.44-34.98L37.44-39.61L44.44-39.61L44.44-44.23L51.31-44.23L51.31-48.86L55.94-48.86L55.94-78.86L60.56-78.86L60.56-83.48L97.44-83.48L97.44-78.86L102.06-78.86L102.06-58.11L79.06-58.11L79.06-53.48L92.81-53.48L92.81-48.86L74.44-48.86L74.44-39.61L83.69-39.61L83.69-30.48L79.06-30.48L79.06-34.98L74.44-34.98L74.44-18.86L69.81-18.86L69.81-11.98L65.19-11.98L65.19-7.36L60.56-7.36L60.56 11.14L65.19 11.14L65.19 15.77L55.94 15.77L55.94 1.89L51.31 1.89L51.31-2.73L46.69-2.73L46.69 1.89L42.06 1.89L42.06 6.52L37.44 6.52L37.44 11.14L42.06 11.14L42.06 15.77L32.81 15.77ZM65.19-71.98L69.81-71.98L69.81-76.61L65.19-76.61L65.19-71.98ZM5.19 1.89L23.69 1.89L23.69-0.48L5.19-0.48L5.19 1.89ZM69.81 1.89L106.69 1.89L106.69-0.48L69.81-0.48L69.81 1.89ZM19.06 11.14L28.19 11.14L28.19 8.77L19.06 8.77L19.06 11.14ZM5.19 18.02L14.44 18.02L14.44 15.77L5.19 15.77L5.19 18.02ZM99.81 15.77L102.06 15.77L102.06 13.39L99.81 13.39L99.81 15.77Z"/>
+  </g>
+  <g id="Thin-M" transform="matrix(1 0 0 1 805.047 1126)">
+   <path d="M30.565 11.64L30.565-5.23L26.315-5.23L26.315-9.48L22.065-9.48L22.065-13.73L17.815-13.73L17.815-17.98L13.565-17.98L13.565-22.11L9.44-22.11L9.44-47.48L13.565-47.48L13.565-39.11L17.815-39.11L17.815-34.86L22.065-34.86L22.065-30.61L30.565-30.61L30.565-34.86L34.815-34.86L34.815-39.11L41.065-39.11L41.065-43.36L47.44-43.36L47.44-47.48L51.69-47.48L51.69-74.98L55.94-74.98L55.94-79.23L89.815-79.23L89.815-74.98L93.94-74.98L93.94-55.98L72.815-55.98L72.815-51.73L85.565-51.73L85.565-47.48L68.565-47.48L68.565-39.11L77.065-39.11L77.065-30.61L72.815-30.61L72.815-34.86L68.565-34.86L68.565-19.98L64.44-19.98L64.44-13.73L60.19-13.73L60.19-9.48L55.94-9.48L55.94 7.52L60.19 7.52L60.19 11.64L51.69 11.64L51.69-0.98L47.44-0.98L47.44-5.23L43.19-5.23L43.19-0.98L38.94-0.98L38.94 3.27L34.815 3.27L34.815 7.52L38.94 7.52L38.94 11.64L30.565 11.64ZM60.19-68.73L64.44-68.73L64.44-72.98L60.19-72.98L60.19-68.73ZM5.19-0.98L22.065-0.98L22.065-3.11L5.19-3.11L5.19-0.98ZM64.44-0.98L98.19-0.98L98.19-3.11L64.44-3.11L64.44-0.98ZM17.815 7.52L26.315 7.52L26.315 5.39L17.815 5.39L17.815 7.52ZM5.19 13.77L13.565 13.77L13.565 11.64L5.19 11.64L5.19 13.77ZM91.94 11.64L93.94 11.64L93.94 9.52L91.94 9.52L91.94 11.64Z"/>
+  </g>
+  <g id="Ultralight-M" transform="matrix(1 0 0 1 510.836 1126)">
+   <path d="M29.19 9.27L29.19-6.73L25.19-6.73L25.19-10.73L21.19-10.73L21.19-14.73L17.19-14.73L17.19-18.73L13.19-18.73L13.19-22.73L9.19-22.73L9.19-46.73L13.19-46.73L13.19-38.73L17.19-38.73L17.19-34.73L21.19-34.73L21.19-30.73L29.19-30.73L29.19-34.73L33.19-34.73L33.19-38.73L39.19-38.73L39.19-42.73L45.19-42.73L45.19-46.73L49.19-46.73L49.19-72.73L53.19-72.73L53.19-76.73L85.19-76.73L85.19-72.73L89.19-72.73L89.19-54.73L69.19-54.73L69.19-50.73L81.19-50.73L81.19-46.73L65.19-46.73L65.19-38.73L73.19-38.73L73.19-30.73L69.19-30.73L69.19-34.73L65.19-34.73L65.19-20.73L61.19-20.73L61.19-14.73L57.19-14.73L57.19-10.73L53.19-10.73L53.19 5.27L57.19 5.27L57.19 9.27L49.19 9.27L49.19-2.73L45.19-2.73L45.19-6.73L41.19-6.73L41.19-2.73L37.19-2.73L37.19 1.27L33.19 1.27L33.19 5.27L37.19 5.27L37.19 9.27L29.19 9.27ZM57.19-66.73L61.19-66.73L61.19-70.73L57.19-70.73L57.19-66.73ZM5.19-2.73L21.19-2.73L21.19-4.73L5.19-4.73L5.19-2.73ZM61.19-2.73L93.19-2.73L93.19-4.73L61.19-4.73L61.19-2.73ZM17.19 5.27L25.19 5.27L25.19 3.27L17.19 3.27L17.19 5.27ZM5.19 11.27L13.19 11.27L13.19 9.27L5.19 9.27L5.19 11.27ZM87.19 9.27L89.19 9.27L89.19 7.27L87.19 7.27L87.19 9.27Z"/>
+  </g>
+  <g id="Black-S" transform="matrix(1 0 0 1 2883.85 696)">
+   <path d="M29.75 8.756L29.75-7.519L25.71-7.519L25.71-11.662L21.56-11.662L21.56-15.804L17.52-15.804L17.52-19.946L13.38-19.946L13.38-23.989L9.24-23.989L9.24-48.45L13.38-48.45L13.38-40.26L17.42-40.26L17.42-36.217L21.56-36.217L21.56-32.174L29.75-32.174L29.75-36.217L33.59-36.217L33.79-40.26L41.68-40.26L41.68-44.407L49.48-44.407L49.48-48.45L53.52-48.45L53.52-73.398L57.56-73.398L57.56-77.445L87.05-77.445L87.05-73.398L91.09-73.398L91.09-56.635L70.58-56.635L70.58-52.592L82.91-52.592L82.91-48.45L66.54-48.45L66.54-40.26L74.62-40.26L74.62-32.174L70.58-32.174L70.58-36.217L66.54-36.217L66.54-23.596L62.4-23.596L62.4-19.056L58.35-19.056L58.35-15.013L54.21-15.013L54.21 4.713L58.35 4.713L58.35 8.756L50.16 8.756L50.16-3.477L46.12-3.477L46.12-7.519L41.98-7.519L41.98-3.477L37.94-3.477L37.94 0.666L33.79 0.666L33.79 4.713L37.94 4.713L37.94 8.756L29.75 8.756ZM57.96-66.694L62-66.694L62-70.837L57.96-70.837L57.96-66.694ZM5.19-1.895L21.56-1.895L21.56-3.869L5.19-3.869L5.19-1.895ZM62.4-1.895L94.55-1.895L94.55-3.869L62.4-3.869L62.4-1.895ZM17.52 6.389L25.71 6.389L25.71 4.316L17.52 4.316L17.52 6.389ZM5.19 12.501L13.28 12.501L13.28 10.432L5.19 10.432L5.19 12.501ZM89.02 10.432L91.09 10.432L91.09 8.358L89.02 8.358L89.02 10.432Z"/>
+  </g>
+  <g id="Heavy-S" transform="matrix(1 0 0 1 2587.13 696)">
+   <path d="M29.75 8.756L29.75-7.519L25.71-7.519L25.71-11.662L21.56-11.662L21.56-15.804L17.52-15.804L17.52-19.946L13.38-19.946L13.38-23.989L9.24-23.989L9.24-48.45L13.38-48.45L13.38-40.26L17.42-40.26L17.42-36.217L21.56-36.217L21.56-32.174L29.75-32.174L29.75-36.217L33.59-36.217L33.79-40.26L41.68-40.26L41.68-44.407L49.48-44.407L49.48-48.45L53.52-48.45L53.52-73.398L57.56-73.398L57.56-77.445L87.05-77.445L87.05-73.398L91.09-73.398L91.09-56.635L70.58-56.635L70.58-52.592L82.91-52.592L82.91-48.45L66.54-48.45L66.54-40.26L74.62-40.26L74.62-32.174L70.58-32.174L70.58-36.217L66.54-36.217L66.54-23.596L62.4-23.596L62.4-19.056L58.35-19.056L58.35-15.013L54.21-15.013L54.21 4.713L58.35 4.713L58.35 8.756L50.16 8.756L50.16-3.477L46.12-3.477L46.12-7.519L41.98-7.519L41.98-3.477L37.94-3.477L37.94 0.666L33.79 0.666L33.79 4.713L37.94 4.713L37.94 8.756L29.75 8.756ZM57.96-66.694L62-66.694L62-70.837L57.96-70.837L57.96-66.694ZM5.19-1.895L21.56-1.895L21.56-3.869L5.19-3.869L5.19-1.895ZM62.4-1.895L94.55-1.895L94.55-3.869L62.4-3.869L62.4-1.895ZM17.52 6.389L25.71 6.389L25.71 4.316L17.52 4.316L17.52 6.389ZM5.19 12.501L13.28 12.501L13.28 10.432L5.19 10.432L5.19 12.501ZM89.02 10.432L91.09 10.432L91.09 8.358L89.02 8.358L89.02 10.432Z"/>
+  </g>
+  <g id="Bold-S" transform="matrix(1 0 0 1 2290.42 696)">
+   <path d="M29.75 8.756L29.75-7.519L25.71-7.519L25.71-11.662L21.57-11.662L21.57-15.804L17.52-15.804L17.52-19.946L13.38-19.946L13.38-23.989L9.24-23.989L9.24-48.45L13.38-48.45L13.38-40.26L17.42-40.26L17.42-36.217L21.57-36.217L21.57-32.174L29.75-32.174L29.75-36.217L33.59-36.217L33.79-40.26L41.68-40.26L41.68-44.407L49.48-44.407L49.48-48.45L53.52-48.45L53.52-73.398L57.56-73.398L57.56-77.445L87.05-77.445L87.05-73.398L91.09-73.398L91.09-56.635L70.58-56.635L70.58-52.592L82.91-52.592L82.91-48.45L66.54-48.45L66.54-40.26L74.62-40.26L74.62-32.174L70.58-32.174L70.58-36.217L66.54-36.217L66.54-23.596L62.4-23.596L62.4-19.056L58.35-19.056L58.35-15.013L54.21-15.013L54.21 4.713L58.35 4.713L58.35 8.756L50.16 8.756L50.16-3.477L46.12-3.477L46.12-7.519L41.98-7.519L41.98-3.477L37.94-3.477L37.94 0.666L33.79 0.666L33.79 4.713L37.94 4.713L37.94 8.756L29.75 8.756ZM57.96-66.694L62-66.694L62-70.837L57.96-70.837L57.96-66.694ZM5.19-1.895L21.57-1.895L21.57-3.869L5.19-3.869L5.19-1.895ZM62.4-1.895L94.55-1.895L94.55-3.869L62.4-3.869L62.4-1.895ZM17.52 6.389L25.71 6.389L25.71 4.316L17.52 4.316L17.52 6.389ZM5.19 12.501L13.28 12.501L13.28 10.432L5.19 10.432L5.19 12.501ZM89.02 10.432L91.09 10.432L91.09 8.358L89.02 8.358L89.02 10.432Z"/>
+  </g>
+  <g id="Semibold-S" transform="matrix(1 0 0 1 1994.99 696)">
+   <path d="M28.96 8.167L28.96-7.616L25.02-7.616L25.02-11.659L21.08-11.659L21.08-15.602L17.13-15.602L17.13-19.551L13.09-19.551L13.09-23.494L9.14-23.494L9.14-47.164L13.09-47.164L13.09-39.272L17.13-39.272L17.13-35.329L21.08-35.329L21.08-31.48L28.96-31.48L28.96-35.329L32.81-35.329L32.91-39.272L39.81-39.272L39.81-43.221L46.72-43.221L46.72-47.164L50.66-47.164L50.66-72.018L54.61-72.018L54.61-75.961L84.39-75.961L84.39-72.018L88.34-72.018L88.34-55.15L68.51-55.15L68.51-51.207L80.45-51.207L80.45-47.164L64.57-47.164L64.57-39.272L72.46-39.272L72.46-31.386L68.51-31.386L68.51-35.329L64.57-35.329L64.57-22.505L60.62-22.505L60.62-17.477L56.68-17.477L56.68-13.534L52.63-13.534L52.63 4.219L56.68 4.219L56.68 8.167L48.79 8.167L48.79-3.668L44.84-3.668L44.84-7.616L40.9-7.616L40.9-3.668L36.85-3.668L36.85 0.275L32.91 0.275L32.91 4.219L36.95 4.219L36.95 8.167L28.96 8.167ZM56.48-65.707L60.43-65.707L60.43-69.75L56.48-69.75L56.48-65.707ZM5.19-2.783L21.08-2.783L21.08-4.752L5.19-4.752L5.19-2.783ZM60.62-2.783L91.99-2.783L91.99-4.752L60.62-4.752L60.62-2.783ZM17.13 5.208L25.12 5.208L25.12 3.234L17.13 3.234L17.13 5.208ZM5.19 11.126L13.09 11.126L13.09 9.152L5.19 9.152L5.19 11.126ZM86.36 9.152L88.34 9.152L88.34 7.083L86.36 7.083L86.36 9.152Z"/>
+  </g>
+  <g id="Medium-S" transform="matrix(1 0 0 1 1699.37 696)">
+   <path d="M28.27 7.666L28.27-7.719L24.42-7.719L24.42-11.563L20.57-11.563L20.57-15.407L16.73-15.407L16.73-19.256L12.79-19.256L12.79-23.2L8.94-23.2L8.94-46.278L12.79-46.278L12.79-38.585L16.73-38.585L16.73-34.741L20.57-34.741L20.57-30.892L28.27-30.892L28.27-34.741L32.11-34.741L32.11-38.585L38.33-38.585L38.33-42.434L44.54-42.434L44.54-46.278L48.39-46.278L48.39-70.937L52.23-70.937L52.23-74.781L82.31-74.781L82.31-71.032L86.16-71.032L86.16-54.07L66.83-54.07L66.83-50.226L78.47-50.226L78.47-46.278L62.98-46.278L62.98-38.585L70.68-38.585L70.68-30.892L66.83-30.892L66.83-34.741L62.98-34.741L62.98-21.623L59.14-21.623L59.14-16.198L55.29-16.198L55.29-12.354L51.34-12.354L51.34 3.922L55.29 3.922L55.29 7.666L47.6 7.666L47.6-3.871L43.75-3.871L43.75-7.719L39.91-7.719L39.91-3.771L35.96-3.771L35.96 0.073L32.11 0.073L32.11 3.922L36.06 3.922L36.06 7.666L28.27 7.666ZM55.19-65.02L59.04-65.02L59.04-68.963L55.19-68.963L55.19-65.02ZM5.19-3.478L20.57-3.478L20.57-5.352L5.19-5.352L5.19-3.478ZM59.14-3.478L89.81-3.478L89.81-5.352L59.14-5.352L59.14-3.478ZM16.73 4.314L24.42 4.314L24.42 2.345L16.73 2.345L16.73 4.314ZM5.19 10.133L12.79 10.133L12.79 8.064L5.19 8.064L5.19 10.133ZM84.29 8.064L86.16 8.064L86.16 6.189L84.29 6.189L84.29 8.064Z"/>
+  </g>
+  <g id="Regular-S" transform="matrix(1 0 0 1 1403.35 696)">
+   <path d="M27.87 7.376L27.87-7.716L24.12-7.716L24.12-11.56L20.28-11.56L20.28-15.31L16.53-15.31L16.53-19.154L12.69-19.154L12.69-22.903L8.94-22.903L8.94-45.588L12.69-45.588L12.69-37.99L16.53-37.99L16.53-34.246L20.28-34.246L20.28-30.496L27.87-30.496L27.87-34.246L31.62-34.246L31.62-37.99L37.34-37.99L37.34-41.839L43.06-41.839L43.06-45.588L46.81-45.588L46.81-70.243L50.56-70.243L50.56-73.992L80.93-73.992L80.93-70.243L84.68-70.243L84.68-53.182L65.75-53.182L65.75-49.432L77.09-49.432L77.09-45.588L62-45.588L62-37.99L69.5-37.99L69.5-30.496L65.75-30.496L65.75-34.246L62-34.246L62-21.028L58.15-21.028L58.15-15.31L54.4-15.31L54.4-11.56L50.56-11.56L50.56 3.626L54.4 3.626L54.4 7.376L46.81 7.376L46.81-3.967L43.06-3.967L43.06-7.716L39.22-7.716L39.22-3.967L35.47-3.967L35.47-0.118L31.62-0.118L31.62 3.626L35.47 3.626L35.47 7.376L27.87 7.376ZM54.4-64.524L58.15-64.524L58.15-68.368L54.4-68.368L54.4-64.524ZM5.19-3.967L20.28-3.967L20.28-5.842L5.19-5.842L5.19-3.967ZM58.15-3.967L88.43-3.967L88.43-5.842L58.15-5.842L58.15-3.967ZM16.53 3.626L24.12 3.626L24.12 1.752L16.53 1.752L16.53 3.626ZM5.19 9.35L12.69 9.35L12.69 7.376L5.19 7.376L5.19 9.35ZM82.81 7.376L84.68 7.376L84.68 5.501L82.81 5.501L82.81 7.376Z"/>
+  </g>
+  <g id="Light-S" transform="matrix(1 0 0 1 1109.99 696)">
+   <path d="M25.99 4.213L25.99-9.69L22.54-9.69L22.54-13.241L19.09-13.241L19.09-16.692L15.64-16.692L15.64-20.143L12.09-20.143L12.09-23.694L8.64-23.694L8.64-44.504L12.09-44.504L12.09-37.602L15.54-37.602L15.54-34.052L19.09-34.052L19.09-30.7L25.99-30.7L25.99-34.052L29.54-34.052L29.54-37.503L34.77-37.503L34.77-41.053L40-41.053L40-44.504L43.45-44.504L43.45-67.19L46.9-67.19L46.9-70.641L74.81-70.641L74.81-67.19L78.26-67.19L78.26-51.511L60.91-51.511L60.91-48.055L71.26-48.055L71.26-44.504L57.36-44.504L57.36-37.602L64.36-37.602L64.36-30.6L60.91-30.6L60.91-34.052L57.36-34.052L57.36-21.923L53.9-21.923L53.9-16.692L50.45-16.692L50.45-13.241L46.9-13.241L46.9 0.762L50.45 0.762L50.45 4.213L43.45 4.213L43.45-6.239L40-6.239L40-9.69L36.45-9.69L36.45-6.239L33-6.239L33-2.689L29.45-2.689L29.45 0.762L33 0.762L33 4.213L25.99 4.213ZM50.45-61.963L53.9-61.963L53.9-65.514L50.45-65.514L50.45-61.963ZM5.19-6.239L19.09-6.239L19.09-8.015L5.19-8.015L5.19-6.239ZM53.9-6.239L81.72-6.239L81.72-8.015L53.9-8.015L53.9-6.239ZM15.64 0.762L22.54 0.762L22.54-1.013L15.64-1.013L15.64 0.762ZM5.19 5.989L12.09 5.989L12.09 4.213L5.19 4.213L5.19 5.989ZM76.59 4.213L78.26 4.213L78.26 2.438L76.59 2.438L76.59 4.213Z"/>
+  </g>
+  <g id="Thin-S" transform="matrix(1 0 0 1 816.634 696)">
+   <path d="M24.225 0.961L24.225-11.665L21.068-11.665L21.068-14.917L17.815-14.917L17.815-18.074L14.658-18.074L14.658-21.232L11.406-21.232L11.406-24.385L8.347-24.385L8.347-43.42L11.5-43.42L11.5-37.11L14.658-37.11L14.658-33.952L17.815-33.952L17.815-30.795L24.225-30.795L24.225-33.952L27.378-33.952L27.378-37.11L32.112-37.11L32.112-40.263L36.946-40.263L36.946-43.42L40.103-43.42L40.103-64.131L43.261-64.131L43.261-67.289L68.706-67.289L68.706-64.131L71.859-64.131L71.859-49.929L55.981-49.929L55.981-46.677L65.548-46.677L65.548-43.42L52.828-43.42L52.828-37.11L59.139-37.11L59.139-30.795L55.981-30.795L55.981-33.952L52.828-33.952L52.828-22.808L49.671-22.808L49.671-18.074L46.513-18.074L46.513-14.917L43.261-14.917L43.261-2.097L46.513-2.097L46.513 0.961L40.103 0.961L40.103-8.507L36.946-8.507L36.946-11.665L33.793-11.665L33.793-8.507L30.536-8.507L30.536-5.354L27.378-5.354L27.378-2.097L30.536-2.097L30.536 0.961L24.225 0.961ZM46.513-59.397L49.671-59.397L49.671-62.654L46.513-62.654L46.513-59.397ZM5.19-8.507L17.815-8.507L17.815-10.088L5.19-10.088L5.19-8.507ZM49.671-8.507L75.016-8.507L75.016-10.088L49.671-10.088L49.671-8.507ZM14.658-2.097L21.068-2.097L21.068-3.674L14.658-3.674L14.658-2.097ZM5.19 2.637L11.406 2.637L11.406 0.961L5.19 0.961L5.19 2.637ZM70.382 0.961L71.859 0.961L71.859-0.62L70.382-0.62L70.382 0.961Z"/>
+  </g>
+  <g id="Ultralight-S" transform="matrix(1 0 0 1 521.897 696)">
+   <path d="M23.136-0.911L23.136-12.846L20.178-12.846L20.178-15.904L17.12-15.904L17.12-18.862L14.166-18.862L14.166-21.921L11.107-21.921L11.107-24.875L8.149-24.875L8.149-42.826L11.107-42.826L11.107-36.809L14.166-36.809L14.166-33.85L17.12-33.85L17.12-30.892L23.136-30.892L23.136-33.85L26.095-33.85L26.095-36.809L30.635-36.809L30.635-39.867L35.17-39.867L35.17-42.826L38.129-42.826L38.129-62.354L41.088-62.354L41.088-65.312L65.151-65.312L65.151-62.354L68.109-62.354L68.109-48.843L53.122-48.843L53.122-45.884L62.093-45.884L62.093-42.826L50.163-42.826L50.163-36.809L56.08-36.809L56.08-30.892L53.122-30.892L53.122-33.85L50.163-33.85L50.163-23.398L47.105-23.398L47.105-18.862L44.146-18.862L44.146-15.904L41.088-15.904L41.088-3.87L44.146-3.87L44.146-0.911L38.129-0.911L38.129-9.887L35.17-9.887L35.17-12.846L32.112-12.846L32.112-9.887L29.153-9.887L29.153-6.829L26.095-6.829L26.095-3.87L29.153-3.87L29.153-0.911L23.136-0.911ZM44.146-57.819L47.105-57.819L47.105-60.877L44.146-60.877L44.146-57.819ZM5.19-9.887L17.12-9.887L17.12-11.364L5.19-11.364L5.19-9.887ZM47.105-9.887L71.068-9.887L71.068-11.364L47.105-11.364L47.105-9.887ZM14.166-3.87L20.178-3.87L20.178-5.352L14.166-5.352L14.166-3.87ZM5.19 0.665L11.107 0.665L11.107-0.911L5.19-0.911L5.19 0.665ZM66.632-0.911L68.109-0.911L68.109-2.388L66.632-2.388L66.632-0.911Z"/>
+  </g>
+ </g>
+</svg>
diff --git a/ios/chrome/browser/ui/icons/symbol_names.h b/ios/chrome/browser/ui/icons/symbol_names.h
index a5fe06a..96566ec 100644
--- a/ios/chrome/browser/ui/icons/symbol_names.h
+++ b/ios/chrome/browser/ui/icons/symbol_names.h
@@ -49,6 +49,7 @@
 extern NSString* const kShieldSymbol;
 extern NSString* const kCloudSlashSymbol;
 extern NSString* const kCloudAndArrowUpSymbol;
+extern NSString* const kDinoSymbol;
 
 // Custom symbol names which can be configured with a color palette. iOS 15+
 // only.
@@ -94,6 +95,7 @@
 extern NSString* const kCreditCardSymbol;
 extern NSString* const kMicrophoneFillSymbol;
 extern NSString* const kMicrophoneSymbol;
+extern NSString* const kMagnifyingglassSymbol;
 extern NSString* const kEllipsisCircleFillSymbol;
 extern NSString* const kPinSlashSymbol;
 extern NSString* const kSettingsSymbol;
diff --git a/ios/chrome/browser/ui/icons/symbol_names.mm b/ios/chrome/browser/ui/icons/symbol_names.mm
index 6ea8097..bed26e9 100644
--- a/ios/chrome/browser/ui/icons/symbol_names.mm
+++ b/ios/chrome/browser/ui/icons/symbol_names.mm
@@ -46,6 +46,7 @@
 NSString* const kShieldSymbol = @"shield";
 NSString* const kCloudSlashSymbol = @"cloud_slash";
 NSString* const kCloudAndArrowUpSymbol = @"cloud_and_arrow_up";
+NSString* const kDinoSymbol = @"dino";
 
 // Custom symbol names which can be configured with a color palette.
 NSString* const kIncognitoCircleFillSymbol = @"incognito_circle_fill";
@@ -86,6 +87,7 @@
 NSString* const kCreditCardSymbol = @"creditcard";
 NSString* const kMicrophoneFillSymbol = @"mic.fill";
 NSString* const kMicrophoneSymbol = @"mic";
+NSString* const kMagnifyingglassSymbol = @"magnifyingglass";
 NSString* const kEllipsisCircleFillSymbol = @"ellipsis.circle.fill";
 NSString* const kPinSlashSymbol = @"pin.slash";
 NSString* const kSettingsSymbol = @"gearshape";
diff --git a/ios/chrome/browser/ui/location_bar/BUILD.gn b/ios/chrome/browser/ui/location_bar/BUILD.gn
index 2333db2..3fb18af 100644
--- a/ios/chrome/browser/ui/location_bar/BUILD.gn
+++ b/ios/chrome/browser/ui/location_bar/BUILD.gn
@@ -28,6 +28,7 @@
     "resources:location_bar_connection_info",
     "resources:location_bar_connection_offline",
     "resources:location_bar_connection_secure",
+    "resources:location_bar_magnifyingglass",
     "resources:location_bar_share",
     "resources:location_bar_voice",
     "//base",
diff --git a/ios/chrome/browser/ui/location_bar/resources/BUILD.gn b/ios/chrome/browser/ui/location_bar/resources/BUILD.gn
index f44eacb0..955135e 100644
--- a/ios/chrome/browser/ui/location_bar/resources/BUILD.gn
+++ b/ios/chrome/browser/ui/location_bar/resources/BUILD.gn
@@ -59,3 +59,11 @@
     "location_bar_connection_info.imageset/location_bar_connection_info@3x.png",
   ]
 }
+
+imageset("location_bar_magnifyingglass") {
+  sources = [
+    "location_bar_magnifyingglass.imageset/Contents.json",
+    "location_bar_magnifyingglass.imageset/location_bar_magnifyingglass@2x.png",
+    "location_bar_magnifyingglass.imageset/location_bar_magnifyingglass@3x.png",
+  ]
+}
diff --git a/ios/chrome/browser/ui/location_bar/resources/location_bar_magnifyingglass.imageset/Contents.json b/ios/chrome/browser/ui/location_bar/resources/location_bar_magnifyingglass.imageset/Contents.json
new file mode 100644
index 0000000..7aa71c2
--- /dev/null
+++ b/ios/chrome/browser/ui/location_bar/resources/location_bar_magnifyingglass.imageset/Contents.json
@@ -0,0 +1,19 @@
+{
+    "images": [
+        {
+            "idiom": "universal",
+            "scale": "2x",
+            "filename": "location_bar_magnifyingglass@2x.png"
+        },
+        {
+            "idiom": "universal",
+            "scale": "3x",
+            "filename": "location_bar_magnifyingglass@3x.png"
+        }
+    ],
+    "info": {
+        "version": 1,
+        "author": "xcode"
+    }
+}
+
diff --git a/ios/chrome/browser/ui/location_bar/resources/location_bar_magnifyingglass.imageset/location_bar_magnifyingglass@2x.png b/ios/chrome/browser/ui/location_bar/resources/location_bar_magnifyingglass.imageset/location_bar_magnifyingglass@2x.png
new file mode 100644
index 0000000..2e2b0c03
--- /dev/null
+++ b/ios/chrome/browser/ui/location_bar/resources/location_bar_magnifyingglass.imageset/location_bar_magnifyingglass@2x.png
Binary files differ
diff --git a/ios/chrome/browser/ui/location_bar/resources/location_bar_magnifyingglass.imageset/location_bar_magnifyingglass@3x.png b/ios/chrome/browser/ui/location_bar/resources/location_bar_magnifyingglass.imageset/location_bar_magnifyingglass@3x.png
new file mode 100644
index 0000000..733d36f53
--- /dev/null
+++ b/ios/chrome/browser/ui/location_bar/resources/location_bar_magnifyingglass.imageset/location_bar_magnifyingglass@3x.png
Binary files differ
diff --git a/ios/chrome/browser/ui/ntp/metrics/BUILD.gn b/ios/chrome/browser/ui/ntp/metrics/BUILD.gn
index f901c1f..8308bbf 100644
--- a/ios/chrome/browser/ui/ntp/metrics/BUILD.gn
+++ b/ios/chrome/browser/ui/ntp/metrics/BUILD.gn
@@ -23,6 +23,7 @@
     "//components/ntp_tiles",
     "//ios/chrome/browser/application_context",
     "//ios/chrome/browser/discover_feed:constants",
+    "//ios/chrome/browser/discover_feed:discover_feed_refresher",
     "//ios/chrome/browser/ntp:features",
     "//ios/chrome/browser/ui/content_suggestions:metrics",
     "//ios/chrome/browser/ui/favicon",
diff --git a/ios/chrome/browser/ui/ntp/metrics/feed_metrics_recorder.h b/ios/chrome/browser/ui/ntp/metrics/feed_metrics_recorder.h
index 48dba11..4fe8297 100644
--- a/ios/chrome/browser/ui/ntp/metrics/feed_metrics_recorder.h
+++ b/ios/chrome/browser/ui/ntp/metrics/feed_metrics_recorder.h
@@ -11,6 +11,7 @@
 #import "ios/chrome/browser/discover_feed/feed_constants.h"
 #import "ios/chrome/browser/ui/ntp/metrics/feed_metrics_constants.h"
 
+class DiscoverFeedRefresher;
 @protocol FeedControlDelegate;
 @protocol NewTabPageFollowDelegate;
 
@@ -30,6 +31,9 @@
 // Whether or not the feed is currently being shown on the Start Surface.
 @property(nonatomic, assign) BOOL isShownOnStartSurface;
 
+// Object that can refresh the feed.
+@property(nonatomic, assign) DiscoverFeedRefresher* feedRefresher;
+
 // Records the trigger where a feed refresh is requested.
 + (void)recordFeedRefreshTrigger:(FeedRefreshTrigger)trigger;
 
diff --git a/ios/chrome/browser/ui/ntp/metrics/feed_metrics_recorder.mm b/ios/chrome/browser/ui/ntp/metrics/feed_metrics_recorder.mm
index 3ec6888f..fc7a611 100644
--- a/ios/chrome/browser/ui/ntp/metrics/feed_metrics_recorder.mm
+++ b/ios/chrome/browser/ui/ntp/metrics/feed_metrics_recorder.mm
@@ -11,6 +11,7 @@
 #import "base/metrics/user_metrics_action.h"
 #import "base/time/time.h"
 #import "components/feed/core/v2/public/common_enums.h"
+#import "ios/chrome/browser/discover_feed/discover_feed_refresher.h"
 #import "ios/chrome/browser/ntp/features.h"
 #import "ios/chrome/browser/ui/content_suggestions/ntp_home_metrics.h"
 #import "ios/chrome/browser/ui/ntp/feed_control_delegate.h"
@@ -70,6 +71,9 @@
 @property(nonatomic, assign) NSTimeInterval discoverPreviousTimeInFeedGV;
 @property(nonatomic, assign) NSTimeInterval followingPreviousTimeInFeedGV;
 
+// Timer to signal end of session. Set for `kMinutesBetweenSessions`.
+@property(nonatomic, strong) NSTimer* sessionEndTimer;
+
 @end
 
 @implementation FeedMetricsRecorder
@@ -85,13 +89,16 @@
 
 #pragma mark - Public
 
+- (void)dealloc {
+  [self.sessionEndTimer invalidate];
+  self.sessionEndTimer = nil;
+}
+
 + (void)recordFeedRefreshTrigger:(FeedRefreshTrigger)trigger {
   base::UmaHistogramEnumeration(kDiscoverFeedRefreshTrigger, trigger);
 }
 
 - (void)recordFeedScrolled:(int)scrollDistance {
-  [self recordEngagement:scrollDistance interacted:NO];
-
   if (IsGoodVisitsMetricEnabled()) {
     self.goodVisitScroll = YES;
     [self checkEngagementGoodVisitWithInteraction:NO];
@@ -118,6 +125,8 @@
                               FeedEngagementType::kFeedScrolled);
     self.scrolledReportedFollowing = YES;
   }
+
+  [self recordEngagement:scrollDistance interacted:NO];
 }
 
 - (void)recordDeviceOrientationChanged:(UIDeviceOrientation)orientation {
@@ -137,6 +146,12 @@
 }
 
 - (void)recordNTPDidChangeVisibility:(BOOL)visible {
+  // Invalidate the timer when the user returns to the feed since the feed
+  // should not be refreshed when the user is viewing it.
+  if (visible) {
+    [self.sessionEndTimer invalidate];
+  }
+
   if (!IsGoodVisitsMetricEnabled()) {
     return;
   }
@@ -846,6 +861,13 @@
   }
 
   [self.sessionRecorder recordUserInteractionOrScrolling];
+
+  // This must be called after memoizing if the current session has met
+  // engagement criteria. For example, setting `engagedSimpleReportedDiscover`
+  // must happen before this call.
+  if (IsFeedRefreshPostFeedSessionEnabled()) {
+    [self setOrExtendSessionEndTimer];
+  }
 }
 
 // Checks if a Good Visit should be recorded. `interacted` is YES if it was
@@ -1169,6 +1191,30 @@
   }
 }
 
+// Sets or extends the session end timer by `kMinutesBetweenSessions`.
+- (void)setOrExtendSessionEndTimer {
+  [self.sessionEndTimer invalidate];
+  __weak FeedMetricsRecorder* weakSelf = self;
+  self.sessionEndTimer =
+      [NSTimer scheduledTimerWithTimeInterval:kMinutesBetweenSessions *
+                                              60 /*seconds per minute*/
+                                       target:weakSelf
+                                     selector:@selector
+                                     (refreshFeedIfSessionConditionsAreMet)
+                                     userInfo:nil
+                                      repeats:NO];
+}
+
+// Refresh the feed if session conditions are met. See implementation for which
+// specific conditions are used.
+- (void)refreshFeedIfSessionConditionsAreMet {
+  [self.sessionEndTimer invalidate];
+  self.sessionEndTimer = nil;
+  if (self.engagedSimpleReportedDiscover) {
+    self.feedRefresher->RefreshFeed(/*feed_visible=*/false);
+  }
+}
+
 #pragma mark - Converters
 
 // Converts a FollowingFeedSortType NSEnum into a FeedSortType enum.
diff --git a/ios/chrome/browser/ui/ntp/new_tab_page_feature.h b/ios/chrome/browser/ui/ntp/new_tab_page_feature.h
index 705cb1d1..0eca0d7 100644
--- a/ios/chrome/browser/ui/ntp/new_tab_page_feature.h
+++ b/ios/chrome/browser/ui/ntp/new_tab_page_feature.h
@@ -82,9 +82,14 @@
 // Publisher.
 extern const char kFollowingFeedDefaultSortTypeGroupedByPublisher[];
 
-// A parameter value for the feed's refresh threshold.
+// A parameter value for the feed's refresh threshold when the feed has already
+// been seen by the user.
 extern const char kFeedSettingRefreshThresholdInSeconds[];
 
+// A parameter value for the feed's refresh threshold when the feed has not been
+// seen by the user.
+extern const char kFeedSettingUnseenRefreshThresholdInSeconds[];
+
 // A parameter value for the feed's maximum data cache age.
 extern const char kFeedSettingMaximumDataCacheAgeInSeconds[];
 
diff --git a/ios/chrome/browser/ui/ntp/new_tab_page_feature.mm b/ios/chrome/browser/ui/ntp/new_tab_page_feature.mm
index f36f91c..e6d81af 100644
--- a/ios/chrome/browser/ui/ntp/new_tab_page_feature.mm
+++ b/ios/chrome/browser/ui/ntp/new_tab_page_feature.mm
@@ -92,6 +92,8 @@
 // Feature parameters for `kOverrideFeedSettings`.
 const char kFeedSettingRefreshThresholdInSeconds[] =
     "RefreshThresholdInSeconds";
+const char kFeedSettingUnseenRefreshThresholdInSeconds[] =
+    "UnseenRefreshThresholdInSeconds";
 const char kFeedSettingMaximumDataCacheAgeInSeconds[] =
     "MaximumDataCacheAgeInSeconds";
 const char kFeedSettingTimeoutThresholdAfterClearBrowsingData[] =
diff --git a/ios/chrome/browser/ui/ntp/new_tab_page_header_constants.h b/ios/chrome/browser/ui/ntp/new_tab_page_header_constants.h
index 3e06665..5fe7594 100644
--- a/ios/chrome/browser/ui/ntp/new_tab_page_header_constants.h
+++ b/ios/chrome/browser/ui/ntp/new_tab_page_header_constants.h
@@ -17,8 +17,6 @@
 
 extern const CGFloat kScrolledToTopOmniboxBottomMargin;
 
-extern const CGFloat kHintLabelSidePadding;
-
 extern const CGFloat kHintLabelHeightMargin;
 
 // The margin added to the fake omnibox to have at the right position.
diff --git a/ios/chrome/browser/ui/ntp/new_tab_page_header_constants.mm b/ios/chrome/browser/ui/ntp/new_tab_page_header_constants.mm
index 42e8fd2f..e296c68 100644
--- a/ios/chrome/browser/ui/ntp/new_tab_page_header_constants.mm
+++ b/ios/chrome/browser/ui/ntp/new_tab_page_header_constants.mm
@@ -13,7 +13,6 @@
 const CGFloat kAnimationDistance = 42;
 const CGFloat kFakeLocationBarTopConstraint = 4;
 const CGFloat kScrolledToTopOmniboxBottomMargin = 4;
-const CGFloat kHintLabelSidePadding = 37;
 const CGFloat kHintLabelHeightMargin = 2;
 const CGFloat kMaxTopMarginDiff = 4;
 const CGFloat kFakeOmniboxScrolledToTopMargin = 14;
diff --git a/ios/chrome/browser/ui/omnibox/popup/BUILD.gn b/ios/chrome/browser/ui/omnibox/popup/BUILD.gn
index 249921b5..0068095a 100644
--- a/ios/chrome/browser/ui/omnibox/popup/BUILD.gn
+++ b/ios/chrome/browser/ui/omnibox/popup/BUILD.gn
@@ -83,6 +83,7 @@
     "//ios/chrome/browser/ui/default_promo:utils",
     "//ios/chrome/browser/ui/favicon",
     "//ios/chrome/browser/ui/icons:symbols",
+    "//ios/chrome/browser/ui/icons:symbols_views",
     "//ios/chrome/browser/ui/main:default_browser_scene_agent",
     "//ios/chrome/browser/ui/main:layout_guide_util",
     "//ios/chrome/browser/ui/main:scene_state_header",
@@ -109,6 +110,7 @@
     "//ios/chrome/common/ui/colors",
     "//ios/chrome/common/ui/favicon",
     "//ios/chrome/common/ui/util",
+    "//ios/chrome/common/ui/util:image_util",
     "//ios/web/public:public",
     "//net",
     "//ui/base",
diff --git a/ios/chrome/browser/ui/omnibox/popup/omnibox_pedal_annotator.mm b/ios/chrome/browser/ui/omnibox/popup/omnibox_pedal_annotator.mm
index b0a2648..67524e96 100644
--- a/ios/chrome/browser/ui/omnibox/popup/omnibox_pedal_annotator.mm
+++ b/ios/chrome/browser/ui/omnibox/popup/omnibox_pedal_annotator.mm
@@ -13,8 +13,12 @@
 #import "ios/chrome/browser/ui/commands/application_commands.h"
 #import "ios/chrome/browser/ui/commands/omnibox_commands.h"
 #import "ios/chrome/browser/ui/commands/open_new_tab_command.h"
+#import "ios/chrome/browser/ui/icons/colorful_background_symbol_view.h"
+#import "ios/chrome/browser/ui/icons/symbols.h"
 #import "ios/chrome/browser/ui/omnibox/popup/popup_swift.h"
 #import "ios/chrome/browser/url/chrome_url_constants.h"
+#import "ios/chrome/common/ui/colors/semantic_color_names.h"
+#import "ios/chrome/common/ui/util/image_util.h"
 #import "ios/chrome/grit/ios_strings.h"
 #import "ui/base/l10n/l10n_util.h"
 
@@ -58,7 +62,11 @@
   NSInteger pedalType = static_cast<NSInteger>(
       static_cast<const OmniboxPedal*>(pedalAction)->GetMetricsId());
 
-  switch (static_cast<OmniboxPedalId>(pedalAction->GetID())) {
+  OmniboxPedalId pedalId = static_cast<OmniboxPedalId>(pedalAction->GetID());
+
+  UIImage* image = [self pedalIconForPedalId:pedalId incognito:incognito];
+
+  switch (pedalId) {
     case OmniboxPedalId::PLAY_CHROME_DINO_GAME: {
       NSString* urlStr = [NSString
           stringWithFormat:@"%s://%s", kChromeUIScheme, kChromeUIDinoHost];
@@ -67,9 +75,8 @@
               initWithTitle:hint
                    subtitle:urlStr
           accessibilityHint:suggestionContents
-                  imageName:@"pedal_dino"
+                      image:image
                        type:pedalType
-                  incognito:incognito
                      action:^{
                        OpenNewTabCommand* command =
                            [OpenNewTabCommand commandWithURLFromChrome:url
@@ -84,9 +91,8 @@
                        l10n_util::GetNSString(
                            IDS_IOS_OMNIBOX_PEDAL_SUBTITLE_CLEAR_BROWSING_DATA)
           accessibilityHint:suggestionContents
-                  imageName:@"pedal_clear_browsing_data"
+                      image:image
                        type:pedalType
-                  incognito:incognito
                      action:^{
                        [omniboxCommandHandler cancelOmniboxEdit];
                        [pedalsEndpoint showClearBrowsingDataSettings];
@@ -106,9 +112,8 @@
                    subtitle:l10n_util::GetNSString(
                                 IDS_IOS_OMNIBOX_PEDAL_SUBTITLE_DEFAULT_BROWSER)
           accessibilityHint:suggestionContents
-                  imageName:@"pedal_default_browser"
+                      image:image
                        type:pedalType
-                  incognito:incognito
                      action:action];
     }
     case OmniboxPedalId::MANAGE_PASSWORDS: {
@@ -117,9 +122,8 @@
                    subtitle:l10n_util::GetNSString(
                                 IDS_IOS_OMNIBOX_PEDAL_SUBTITLE_MANAGE_PASSWORDS)
           accessibilityHint:suggestionContents
-                  imageName:@"pedal_passwords"
+                      image:image
                        type:pedalType
-                  incognito:incognito
                      action:^{
                        [omniboxCommandHandler cancelOmniboxEdit];
                        [pedalsEndpoint
@@ -134,9 +138,8 @@
                        l10n_util::GetNSString(
                            IDS_IOS_OMNIBOX_PEDAL_SUBTITLE_UPDATE_CREDIT_CARD)
           accessibilityHint:suggestionContents
-                  imageName:@"pedal_payments"
+                      image:image
                        type:pedalType
-                  incognito:incognito
                      action:^{
                        [omniboxCommandHandler cancelOmniboxEdit];
                        [pedalsEndpoint showCreditCardSettings];
@@ -148,9 +151,8 @@
                    subtitle:l10n_util::GetNSString(
                                 IDS_IOS_OMNIBOX_PEDAL_SUBTITLE_LAUNCH_INCOGNITO)
           accessibilityHint:suggestionContents
-                  imageName:@"pedal_incognito"
+                      image:image
                        type:pedalType
-                  incognito:incognito
                      action:^{
                        [omniboxCommandHandler cancelOmniboxEdit];
                        [pedalsEndpoint
@@ -165,9 +167,8 @@
               initWithTitle:hint
                    subtitle:subtitle
           accessibilityHint:suggestionContents
-                  imageName:@"pedal_safety_check"
+                      image:image
                        type:pedalType
-                  incognito:incognito
                      action:^{
                        [omniboxCommandHandler cancelOmniboxEdit];
                        [pedalsEndpoint
@@ -181,9 +182,8 @@
               initWithTitle:hint
                    subtitle:subtitle
           accessibilityHint:suggestionContents
-                  imageName:@"pedal_settings"
+                      image:image
                        type:pedalType
-                  incognito:incognito
                      action:^{
                        [omniboxCommandHandler cancelOmniboxEdit];
                        [pedalsEndpoint showSettingsFromViewController:nil];
@@ -196,17 +196,151 @@
                        l10n_util::GetNSString(
                            IDS_IOS_OMNIBOX_PEDAL_SUBTITLE_VIEW_CHROME_HISTORY)
           accessibilityHint:suggestionContents
-                  imageName:@"pedal_history"
+                      image:image
                        type:pedalType
-                  incognito:incognito
                      action:^{
                        [omniboxCommandHandler cancelOmniboxEdit];
                        [pedalsEndpoint showHistory];
                      }];
     }
+      // If a new case is added here, make sure to update the method returning
+      // the icon.
     default:
       return nil;
   }
 }
 
+#pragma mark - Private
+
+// Returns the image associated with `pedalId`, for `incognito` or not.
+- (UIImage*)pedalIconForPedalId:(OmniboxPedalId)pedalId
+                      incognito:(BOOL)incognito {
+  ColorfulBackgroundSymbolView* symbolView =
+      [[ColorfulBackgroundSymbolView alloc] init];
+  if (incognito) {
+    // Dark mode is set explicitly if incognito is enabled.
+    symbolView.overrideUserInterfaceStyle = UIUserInterfaceStyleDark;
+  }
+
+  // Dark mode is set explicitly if incognito is enabled.
+  UITraitCollection* traitCollection =
+      [UITraitCollection traitCollectionWithUserInterfaceStyle:
+                             incognito ? UIUserInterfaceStyleDark
+                                       : UIUserInterfaceStyleUnspecified];
+
+  switch (pedalId) {
+    case OmniboxPedalId::PLAY_CHROME_DINO_GAME: {
+      if (UseSymbolsInOmnibox()) {
+        [symbolView setSymbol:CustomSymbolWithPointSize(kDinoSymbol, 22)];
+        symbolView.backgroundColor = UIColor.whiteColor;
+        [symbolView setSymbolTintColor:UIColor.blackColor];
+        symbolView.borderColor = [UIColor colorNamed:kGrey200Color];
+        return ImageFromView(symbolView, nil, UIEdgeInsetsZero);
+      } else {
+        return [UIImage imageNamed:@"pedal_dino"
+                                 inBundle:nil
+            compatibleWithTraitCollection:traitCollection];
+      }
+    }
+    case OmniboxPedalId::CLEAR_BROWSING_DATA: {
+      if (UseSymbolsInOmnibox()) {
+        [symbolView setSymbolName:kTrashSymbol systemSymbol:YES];
+        symbolView.backgroundColor = [UIColor colorNamed:kBlue500Color];
+        return ImageFromView(symbolView, nil, UIEdgeInsetsZero);
+      } else {
+        return [UIImage imageNamed:@"pedal_clear_browsing_data"
+                                 inBundle:nil
+            compatibleWithTraitCollection:traitCollection];
+      }
+    }
+    case OmniboxPedalId::SET_CHROME_AS_DEFAULT_BROWSER: {
+      if (UseSymbolsInOmnibox()) {
+#if BUILDFLAG(IOS_USE_BRANDED_SYMBOLS)
+        symbolView.backgroundColor = UIColor.whiteColor;
+        [symbolView setSymbol:MakeSymbolMulticolor(CustomSymbolWithPointSize(
+                                  kGoogleIconSymbol, 22))];
+        symbolView.borderColor = [UIColor colorNamed:kGrey200Color];
+#else
+        [symbolView setSymbolName:kDefaultBrowserSymbol systemSymbol:YES];
+        symbolView.backgroundColor = [UIColor colorNamed:kPurple500Color];
+#endif  // BUILDFLAG(IOS_USE_BRANDED_SYMBOLS)
+        return ImageFromView(symbolView, nil, UIEdgeInsetsZero);
+      } else {
+        return [UIImage imageNamed:@"pedal_default_browser"
+                                 inBundle:nil
+            compatibleWithTraitCollection:traitCollection];
+      }
+    }
+    case OmniboxPedalId::MANAGE_PASSWORDS: {
+      if (UseSymbolsInOmnibox()) {
+        [symbolView setSymbolName:kPasswordSymbol systemSymbol:NO];
+        symbolView.backgroundColor = [UIColor colorNamed:kYellow500Color];
+        return ImageFromView(symbolView, nil, UIEdgeInsetsZero);
+      } else {
+        return [UIImage imageNamed:@"pedal_passwords"
+                                 inBundle:nil
+            compatibleWithTraitCollection:traitCollection];
+      }
+    }
+    case OmniboxPedalId::UPDATE_CREDIT_CARD: {
+      if (UseSymbolsInOmnibox()) {
+        [symbolView setSymbolName:kCreditCardSymbol systemSymbol:YES];
+        symbolView.backgroundColor = [UIColor colorNamed:kYellow500Color];
+        return ImageFromView(symbolView, nil, UIEdgeInsetsZero);
+      } else {
+        return [UIImage imageNamed:@"pedal_payments"
+                                 inBundle:nil
+            compatibleWithTraitCollection:traitCollection];
+      }
+    }
+    case OmniboxPedalId::LAUNCH_INCOGNITO: {
+      if (UseSymbolsInOmnibox()) {
+        [symbolView setSymbolName:kIncognitoSymbol systemSymbol:NO];
+        symbolView.backgroundColor = [UIColor colorNamed:kGrey800Color];
+        return ImageFromView(symbolView, nil, UIEdgeInsetsZero);
+      } else {
+        return [UIImage imageNamed:@"pedal_incognito"
+                                 inBundle:nil
+            compatibleWithTraitCollection:traitCollection];
+      }
+    }
+    case OmniboxPedalId::RUN_CHROME_SAFETY_CHECK: {
+      if (UseSymbolsInOmnibox()) {
+        [symbolView setSymbolName:kSafetyCheckSymbol systemSymbol:NO];
+        symbolView.backgroundColor = [UIColor colorNamed:kBlue500Color];
+        return ImageFromView(symbolView, nil, UIEdgeInsetsZero);
+      } else {
+        return [UIImage imageNamed:@"pedal_safety_check"
+                                 inBundle:nil
+            compatibleWithTraitCollection:traitCollection];
+      }
+    }
+    case OmniboxPedalId::MANAGE_CHROME_SETTINGS: {
+      if (UseSymbolsInOmnibox()) {
+        [symbolView setSymbolName:kSettingsSymbol systemSymbol:YES];
+        symbolView.backgroundColor = [UIColor colorNamed:kGrey500Color];
+        return ImageFromView(symbolView, nil, UIEdgeInsetsZero);
+      } else {
+        return [UIImage imageNamed:@"pedal_settings"
+                                 inBundle:nil
+            compatibleWithTraitCollection:traitCollection];
+      }
+    }
+    case OmniboxPedalId::VIEW_CHROME_HISTORY: {
+      if (UseSymbolsInOmnibox()) {
+        [symbolView setSymbolName:kHistorySymbol systemSymbol:YES];
+        symbolView.backgroundColor = [UIColor colorNamed:kBlue500Color];
+        return ImageFromView(symbolView, nil, UIEdgeInsetsZero);
+      } else {
+        return [UIImage imageNamed:@"pedal_history"
+                                 inBundle:nil
+            compatibleWithTraitCollection:traitCollection];
+      }
+    }
+    default:
+      NOTREACHED();
+      return nil;
+  }
+}
+
 @end
diff --git a/ios/chrome/browser/ui/omnibox/popup/shared/omnibox_pedal.swift b/ios/chrome/browser/ui/omnibox/popup/shared/omnibox_pedal.swift
index 302ca03..c4ecef4 100644
--- a/ios/chrome/browser/ui/omnibox/popup/shared/omnibox_pedal.swift
+++ b/ios/chrome/browser/ui/omnibox/popup/shared/omnibox_pedal.swift
@@ -13,10 +13,8 @@
   public let subtitle: String
   /// Describes the action performed; can be used for voiceover.
   public let hint: String
-  /// Name of the image in the bundle.
-  public let imageName: String
-  /// Whether the pedal is displayed from an incognito session.
-  public let incognito: Bool
+  /// The image for the Pedal.
+  public let image: UIImage
   /// Action to run when the pedal is executed.
   public let action: () -> Void
   /// Action type for metrics collection. Int-casted OmniboxPedalId
@@ -24,15 +22,14 @@
 
   public init(
     title: String, subtitle: String,
-    accessibilityHint: String, imageName: String, type: Int, incognito: Bool,
+    accessibilityHint: String, image: UIImage, type: Int,
     action: @escaping () -> Void
   ) {
     self.title = title
     self.subtitle = subtitle
     self.hint = accessibilityHint
-    self.imageName = imageName
+    self.image = image
     self.type = type
-    self.incognito = incognito
     self.action = action
   }
 }
@@ -44,12 +41,7 @@
   }
 
   public var iconImage: UIImage? {
-    // Dark mode is set explicitly if incognito is enabled.
-    let userInterfaceStyle =
-      UITraitCollection(userInterfaceStyle: incognito ? .dark : .unspecified)
-    return UIImage(
-      named: self.imageName, in: nil,
-      compatibleWith: UITraitCollection(traitsFrom: [.current, userInterfaceStyle]))
+    return image
   }
 
   public var imageURL: CrURL? { return nil }
diff --git a/ios/chrome/test/providers/signin/fake_trusted_vault_client_backend.h b/ios/chrome/test/providers/signin/fake_trusted_vault_client_backend.h
index 3c93e5fc..eaa5e1d 100644
--- a/ios/chrome/test/providers/signin/fake_trusted_vault_client_backend.h
+++ b/ios/chrome/test/providers/signin/fake_trusted_vault_client_backend.h
@@ -18,6 +18,8 @@
   // TrustedVaultClientBackend implementation.
   void AddObserver(Observer* observer) final;
   void RemoveObserver(Observer* observer) final;
+  void SetDeviceRegistrationPublicKeyVerifierForUMA(
+      VerifierCallback verifier) final;
   void FetchKeys(id<SystemIdentity> identity,
                  KeyFetchedCallback callback) final;
   void MarkLocalKeysAsStale(id<SystemIdentity> identity,
@@ -32,6 +34,8 @@
                                  UIViewController* presenting_view_controller,
                                  CompletionBlock callback) final;
   void CancelDialog(BOOL animated, ProceduralBlock callback) final;
+  void ClearLocalData(id<SystemIdentity> identity,
+                      base::OnceCallback<void(bool)> callback) final;
 
   // Simulates user cancelling the reauth dialog.
   void SimulateUserCancel();
diff --git a/ios/chrome/test/providers/signin/fake_trusted_vault_client_backend.mm b/ios/chrome/test/providers/signin/fake_trusted_vault_client_backend.mm
index a9429f6f..a6ce15ae 100644
--- a/ios/chrome/test/providers/signin/fake_trusted_vault_client_backend.mm
+++ b/ios/chrome/test/providers/signin/fake_trusted_vault_client_backend.mm
@@ -72,6 +72,11 @@
   // Do nothing.
 }
 
+void FakeTrustedVaultClientBackend::
+    SetDeviceRegistrationPublicKeyVerifierForUMA(VerifierCallback verifier) {
+  // Do nothing.
+}
+
 void FakeTrustedVaultClientBackend::FetchKeys(id<SystemIdentity> identity,
                                               KeyFetchedCallback callback) {
   // Do nothing.
@@ -108,6 +113,12 @@
   // Do nothing.
 }
 
+void FakeTrustedVaultClientBackend::ClearLocalData(
+    id<SystemIdentity> identity,
+    base::OnceCallback<void(bool)> callback) {
+  // Do nothing.
+}
+
 void FakeTrustedVaultClientBackend::CancelDialog(BOOL animated,
                                                  ProceduralBlock callback) {
   DCHECK(view_controller_);
diff --git a/ios/web/js_features/context_menu/resources/all_frames_context_menu.js b/ios/web/js_features/context_menu/resources/all_frames_context_menu.js
index 9fd3b6e..e16bfd46 100644
--- a/ios/web/js_features/context_menu/resources/all_frames_context_menu.js
+++ b/ios/web/js_features/context_menu/resources/all_frames_context_menu.js
@@ -124,9 +124,17 @@
         result.innerText = textNode.nodeValue;
 
         if (extractSurroundingText) {
-          var textAndStartPos = getSurroundingText(range);
-          result.surroundingText = textAndStartPos['text'];
-          result.surroundingTextOffset = textAndStartPos['pos'];
+          // TODO(crbug.com/1416910): getSurroundingText throws an exception
+          // when a node is null, a fix is planned to remove the exception
+          // handling.
+          try {
+            var textAndStartPos = getSurroundingText(range);
+            result.surroundingText = textAndStartPos['text'];
+            result.surroundingTextOffset = textAndStartPos['pos'];
+          } catch (err) {
+            result.surroundingText = '';
+            result.surroundingTextOffset = 0;
+          }
         }
       }
     }
diff --git a/ios/web/navigation/crw_wk_navigation_handler.mm b/ios/web/navigation/crw_wk_navigation_handler.mm
index 5b87c306..71f4de9 100644
--- a/ios/web/navigation/crw_wk_navigation_handler.mm
+++ b/ios/web/navigation/crw_wk_navigation_handler.mm
@@ -1863,10 +1863,6 @@
       [[CRWErrorPageHelper alloc] initWithError:error];
   WKBackForwardListItem* backForwardItem = webView.backForwardList.currentItem;
   GURL backForwardGURL = net::GURLWithNSURL(backForwardItem.URL);
-  if (web::wk_navigation_util::IsRestoreSessionUrl(backForwardGURL)) {
-    web::wk_navigation_util::ExtractTargetURL(backForwardGURL,
-                                              &backForwardGURL);
-  }
   GURL failedURL = [CRWErrorPageHelper
       failedNavigationURLFromErrorPageFileURL:backForwardGURL];
   bool isSameURLFromWebClient = web::GetWebClient()->IsPointingToSameDocument(
@@ -1875,20 +1871,21 @@
   //   1. Current nav item is an error page for failed URL;
   //   2. Current nav item has a failed URL. This may happen when
   //      back/forward/refresh on a loaded page;
-  //   3. Current nav item is a session restoration of the failed navigation.
-  //   4. Current nav item is an irrelevant page.
-  // For 1, 2 and 3, load an empty string to remove existing JS code. The URL is
+  //   3. Current nav item is an irrelevant page.
+  //   4. Current nav item is a session restoration.
+  // For 1, 2 and 4, load an empty string to remove existing JS code. The URL is
   // also updated to the URL of the page that failed to allow back/forward
   // navigations even on navigations originating from pushstate. See
   // crbug.com/1153261.
-  // For 4, load error page file to create a new nav item.
+  // For 3, load error page file to create a new nav item.
   // The actual error HTML will be loaded in didFinishNavigation callback.
   WKNavigation* errorNavigation = nil;
   if (provisionalLoad &&
       ![errorPage
           isErrorPageFileURLForFailedNavigationURL:backForwardItem.URL] &&
       !isSameURLFromWebClient &&
-      backForwardGURL != net::GURLWithNSURL(errorPage.failedNavigationURL)) {
+      ![backForwardItem.URL isEqual:errorPage.failedNavigationURL] &&
+      !web::wk_navigation_util::IsRestoreSessionUrl(backForwardItem.URL)) {
     errorNavigation = [webView loadFileURL:errorPage.errorPageFileURL
                    allowingReadAccessToURL:errorPage.errorPageFileURL];
   } else {
diff --git a/ios/web/web_state/error_page_inttest.mm b/ios/web/web_state/error_page_inttest.mm
index 23e9f68..7939681 100644
--- a/ios/web/web_state/error_page_inttest.mm
+++ b/ios/web/web_state/error_page_inttest.mm
@@ -599,53 +599,4 @@
       restored_web_state->GetNavigationManager()->GetForwardItems().size());
 }
 
-// Tests that navigating to a page that fails during the provisional load while
-// being on a session restoration page create a new navigation.
-TEST_F(ErrorPageTest, LoadFailingPageAfterRestoration) {
-  server_responds_with_content_ = true;
-
-  test::LoadUrl(web_state(), server_.GetURL("/echo-query?foo"));
-  ASSERT_TRUE(test::WaitForWebViewContainingText(web_state(), "foo"));
-
-  // Restore the session.
-  WebState::CreateParams params(GetBrowserState());
-  auto restored_web_state = WebState::CreateWithStorageSession(
-      params, web_state()->BuildSessionStorage());
-  NavigationManager* navigation_manager =
-      restored_web_state->GetNavigationManager();
-
-  navigation_manager->LoadIfNecessary();
-
-  ASSERT_TRUE(test::WaitForWebViewContainingText(web_state(), "foo"));
-  // Check that there is no back/forward item.
-  ASSERT_EQ(0UL, navigation_manager->GetBackwardItems().size());
-  ASSERT_EQ(0UL, navigation_manager->GetForwardItems().size());
-
-  // Load invalid URL (failing during the load).
-  std::string scheme = kTestWebUIScheme;
-  GURL invalid_webui = GURL(scheme + "://invalid");
-  test::LoadUrl(restored_web_state.get(), invalid_webui);
-
-  NSError* error = testing::CreateErrorWithUnderlyingErrorChain(
-      {{@"NSURLErrorDomain", NSURLErrorUnsupportedURL},
-       {net::kNSErrorDomain, net::ERR_INVALID_URL}});
-  ASSERT_TRUE(test::WaitForWebViewContainingText(
-      restored_web_state.get(),
-      testing::GetErrorText(restored_web_state.get(), invalid_webui, error,
-                            /*is_post=*/false, /*is_otr=*/false,
-                            /*cert_status=*/0)));
-
-  // Check that there is one item in the back list and no forward item.
-  EXPECT_EQ(1UL, navigation_manager->GetBackwardItems().size());
-  EXPECT_EQ(0UL, navigation_manager->GetForwardItems().size());
-
-  navigation_manager->GoBack();
-  ASSERT_TRUE(
-      test::WaitForWebViewContainingText(restored_web_state.get(), "foo"));
-
-  // Check that there is one item in the forward list and no back item.
-  EXPECT_EQ(0UL, navigation_manager->GetBackwardItems().size());
-  EXPECT_EQ(1UL, navigation_manager->GetForwardItems().size());
-}
-
 }  // namespace web
diff --git a/media/mojo/services/stable_video_decoder_service.cc b/media/mojo/services/stable_video_decoder_service.cc
index 450d23e..3811f553 100644
--- a/media/mojo/services/stable_video_decoder_service.cc
+++ b/media/mojo/services/stable_video_decoder_service.cc
@@ -176,7 +176,6 @@
   // The mojo traits have been coded assuming these conditions.
   CHECK(frame->metadata().allow_overlay);
   CHECK(!frame->metadata().end_of_stream);
-  CHECK(frame->metadata().read_lock_fences_enabled);
   CHECK(frame->metadata().power_efficient);
 
   stable_video_decoder_client_remote_->OnVideoFrameDecoded(
diff --git a/media/mojo/services/stable_video_decoder_service_unittest.cc b/media/mojo/services/stable_video_decoder_service_unittest.cc
index 5e7e8a7..a65dcc51 100644
--- a/media/mojo/services/stable_video_decoder_service_unittest.cc
+++ b/media/mojo/services/stable_video_decoder_service_unittest.cc
@@ -787,6 +787,9 @@
       video_frame_to_send, kCanReadWithoutStalling, token_for_release);
   auxiliary_endpoints->video_decoder_client_remote.FlushForTesting();
   ASSERT_TRUE(video_frame_received);
+  EXPECT_FALSE(video_frame_received->metadata().end_of_stream);
+  EXPECT_TRUE(video_frame_received->metadata().read_lock_fences_enabled);
+  EXPECT_TRUE(video_frame_received->metadata().power_efficient);
   EXPECT_TRUE(video_frame_received->metadata().allow_overlay);
 }
 
diff --git a/net/BUILD.gn b/net/BUILD.gn
index 4e2aa15..d7ee4190 100644
--- a/net/BUILD.gn
+++ b/net/BUILD.gn
@@ -315,6 +315,8 @@
     "cert/internal/revocation_checker.h",
     "cert/internal/system_trust_store.cc",
     "cert/internal/system_trust_store.h",
+    "cert/internal/trust_store_features.cc",
+    "cert/internal/trust_store_features.h",
     "cert/known_roots.cc",
     "cert/known_roots.h",
     "cert/merkle_audit_proof.cc",
diff --git a/net/cert/internal/system_trust_store_nss_unittest.cc b/net/cert/internal/system_trust_store_nss_unittest.cc
index 926c1e9d..96ae434 100644
--- a/net/cert/internal/system_trust_store_nss_unittest.cc
+++ b/net/cert/internal/system_trust_store_nss_unittest.cc
@@ -13,6 +13,7 @@
 #include "crypto/scoped_nss_types.h"
 #include "crypto/scoped_test_nss_db.h"
 #include "net/cert/internal/system_trust_store_nss.h"
+#include "net/cert/internal/trust_store_features.h"
 #include "net/cert/pki/cert_errors.h"
 #include "net/cert/pki/parsed_certificate.h"
 #include "net/cert/test_root_certs.h"
@@ -100,6 +101,28 @@
 // Tests that SystemTrustStore created for NSS with a user-slot restriction
 // allows certificates stored on the specified user slot to be trusted.
 TEST_F(SystemTrustStoreNSSTest, UserSlotRestrictionAllows) {
+  ScopedLocalAnchorConstraintsEnforcementForTesting
+      scoped_enforce_local_anchor_constraints(true);
+  std::unique_ptr<SystemTrustStore> system_trust_store =
+      CreateSslSystemTrustStoreNSSWithUserSlotRestriction(
+          crypto::ScopedPK11Slot(PK11_ReferenceSlot(test_nssdb_.slot())));
+
+  ASSERT_NO_FATAL_FAILURE(ImportRootCertAsTrusted(test_nssdb_.slot()));
+
+  CertificateTrust trust =
+      system_trust_store->GetTrustStore()->GetTrust(parsed_root_cert_.get(),
+                                                    /*debug_data=*/nullptr);
+  EXPECT_EQ(CertificateTrust::ForTrustAnchor()
+                .WithEnforceAnchorConstraints()
+                .WithEnforceAnchorExpiry()
+                .ToDebugString(),
+            trust.ToDebugString());
+}
+
+TEST_F(SystemTrustStoreNSSTest,
+       UserSlotRestrictionAllowsWithAnchorConstraintsDisabled) {
+  ScopedLocalAnchorConstraintsEnforcementForTesting
+      scoped_enforce_local_anchor_constraints(false);
   std::unique_ptr<SystemTrustStore> system_trust_store =
       CreateSslSystemTrustStoreNSSWithUserSlotRestriction(
           crypto::ScopedPK11Slot(PK11_ReferenceSlot(test_nssdb_.slot())));
diff --git a/net/cert/internal/trust_store_features.cc b/net/cert/internal/trust_store_features.cc
new file mode 100644
index 0000000..a86231f
--- /dev/null
+++ b/net/cert/internal/trust_store_features.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 "net/cert/internal/trust_store_features.h"
+
+#include <atomic>
+
+#include "base/no_destructor.h"
+
+namespace net {
+
+namespace {
+std::atomic_bool* GetLocalAnchorConstraintsEnforcementFlag() {
+  static std::atomic_bool flag = base::FeatureList::IsEnabled(
+      net::features::kEnforceLocalAnchorConstraints);
+  return &flag;
+}
+
+}  // namespace
+
+bool IsLocalAnchorConstraintsEnforcementEnabled() {
+  return GetLocalAnchorConstraintsEnforcementFlag()->load();
+}
+
+void SetLocalAnchorConstraintsEnforcementEnabled(bool enabled) {
+  GetLocalAnchorConstraintsEnforcementFlag()->store(enabled);
+}
+
+namespace features {
+
+BASE_FEATURE(kEnforceLocalAnchorConstraints,
+             "EnforceLocalAnchorConstraints",
+             base::FEATURE_ENABLED_BY_DEFAULT);
+
+}  // namespace features
+
+}  // namespace net
diff --git a/net/cert/internal/trust_store_features.h b/net/cert/internal/trust_store_features.h
new file mode 100644
index 0000000..02156a07
--- /dev/null
+++ b/net/cert/internal/trust_store_features.h
@@ -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.
+
+#ifndef NET_CERT_INTERNAL_TRUST_STORE_FEATURES_H_
+#define NET_CERT_INTERNAL_TRUST_STORE_FEATURES_H_
+
+#include "base/feature_list.h"
+#include "net/base/net_export.h"
+
+namespace net {
+
+// Returns true when platform TrustStore implementations should enforce
+// constraints encoded into X.509 certificate trust anchors.
+// When disabled, platform TrustStore implementations will not enforce anchor
+// constraints (other than expiry).
+// Has no effect if using a platform CertVerifyProc implementation.
+// TODO(https://crbug.com/1406103): remove this a few milestones after the
+// trust anchor constraints enforcement has been launched on all relevant
+// platforms.
+// Should only be called after base::Features have been resolved. Note that
+// using ScopedFeatureList to override this won't work properly in unittests,
+// use ScopedLocalAnchorConstraintsEnforcementForTesting instead. Using
+// ScopedFeatureList in browser_tests is fine.
+// It is safe to call this function on any thread.
+NET_EXPORT bool IsLocalAnchorConstraintsEnforcementEnabled();
+
+// Override the feature flag. Don't call this without consulting
+// net/cert/OWNERS.
+// It is safe to call this function on any thread.
+NET_EXPORT void SetLocalAnchorConstraintsEnforcementEnabled(bool enabled);
+
+// Temporarily change the SetLocalAnchorConstraintsEnforcementEnabled value,
+// resetting to the original value when destructed.
+class NET_EXPORT ScopedLocalAnchorConstraintsEnforcementForTesting {
+ public:
+  explicit ScopedLocalAnchorConstraintsEnforcementForTesting(bool enabled)
+      : previous_value_(IsLocalAnchorConstraintsEnforcementEnabled()) {
+    SetLocalAnchorConstraintsEnforcementEnabled(enabled);
+  }
+
+  ~ScopedLocalAnchorConstraintsEnforcementForTesting() {
+    SetLocalAnchorConstraintsEnforcementEnabled(previous_value_);
+  }
+
+ private:
+  const bool previous_value_;
+};
+
+namespace features {
+
+// Most code should not check this feature flag directly, instead use
+// IsLocalAnchorConstraintsEnforcementEnabled().
+NET_EXPORT BASE_DECLARE_FEATURE(kEnforceLocalAnchorConstraints);
+
+}  // namespace features
+
+}  // namespace net
+
+#endif  // NET_CERT_INTERNAL_TRUST_STORE_FEATURES_H_
diff --git a/net/cert/internal/trust_store_mac.cc b/net/cert/internal/trust_store_mac.cc
index ef8912a..1094656 100644
--- a/net/cert/internal/trust_store_mac.cc
+++ b/net/cert/internal/trust_store_mac.cc
@@ -24,6 +24,7 @@
 #include "net/base/features.h"
 #include "net/base/hash_value.h"
 #include "net/base/network_notification_thread_mac.h"
+#include "net/cert/internal/trust_store_features.h"
 #include "net/cert/pki/cert_errors.h"
 #include "net/cert/pki/cert_issuer_source_static.h"
 #include "net/cert/pki/extended_key_usage.h"
@@ -1160,17 +1161,24 @@
                                          base::SupportsUserData* debug_data) {
   TrustStatus trust_status = trust_cache_->IsCertTrusted(cert, debug_data);
   switch (trust_status) {
-    case TrustStatus::TRUSTED:
+    case TrustStatus::TRUSTED: {
+      CertificateTrust trust;
       if (base::FeatureList::IsEnabled(
               features::kTrustStoreTrustedLeafSupport)) {
         // Mac trust settings don't distinguish between trusted anchors and
         // trusted leafs, return a trust record valid for both, which will
         // depend on the context the certificate is encountered in.
-        return CertificateTrust::ForTrustAnchorOrLeaf()
-            .WithEnforceAnchorExpiry();
+        trust =
+            CertificateTrust::ForTrustAnchorOrLeaf().WithEnforceAnchorExpiry();
       } else {
-        return CertificateTrust::ForTrustAnchor().WithEnforceAnchorExpiry();
+        trust = CertificateTrust::ForTrustAnchor().WithEnforceAnchorExpiry();
       }
+      if (IsLocalAnchorConstraintsEnforcementEnabled()) {
+        trust = trust.WithEnforceAnchorConstraints()
+                    .WithRequireAnchorBasicConstraints();
+      }
+      return trust;
+    }
     case TrustStatus::DISTRUSTED:
       return CertificateTrust::ForDistrusted();
     case TrustStatus::UNSPECIFIED:
diff --git a/net/cert/internal/trust_store_mac_unittest.cc b/net/cert/internal/trust_store_mac_unittest.cc
index 9929e80..54bde0e 100644
--- a/net/cert/internal/trust_store_mac_unittest.cc
+++ b/net/cert/internal/trust_store_mac_unittest.cc
@@ -22,10 +22,12 @@
 #include "crypto/mac_security_services_lock.h"
 #include "crypto/sha2.h"
 #include "net/base/features.h"
+#include "net/cert/internal/trust_store_features.h"
 #include "net/cert/pem.h"
 #include "net/cert/pki/cert_errors.h"
 #include "net/cert/pki/parsed_certificate.h"
 #include "net/cert/pki/test_helpers.h"
+#include "net/cert/pki/trust_store.h"
 #include "net/cert/test_keychain_search_list_mac.h"
 #include "net/cert/x509_certificate.h"
 #include "net/cert/x509_util.h"
@@ -121,10 +123,12 @@
 
 class TrustStoreMacImplTest
     : public testing::TestWithParam<
-          std::tuple<TrustStoreMac::TrustImplType, bool>> {
+          std::tuple<TrustStoreMac::TrustImplType, bool, bool>> {
  public:
-  TrustStoreMacImplTest() {
-    if (std::get<1>(GetParam())) {
+  TrustStoreMacImplTest()
+      : scoped_enforce_local_anchor_constraints_(
+            ExpectedEnforceLocalAnchorConstraintsEnabled()) {
+    if (ExpectedTrustedLeafSupportEnabled()) {
       feature_list_.InitAndEnableFeature(
           features::kTrustStoreTrustedLeafSupport);
     } else {
@@ -137,16 +141,36 @@
     return std::get<0>(GetParam());
   }
 
+  bool ExpectedTrustedLeafSupportEnabled() const {
+    return std::get<1>(GetParam());
+  }
+
+  bool ExpectedEnforceLocalAnchorConstraintsEnabled() const {
+    return std::get<2>(GetParam());
+  }
+
   CertificateTrust ExpectedTrustForAnchor() const {
-    if (std::get<1>(GetParam())) {
-      return CertificateTrust::ForTrustAnchorOrLeaf().WithEnforceAnchorExpiry();
+    CertificateTrust trust;
+
+    if (ExpectedTrustedLeafSupportEnabled()) {
+      trust =
+          CertificateTrust::ForTrustAnchorOrLeaf().WithEnforceAnchorExpiry();
     } else {
-      return CertificateTrust::ForTrustAnchor().WithEnforceAnchorExpiry();
+      trust = CertificateTrust::ForTrustAnchor().WithEnforceAnchorExpiry();
     }
+
+    if (ExpectedEnforceLocalAnchorConstraintsEnabled()) {
+      trust = trust.WithEnforceAnchorConstraints()
+                  .WithRequireAnchorBasicConstraints();
+    }
+
+    return trust;
   }
 
  private:
   base::test::ScopedFeatureList feature_list_;
+  ScopedLocalAnchorConstraintsEnforcementForTesting
+      scoped_enforce_local_anchor_constraints_;
 };
 
 // Much of the Keychain API was marked deprecated as of the macOS 13 SDK.
@@ -451,11 +475,14 @@
         testing::Values(TrustStoreMac::TrustImplType::kSimple,
                         TrustStoreMac::TrustImplType::kDomainCacheFullCerts,
                         TrustStoreMac::TrustImplType::kKeychainCacheFullCerts),
-        testing::Values(true, false)),
+        testing::Bool(),
+        testing::Bool()),
     [](const testing::TestParamInfo<TrustStoreMacImplTest::ParamType>& info) {
-      return base::StrCat({TrustImplTypeToString(std::get<0>(info.param)),
-                           std::get<1>(info.param) ? "TrustedLeafSupported"
-                                                   : "TrustAnchorOnly"});
+      return base::StrCat(
+          {TrustImplTypeToString(std::get<0>(info.param)),
+           std::get<1>(info.param) ? "TrustedLeafSupported" : "TrustAnchorOnly",
+           std::get<2>(info.param) ? "EnforceLocalAnchorConstraints"
+                                   : "NoLocalAnchorConstraints"});
     });
 
 }  // namespace net
diff --git a/net/cert/internal/trust_store_nss.cc b/net/cert/internal/trust_store_nss.cc
index 4cf9659..53d8113e 100644
--- a/net/cert/internal/trust_store_nss.cc
+++ b/net/cert/internal/trust_store_nss.cc
@@ -10,6 +10,7 @@
 #include "base/logging.h"
 #include "crypto/nss_util.h"
 #include "net/base/features.h"
+#include "net/cert/internal/trust_store_features.h"
 #include "net/cert/known_roots_nss.h"
 #include "net/cert/pki/cert_errors.h"
 #include "net/cert/pki/parsed_certificate.h"
@@ -108,6 +109,8 @@
 
   bool is_trusted_ca = false;
   bool is_trusted_leaf = false;
+  bool enforce_anchor_constraints =
+      IsLocalAnchorConstraintsEnforcementEnabled();
 
   // Determine if the certificate is a trust anchor.
   //
@@ -123,8 +126,20 @@
     // objects from the builtin token (system trust settings). Properly
     // handling this may require iterating all the slots and manually computing
     // the trust settings directly, rather than CERT_GetCertTrust.
-    if (!ignore_system_trust_settings_ || !IsKnownRoot(nss_cert.get())) {
+    if (ignore_system_trust_settings_) {
+      // Only trust the user roots, and apply the value of
+      // enforce_anchor_constraints.
+      if (!IsKnownRoot(nss_cert.get())) {
+        is_trusted_ca = true;
+      }
+    } else {
       is_trusted_ca = true;
+      if (enforce_anchor_constraints && IsKnownRoot(nss_cert.get())) {
+        // Don't enforce anchor constraints on the builtin roots. Needing to
+        // check IsKnownRoot for this condition isn't ideal, but this should be
+        // good enough for now.
+        enforce_anchor_constraints = false;
+      }
     }
   }
 
@@ -137,9 +152,13 @@
   }
 
   if (is_trusted_ca && is_trusted_leaf) {
-    return CertificateTrust::ForTrustAnchorOrLeaf();
+    return CertificateTrust::ForTrustAnchorOrLeaf()
+        .WithEnforceAnchorConstraints(enforce_anchor_constraints)
+        .WithEnforceAnchorExpiry(enforce_anchor_constraints);
   } else if (is_trusted_ca) {
-    return CertificateTrust::ForTrustAnchor();
+    return CertificateTrust::ForTrustAnchor()
+        .WithEnforceAnchorConstraints(enforce_anchor_constraints)
+        .WithEnforceAnchorExpiry(enforce_anchor_constraints);
   } else if (is_trusted_leaf) {
     return CertificateTrust::ForTrustedLeaf();
   }
diff --git a/net/cert/internal/trust_store_nss_unittest.cc b/net/cert/internal/trust_store_nss_unittest.cc
index 6df62e4..1919c82 100644
--- a/net/cert/internal/trust_store_nss_unittest.cc
+++ b/net/cert/internal/trust_store_nss_unittest.cc
@@ -15,10 +15,12 @@
 #include "crypto/nss_util_internal.h"
 #include "crypto/scoped_test_nss_db.h"
 #include "net/base/features.h"
+#include "net/cert/internal/trust_store_features.h"
 #include "net/cert/known_roots_nss.h"
 #include "net/cert/pki/cert_issuer_source_sync_unittest.h"
 #include "net/cert/pki/parsed_certificate.h"
 #include "net/cert/pki/test_helpers.h"
+#include "net/cert/pki/trust_store.h"
 #include "net/cert/scoped_nss_types.h"
 #include "net/cert/test_root_certs.h"
 #include "net/cert/x509_util.h"
@@ -30,10 +32,6 @@
 
 namespace {
 
-constexpr CertificateTrust ExpectedTrustForAnchor() {
-  return CertificateTrust::ForTrustAnchor();
-}
-
 // Returns true if the provided slot looks like a built-in root.
 bool IsBuiltInRootSlot(PK11SlotInfo* slot) {
   if (!PK11_IsPresent(slot) || !PK11_HasRootCerts(slot))
@@ -105,8 +103,12 @@
 
 class TrustStoreNSSTestBase : public ::testing::Test {
  public:
-  explicit TrustStoreNSSTestBase(bool trusted_leaf_support)
-      : trusted_leaf_support_(trusted_leaf_support) {
+  explicit TrustStoreNSSTestBase(bool trusted_leaf_support,
+                                 bool enforce_local_anchor_constraints)
+      : trusted_leaf_support_(trusted_leaf_support),
+        enforce_local_anchor_constraints_(enforce_local_anchor_constraints),
+        scoped_enforce_local_anchor_constraints_(
+            enforce_local_anchor_constraints) {
     if (trusted_leaf_support) {
       feature_list_.InitAndEnableFeature(
           features::kTrustStoreTrustedLeafSupport);
@@ -116,20 +118,43 @@
     }
   }
 
-  TrustStoreNSSTestBase() : TrustStoreNSSTestBase(true) {}
+  TrustStoreNSSTestBase() : TrustStoreNSSTestBase(true, true) {}
 
-  bool IsTrustedLeafSupportEnabled() const { return trusted_leaf_support_; }
+  bool ExpectedTrustedLeafSupportEnabled() const {
+    return trusted_leaf_support_;
+  }
+
+  bool ExpectedEnforceLocalAnchorConstraintsEnabled() const {
+    return enforce_local_anchor_constraints_;
+  }
+
+  CertificateTrust ExpectedTrustForBuiltinAnchor() const {
+    return CertificateTrust::ForTrustAnchor();
+  }
+
+  CertificateTrust ExpectedTrustForAnchor() const {
+    CertificateTrust trust = CertificateTrust::ForTrustAnchor();
+    if (ExpectedEnforceLocalAnchorConstraintsEnabled()) {
+      trust = trust.WithEnforceAnchorConstraints().WithEnforceAnchorExpiry();
+    }
+    return trust;
+  }
 
   CertificateTrust ExpectedTrustForAnchorOrLeaf() const {
-    if (IsTrustedLeafSupportEnabled()) {
-      return CertificateTrust::ForTrustAnchorOrLeaf();
+    CertificateTrust trust;
+    if (ExpectedTrustedLeafSupportEnabled()) {
+      trust = CertificateTrust::ForTrustAnchorOrLeaf();
     } else {
-      return CertificateTrust::ForTrustAnchor();
+      trust = CertificateTrust::ForTrustAnchor();
     }
+    if (ExpectedEnforceLocalAnchorConstraintsEnabled()) {
+      trust = trust.WithEnforceAnchorConstraints().WithEnforceAnchorExpiry();
+    }
+    return trust;
   }
 
   CertificateTrust ExpectedTrustForLeaf() const {
-    if (IsTrustedLeafSupportEnabled()) {
+    if (ExpectedTrustedLeafSupportEnabled()) {
       return CertificateTrust::ForTrustedLeaf();
     } else {
       return CertificateTrust::ForUnspecified();
@@ -313,6 +338,9 @@
 
   base::test::ScopedFeatureList feature_list_;
   const bool trusted_leaf_support_;
+  const bool enforce_local_anchor_constraints_;
+  ScopedLocalAnchorConstraintsEnforcementForTesting
+      scoped_enforce_local_anchor_constraints_;
 
   std::shared_ptr<const ParsedCertificate> oldroot_;
   std::shared_ptr<const ParsedCertificate> newroot_;
@@ -386,7 +414,7 @@
 TEST_P(TrustStoreNSSTestWithSlotFilterType, TrustAllowedForBuiltinRootCerts) {
   auto builtin_root_cert = GetASSLTrustedBuiltinRoot();
   ASSERT_TRUE(builtin_root_cert);
-  EXPECT_TRUE(HasTrust({builtin_root_cert}, ExpectedTrustForAnchor()));
+  EXPECT_TRUE(HasTrust({builtin_root_cert}, ExpectedTrustForBuiltinAnchor()));
 }
 
 INSTANTIATE_TEST_SUITE_P(
@@ -399,9 +427,11 @@
 // Tests a TrustStoreNSS that ignores system root certs.
 class TrustStoreNSSTestIgnoreSystemCerts
     : public TrustStoreNSSTestBase,
-      public testing::WithParamInterface<bool> {
+      public testing::WithParamInterface<std::tuple<bool, bool>> {
  public:
-  TrustStoreNSSTestIgnoreSystemCerts() : TrustStoreNSSTestBase(GetParam()) {}
+  TrustStoreNSSTestIgnoreSystemCerts()
+      : TrustStoreNSSTestBase(std::get<0>(GetParam()),
+                              std::get<1>(GetParam())) {}
   ~TrustStoreNSSTestIgnoreSystemCerts() override = default;
 
   std::unique_ptr<TrustStoreNSS> CreateTrustStoreNSS() override {
@@ -445,18 +475,23 @@
 INSTANTIATE_TEST_SUITE_P(
     All,
     TrustStoreNSSTestIgnoreSystemCerts,
-    testing::Values(true, false),
+    testing::Combine(testing::Bool(), testing::Bool()),
     [](const testing::TestParamInfo<
         TrustStoreNSSTestIgnoreSystemCerts::ParamType>& info) {
-      return info.param ? "TrustedLeafSupported" : "TrustAnchorOnly";
+      return std::string(std::get<0>(info.param) ? "TrustedLeafSupported"
+                                                 : "TrustAnchorOnly") +
+             (std::get<1>(info.param) ? "EnforceLocalAnchorConstraints"
+                                      : "NoLocalAnchorConstraints");
     });
 
 // Tests a TrustStoreNSS that does not filter which certificates
 class TrustStoreNSSTestWithoutSlotFilter
     : public TrustStoreNSSTestBase,
-      public testing::WithParamInterface<bool> {
+      public testing::WithParamInterface<std::tuple<bool, bool>> {
  public:
-  TrustStoreNSSTestWithoutSlotFilter() : TrustStoreNSSTestBase(GetParam()) {}
+  TrustStoreNSSTestWithoutSlotFilter()
+      : TrustStoreNSSTestBase(std::get<0>(GetParam()),
+                              std::get<1>(GetParam())) {}
 
   ~TrustStoreNSSTestWithoutSlotFilter() override = default;
 
@@ -577,10 +612,13 @@
 INSTANTIATE_TEST_SUITE_P(
     All,
     TrustStoreNSSTestWithoutSlotFilter,
-    testing::Values(true, false),
+    testing::Combine(testing::Bool(), testing::Bool()),
     [](const testing::TestParamInfo<
         TrustStoreNSSTestWithoutSlotFilter::ParamType>& info) {
-      return info.param ? "TrustedLeafSupported" : "TrustAnchorOnly";
+      return std::string(std::get<0>(info.param) ? "TrustedLeafSupported"
+                                                 : "TrustAnchorOnly") +
+             (std::get<1>(info.param) ? "EnforceLocalAnchorConstraints"
+                                      : "NoLocalAnchorConstraints");
     });
 
 // Tests for a TrustStoreNSS which does not allow certificates on user slots
diff --git a/net/cert/internal/trust_store_win.cc b/net/cert/internal/trust_store_win.cc
index 1cb8d2d..ab059b7 100644
--- a/net/cert/internal/trust_store_win.cc
+++ b/net/cert/internal/trust_store_win.cc
@@ -12,6 +12,7 @@
 #include "base/strings/string_number_conversions.h"
 #include "base/threading/scoped_blocking_call.h"
 #include "net/base/features.h"
+#include "net/cert/internal/trust_store_features.h"
 #include "net/cert/pki/cert_errors.h"
 #include "net/cert/pki/parsed_certificate.h"
 #include "net/cert/x509_util.h"
@@ -329,9 +330,14 @@
             // anchors or trusted leafs (if self-signed).
             return CertificateTrust::ForTrustAnchorOrLeaf()
                 .WithEnforceAnchorExpiry()
+                .WithEnforceAnchorConstraints(
+                    IsLocalAnchorConstraintsEnforcementEnabled())
                 .WithRequireLeafSelfSigned();
           } else {
-            return CertificateTrust::ForTrustAnchor().WithEnforceAnchorExpiry();
+            return CertificateTrust::ForTrustAnchor()
+                .WithEnforceAnchorExpiry()
+                .WithEnforceAnchorConstraints(
+                    IsLocalAnchorConstraintsEnforcementEnabled());
           }
         }
       }
diff --git a/net/cert/internal/trust_store_win_unittest.cc b/net/cert/internal/trust_store_win_unittest.cc
index 015d0189..284cfcd 100644
--- a/net/cert/internal/trust_store_win_unittest.cc
+++ b/net/cert/internal/trust_store_win_unittest.cc
@@ -15,6 +15,7 @@
 #include "crypto/scoped_capi_types.h"
 #include "net/base/features.h"
 #include "net/cert/cert_net_fetcher.h"
+#include "net/cert/internal/trust_store_features.h"
 #include "net/cert/pki/cert_errors.h"
 #include "net/cert/pki/parsed_certificate.h"
 #include "net/cert/pki/test_helpers.h"
@@ -52,10 +53,13 @@
   return ::testing::AssertionSuccess();
 }
 
-class TrustStoreWinTest : public testing::TestWithParam<bool> {
+class TrustStoreWinTest
+    : public testing::TestWithParam<std::tuple<bool, bool>> {
  public:
-  TrustStoreWinTest() {
-    if (IsTrustedLeafSupportEnabled()) {
+  TrustStoreWinTest()
+      : scoped_enforce_local_anchor_constraints_(
+            ExpectedEnforceLocalAnchorConstraintsEnabled()) {
+    if (ExpectedTrustedLeafSupportEnabled()) {
       feature_list_.InitAndEnableFeature(
           features::kTrustStoreTrustedLeafSupport);
     } else {
@@ -75,20 +79,31 @@
     ASSERT_TRUE(ParseCertFromFile("multi-root-F-by-E.pem", &f_by_e_));
   }
 
-  bool IsTrustedLeafSupportEnabled() const { return GetParam(); }
+  bool ExpectedTrustedLeafSupportEnabled() const {
+    return std::get<0>(GetParam());
+  }
+
+  bool ExpectedEnforceLocalAnchorConstraintsEnabled() const {
+    return std::get<1>(GetParam());
+  }
 
   CertificateTrust ExpectedTrustForAnchor() const {
-    if (IsTrustedLeafSupportEnabled()) {
+    if (ExpectedTrustedLeafSupportEnabled()) {
       return CertificateTrust::ForTrustAnchorOrLeaf()
           .WithEnforceAnchorExpiry()
+          .WithEnforceAnchorConstraints(
+              ExpectedEnforceLocalAnchorConstraintsEnabled())
           .WithRequireLeafSelfSigned();
     } else {
-      return CertificateTrust::ForTrustAnchor().WithEnforceAnchorExpiry();
+      return CertificateTrust::ForTrustAnchor()
+          .WithEnforceAnchorExpiry()
+          .WithEnforceAnchorConstraints(
+              ExpectedEnforceLocalAnchorConstraintsEnabled());
     }
   }
 
   CertificateTrust ExpectedTrustForPeer() const {
-    if (IsTrustedLeafSupportEnabled()) {
+    if (ExpectedTrustedLeafSupportEnabled()) {
       return CertificateTrust::ForTrustedLeaf().WithRequireLeafSelfSigned();
     } else {
       return CertificateTrust::ForUnspecified();
@@ -143,6 +158,8 @@
 
  private:
   base::test::ScopedFeatureList feature_list_;
+  ScopedLocalAnchorConstraintsEnforcementForTesting
+      scoped_enforce_local_anchor_constraints_;
 };
 
 TEST_P(TrustStoreWinTest, GetTrustInitializationError) {
@@ -387,9 +404,12 @@
 INSTANTIATE_TEST_SUITE_P(
     All,
     TrustStoreWinTest,
-    testing::Values(true, false),
+    testing::Combine(testing::Bool(), testing::Bool()),
     [](const testing::TestParamInfo<TrustStoreWinTest::ParamType>& info) {
-      return info.param ? "TrustedLeafSupported" : "TrustAnchorOnly";
+      return std::string(std::get<0>(info.param) ? "TrustedLeafSupported"
+                                                 : "TrustAnchorOnly") +
+             (std::get<1>(info.param) ? "EnforceLocalAnchorConstraints"
+                                      : "NoLocalAnchorConstraints");
     });
 
 }  // namespace
diff --git a/net/data/ssl/certificates/crit-codeSigning-chain.pem b/net/data/ssl/certificates/crit-codeSigning-chain.pem
index 857ba47..55d1e8c 100644
--- a/net/data/ssl/certificates/crit-codeSigning-chain.pem
+++ b/net/data/ssl/certificates/crit-codeSigning-chain.pem
@@ -1,62 +1,63 @@
------BEGIN RSA PRIVATE KEY-----
-MIIEogIBAAKCAQEArb3ubpJlolhcRduBpP7VEoyb0BbLuEdRBEN+WeuuUmFCUjcE
-srXy9HtlfRRNk0fPFr931k1gmjuoqzz2zJLEd3KitsRo9itnimi2u27pIUr2C912
-4Gp4zq1tkXTQXiNID7VMpNdkpLeVSNHhgtNzZ7BjffOi3guU4SF74DuSwtorXnmE
-CVcxh/TDPwniccahq8vnk3fsE2/eWMmPC2WJTKmMPtaoGis3M6S+gsuvQqnTfe7v
-0IH8noDf56XB3+Efkz6NoxcAdPWkcvFG0IcwptEWkfbj3qMsmbuXEFJjbiDLFQGP
-7gn1Qoa7xd5JfT7FNjPugCLwCntaxagE4gn6mQIDAQABAoIBAE67HMraaFfy9o1p
-dQxRtjhbo1fenJ57Islt1FnnJO2LyUP8TpK1RM1pBYpyoty007EZPrLMCZSaqEpA
-rA30DDecqQNtaarz7E2aOgQhtF8z2t9xkicL2Ia/rEEX0Dx4fHUx0PN57898qZeA
-FFL2gvtfwRYYS5uXX/XM+JeaJ11IRCZTJCRkaoK6Ffyu0pi0yCg/WS852XlAuTWp
-sguJRydFtt2xW8qF9Ua8mVVCVEo4olaopvGECu1cauj3Oxydk0vLc9lABPRQrS3u
-0nTdovZiuk0EPy4s8pyFTo9isqSLq79HWi4v/ByXeCLRp6ZpjR7s3s/3SEyG2NG/
-6nMtmiECgYEA5uWN232qNv0hGgQLzTUPwk8VyGbpSAoETYyT8nwc7o4ZS/tghvXI
-4JFxyQFEoMxeMmVWS8FgN6DGnDjjXQGD40ZO6UkwFX48E07jE8sh767cCL80BO36
-q8DSdicOUoYWvQ6WJw/RUNggIFzOZhjKlBw7Yjf5/buRkkOAJ3xk3nUCgYEAwKGd
-Es0UjNU6/iYtNOlYlAxKQS8d+EwnxelWbBY6h89Uquqnt36+Bgv8ceDMx6JwjZGq
-b6olv4f3HrmVwighEWUiiccniEuMx/50XInISHfHZV8OsQ9p5bUI0vHo3XNsegbg
-JdeQqxC88JeCTrq7Fm09wKIZ8ft6WR0+DSAzbxUCgYAddei1usD/JykUErQWyNBr
-8H9NBKR7Rpvp8SfnZqKiZYsgwMA+OBobXTNxfDHvemQCdh+eptvJ/T+aK0AHW+wi
-EZR7+5ShCWxM4mHi4qY/2MXGb+8JOfwj8gRogu825Fj+YmASN9hzQkBHINBNApjG
-cRu6mn3RPB+E1AwD/cE5CQKBgFDZA8XKUR+ytunIOB0G+uhYKConjlqSC/disaT3
-x2UMvapmhjHbfgGnsjJReWEoajjgtDndna4/cJZyqcotcYONgOt2rL7lhpbB0zCr
-m2Xe7886ED58C6QfUS7H3UZklVi53gXD7bH+em44CLbmZHNLMinRXzZSp80TGuID
-a9LpAoGAUjwIqx5+5xS5uTScPShuxTVh5kTWMhBLVRyh/evzmb9ALk6fUyid8Xm3
-q5E6l49iRsxHFbW2EQ9COp9CtZ3tgJbsivyeMazjlwpZhj1VotzrIHOSN0bup0KE
-qX9WYiR6GU99/vFY8BKOBkVI6AdOfkPm5aWnMUzpceBtJHCJQdQ=
------END RSA PRIVATE KEY-----
+-----BEGIN PRIVATE KEY-----
+MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDo54gDQT1qecev
+bDsUAnrPdTnIFp0Da4tLIr+UCTyfMoF5ZEVfH7PDaS5pydczRr3O5SR7+VUNOaQc
+nU3E2IWgb0tD/AeI6x6Q/rjFMM7DsNvHjvzuX9xjhwGeM5KVNb9jv59g7TmwYvoF
+y6QUwEpV+gySaAfGQi0GsviEIyZR3kWqzalHk8EetSsEql7BEVjeAEoz5raMLukF
+qfJ9lOVkvzPMUXQVEROlCzWePHe7lXkep9LUNIdguu92Qt2458BUgNR703RsJxy3
+HU0dz1pe83Pu4MrQk65Di851HAOYykkZ0NugmWFZn4zHit3KDJZJZ4cBiaq8ftgh
+xPvpH2m1AgMBAAECggEAATjJG+r0Wmlf1MOlZbIhLEQil0XvZ3jXxGuj0s2khllu
+SZtReuyxsJzI/fiLD/h0nK/6gyKZmjOBU3lByP6KQYkrUQcaGfC90x3nSCp3zksV
+a3bu6g+Hri7BA+tBukH4CmnOW9Dt2qmcG/CFNC+958iPr6FmKLv8hkC937Uf9cL6
+nD29wvDuzRibDRV/yDk/dhbc2QTpDp8kkjDBI/Hpibj6GeQra5YDDafqs78w0jQh
+jOl6+E4sn79+X4ZccOCXfgcXsa8gZcskIXdVGhtWnYGWdxcyXDhJrZzKr2tEhzAw
+QFhpHjlGL9l6P/lDyn0qMxp9IEFLcWJk2YTrcaV4wQKBgQD4VLNJj+l2x4O7qGpb
+X5GRYLBCqI76iXLxUaEQJxZpd5qngWKaSJyS9IO82k7fcCgsbhQQnXC59UIlUQ6A
+YLPO4Nj/D6V8ewttCIbiRtddTZBj9wIHJFhOfcRoDBSTskhaJ4kVB1Nh+YchQf+8
+P6/wwkTcn9FRohdeHikYDnpqIQKBgQDwGN6pGMbrnIKv/+HHC55QPshUYjKIjU98
+Bd7TU3Kj8tSpxiKKXY9RtkAfzoSrgPzu5SJ3bSgXBXz+xOtN9b9z5beQHJyufHaO
+NaVQ4ZStMjo3afXMNj7UUd9ZbFuH2K+K1FjXy99Ab5wN/u5snRiErh2KS/w3mxze
+6kOComAVFQKBgQC7vP0WHhB4VfmHg5l0ntmkOJ7IpjoBuqwFOJs1ZPeSoHNxM2Xi
+EgcdKnH18m0yis40WLwem4g/beWl5JO8Bl+phV9H5QJNC5Dly059/uSOizcf+/uy
+fo2sOXSk3I0p49zDG6SNG060gTrhr82w+cz/jT8WNFTBDHPyGYcjwr5VQQKBgEHU
+f9BbU8csHYUGIrCBlgGohSLl3bclD6MQtPy6R5d+MCLwiW3oozAjSUevRx8C+dbC
+ioW2LyTIw3HTKjUw6TJszLy9q5QH2jW5rb8UasBmIiIpclRwlx9950BMfngryE3H
+VSit5GN1dpM7z8GF/T/7wWu208unQu43yxTZUoDVAoGBANzJZijbb2JYdvDSi5aX
+Akv5droXiJ9DG7wew7xGJcGRdmpRw+mas2j5hdIXwfKeN7yr8KFaYxv/DDqlTsv+
+sZPysvWc4TZ8NrNOL9VyAD/3QCjo9uBdd2rSssYMKHI8RXD+Jiold59AW3oMDwVk
+OW8+EuyR+eoq9HueaKbjlJ5O
+-----END PRIVATE KEY-----
 Certificate:
     Data:
-        Version: 1 (0x0)
+        Version: 3 (0x2)
         Serial Number: 2 (0x2)
-    Signature Algorithm: sha256WithRSAEncryption
+        Signature Algorithm: sha256WithRSAEncryption
         Issuer: CN=2048 RSA Test Root CA
         Validity
-            Not Before: Aug 14 02:46:31 2014 GMT
-            Not After : Aug 11 02:46:31 2024 GMT
+            Not Before: Feb 17 17:08:51 2023 GMT
+            Not After : Feb 14 17:08:51 2033 GMT
         Subject: C=US, ST=California, L=Mountain View, O=Test CA, CN=127.0.0.1
         Subject Public Key Info:
             Public Key Algorithm: rsaEncryption
                 Public-Key: (2048 bit)
                 Modulus:
-                    00:ad:bd:ee:6e:92:65:a2:58:5c:45:db:81:a4:fe:
-                    d5:12:8c:9b:d0:16:cb:b8:47:51:04:43:7e:59:eb:
-                    ae:52:61:42:52:37:04:b2:b5:f2:f4:7b:65:7d:14:
-                    4d:93:47:cf:16:bf:77:d6:4d:60:9a:3b:a8:ab:3c:
-                    f6:cc:92:c4:77:72:a2:b6:c4:68:f6:2b:67:8a:68:
-                    b6:bb:6e:e9:21:4a:f6:0b:dd:76:e0:6a:78:ce:ad:
-                    6d:91:74:d0:5e:23:48:0f:b5:4c:a4:d7:64:a4:b7:
-                    95:48:d1:e1:82:d3:73:67:b0:63:7d:f3:a2:de:0b:
-                    94:e1:21:7b:e0:3b:92:c2:da:2b:5e:79:84:09:57:
-                    31:87:f4:c3:3f:09:e2:71:c6:a1:ab:cb:e7:93:77:
-                    ec:13:6f:de:58:c9:8f:0b:65:89:4c:a9:8c:3e:d6:
-                    a8:1a:2b:37:33:a4:be:82:cb:af:42:a9:d3:7d:ee:
-                    ef:d0:81:fc:9e:80:df:e7:a5:c1:df:e1:1f:93:3e:
-                    8d:a3:17:00:74:f5:a4:72:f1:46:d0:87:30:a6:d1:
-                    16:91:f6:e3:de:a3:2c:99:bb:97:10:52:63:6e:20:
-                    cb:15:01:8f:ee:09:f5:42:86:bb:c5:de:49:7d:3e:
-                    c5:36:33:ee:80:22:f0:0a:7b:5a:c5:a8:04:e2:09:
-                    fa:99
+                    00:e8:e7:88:03:41:3d:6a:79:c7:af:6c:3b:14:02:
+                    7a:cf:75:39:c8:16:9d:03:6b:8b:4b:22:bf:94:09:
+                    3c:9f:32:81:79:64:45:5f:1f:b3:c3:69:2e:69:c9:
+                    d7:33:46:bd:ce:e5:24:7b:f9:55:0d:39:a4:1c:9d:
+                    4d:c4:d8:85:a0:6f:4b:43:fc:07:88:eb:1e:90:fe:
+                    b8:c5:30:ce:c3:b0:db:c7:8e:fc:ee:5f:dc:63:87:
+                    01:9e:33:92:95:35:bf:63:bf:9f:60:ed:39:b0:62:
+                    fa:05:cb:a4:14:c0:4a:55:fa:0c:92:68:07:c6:42:
+                    2d:06:b2:f8:84:23:26:51:de:45:aa:cd:a9:47:93:
+                    c1:1e:b5:2b:04:aa:5e:c1:11:58:de:00:4a:33:e6:
+                    b6:8c:2e:e9:05:a9:f2:7d:94:e5:64:bf:33:cc:51:
+                    74:15:11:13:a5:0b:35:9e:3c:77:bb:95:79:1e:a7:
+                    d2:d4:34:87:60:ba:ef:76:42:dd:b8:e7:c0:54:80:
+                    d4:7b:d3:74:6c:27:1c:b7:1d:4d:1d:cf:5a:5e:f3:
+                    73:ee:e0:ca:d0:93:ae:43:8b:ce:75:1c:03:98:ca:
+                    49:19:d0:db:a0:99:61:59:9f:8c:c7:8a:dd:ca:0c:
+                    96:49:67:87:01:89:aa:bc:7e:d8:21:c4:fb:e9:1f:
+                    69:b5
                 Exponent: 65537 (0x10001)
         X509v3 extensions:
             X509v3 Subject Alternative Name: 
@@ -64,42 +65,46 @@
             X509v3 Basic Constraints: critical
                 CA:FALSE
             X509v3 Subject Key Identifier: 
-                C8:14:20:CB:A7:D1:AC:A1:7F:27:3E:72:47:1E:AF:50:EB:DF:CD:73
+                CB:86:F3:FC:03:8D:82:4B:C6:63:57:8A:E6:9F:86:55:10:D9:00:B7
             X509v3 Extended Key Usage: critical
                 Code Signing
+            X509v3 Authority Key Identifier: 
+                D5:28:55:87:C7:A3:BF:D7:C4:CE:BE:3D:01:D2:BE:8B:7C:E4:E2:E2
     Signature Algorithm: sha256WithRSAEncryption
-         18:5e:81:7e:31:f7:38:78:d2:2a:08:7f:57:60:a0:e1:b3:72:
-         7e:7f:8a:6f:76:bd:cb:56:74:14:9a:65:96:0e:99:1b:59:90:
-         4b:f1:d9:10:89:0a:5c:75:f3:01:1b:50:a5:00:b7:34:0f:d5:
-         5a:24:df:10:79:83:17:9b:82:bf:66:b0:4f:eb:53:d1:0b:8c:
-         01:b8:57:58:4f:31:f6:66:f3:89:ba:02:9c:fb:a4:1c:f8:e6:
-         5d:14:44:13:d9:74:be:b3:1a:1d:6b:35:77:b7:08:28:d3:60:
-         9f:ac:e5:72:57:20:03:2f:ce:5c:10:15:da:45:cd:d8:09:66:
-         4c:a2:44:cb:9a:bf:b3:2e:1b:44:36:62:2f:e2:64:8f:72:b8:
-         da:9a:fb:ef:f5:0e:04:39:d1:2d:9a:1e:bd:48:56:90:d3:3c:
-         f4:d5:89:07:9f:f6:a6:c4:d3:ff:d6:fe:67:b8:29:f8:93:36:
-         e0:4d:69:a3:1a:38:a0:ce:39:02:63:73:3d:dd:13:98:80:36:
-         d0:1e:a0:23:55:8d:59:3f:dd:12:3c:ee:af:f7:a3:e4:f2:e3:
-         57:b4:f5:f3:2a:e5:60:70:c1:88:89:f0:06:6d:de:c0:b1:cf:
-         a5:65:cc:a0:d3:41:f6:fb:4e:94:ba:17:0a:60:59:bb:ac:18:
-         d7:f6:9c:52
+    Signature Value:
+        64:ff:92:1d:66:c5:2b:6c:b8:d6:82:39:80:8e:ef:55:cd:bb:
+        6f:1c:16:20:a5:6b:90:fa:f3:20:c7:37:0d:7d:4f:da:f5:1f:
+        9c:dc:28:3e:39:56:a7:17:eb:3b:09:53:d7:1e:1b:a1:eb:24:
+        f7:7f:04:38:77:e1:e4:39:60:bf:24:13:37:05:8b:9f:36:94:
+        91:e4:fc:43:97:d6:0e:11:cb:ee:a9:f9:c1:05:6e:2f:ea:76:
+        af:f5:69:ae:06:97:5a:2d:97:0b:cd:3a:1d:01:30:26:ac:da:
+        f6:03:e4:df:32:08:64:81:ea:36:85:0c:03:41:6a:ef:10:6b:
+        2a:a4:cb:f6:59:9b:bf:fa:1b:5a:9e:05:33:b0:54:30:d1:79:
+        dd:77:6b:e0:c8:be:52:37:58:7e:b0:53:23:fe:62:ce:5c:bb:
+        b5:06:85:18:e0:08:08:cd:48:bc:1d:d7:cb:92:55:33:57:fe:
+        42:4a:81:0d:d3:ee:02:0a:e1:4b:d2:f1:de:81:5e:bb:fa:8b:
+        9f:3f:ec:0a:1a:30:0b:de:15:d3:75:5e:11:f8:a9:7e:4f:7d:
+        40:03:06:e4:2f:3b:e5:5e:ff:42:f5:e6:99:02:f3:26:c0:f7:
+        e2:b2:62:53:d3:e2:5b:2a:17:e3:78:5c:84:cc:14:bf:fa:0d:
+        ac:d7:d0:49
 -----BEGIN CERTIFICATE-----
-MIIDTjCCAjYCAQIwDQYJKoZIhvcNAQELBQAwIDEeMBwGA1UEAwwVMjA0OCBSU0Eg
-VGVzdCBSb290IENBMB4XDTE0MDgxNDAyNDYzMVoXDTI0MDgxMTAyNDYzMVowYDEL
-MAkGA1UEBhMCVVMxEzARBgNVBAgMCkNhbGlmb3JuaWExFjAUBgNVBAcMDU1vdW50
-YWluIFZpZXcxEDAOBgNVBAoMB1Rlc3QgQ0ExEjAQBgNVBAMMCTEyNy4wLjAuMTCC
-ASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAK297m6SZaJYXEXbgaT+1RKM
-m9AWy7hHUQRDflnrrlJhQlI3BLK18vR7ZX0UTZNHzxa/d9ZNYJo7qKs89sySxHdy
-orbEaPYrZ4potrtu6SFK9gvdduBqeM6tbZF00F4jSA+1TKTXZKS3lUjR4YLTc2ew
-Y33zot4LlOEhe+A7ksLaK155hAlXMYf0wz8J4nHGoavL55N37BNv3ljJjwtliUyp
-jD7WqBorNzOkvoLLr0Kp033u79CB/J6A3+elwd/hH5M+jaMXAHT1pHLxRtCHMKbR
-FpH2496jLJm7lxBSY24gyxUBj+4J9UKGu8XeSX0+xTYz7oAi8Ap7WsWoBOIJ+pkC
-AwEAAaNYMFYwDwYDVR0RBAgwBocEfwAAATAMBgNVHRMBAf8EAjAAMB0GA1UdDgQW
-BBTIFCDLp9GsoX8nPnJHHq9Q69/NczAWBgNVHSUBAf8EDDAKBggrBgEFBQcDAzAN
-BgkqhkiG9w0BAQsFAAOCAQEAGF6BfjH3OHjSKgh/V2Cg4bNyfn+Kb3a9y1Z0FJpl
-lg6ZG1mQS/HZEIkKXHXzARtQpQC3NA/VWiTfEHmDF5uCv2awT+tT0QuMAbhXWE8x
-9mbziboCnPukHPjmXRREE9l0vrMaHWs1d7cIKNNgn6zlclcgAy/OXBAV2kXN2Alm
-TKJEy5q/sy4bRDZiL+Jkj3K42pr77/UOBDnRLZoevUhWkNM89NWJB5/2psTT/9b+
-Z7gp+JM24E1poxo4oM45AmNzPd0TmIA20B6gI1WNWT/dEjzur/ej5PLjV7T18yrl
-YHDBiInwBm3ewLHPpWXMoNNB9vtOlLoXCmBZu6wY1/acUg==
+MIIDdDCCAlygAwIBAgIBAjANBgkqhkiG9w0BAQsFADAgMR4wHAYDVQQDDBUyMDQ4
+IFJTQSBUZXN0IFJvb3QgQ0EwHhcNMjMwMjE3MTcwODUxWhcNMzMwMjE0MTcwODUx
+WjBgMQswCQYDVQQGEwJVUzETMBEGA1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwN
+TW91bnRhaW4gVmlldzEQMA4GA1UECgwHVGVzdCBDQTESMBAGA1UEAwwJMTI3LjAu
+MC4xMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA6OeIA0E9annHr2w7
+FAJ6z3U5yBadA2uLSyK/lAk8nzKBeWRFXx+zw2kuacnXM0a9zuUke/lVDTmkHJ1N
+xNiFoG9LQ/wHiOsekP64xTDOw7Dbx4787l/cY4cBnjOSlTW/Y7+fYO05sGL6Bcuk
+FMBKVfoMkmgHxkItBrL4hCMmUd5Fqs2pR5PBHrUrBKpewRFY3gBKM+a2jC7pBany
+fZTlZL8zzFF0FRETpQs1njx3u5V5HqfS1DSHYLrvdkLduOfAVIDUe9N0bCcctx1N
+Hc9aXvNz7uDK0JOuQ4vOdRwDmMpJGdDboJlhWZ+Mx4rdygyWSWeHAYmqvH7YIcT7
+6R9ptQIDAQABo3kwdzAPBgNVHREECDAGhwR/AAABMAwGA1UdEwEB/wQCMAAwHQYD
+VR0OBBYEFMuG8/wDjYJLxmNXiuafhlUQ2QC3MBYGA1UdJQEB/wQMMAoGCCsGAQUF
+BwMDMB8GA1UdIwQYMBaAFNUoVYfHo7/XxM6+PQHSvot85OLiMA0GCSqGSIb3DQEB
+CwUAA4IBAQBk/5IdZsUrbLjWgjmAju9VzbtvHBYgpWuQ+vMgxzcNfU/a9R+c3Cg+
+OVanF+s7CVPXHhuh6yT3fwQ4d+HkOWC/JBM3BYufNpSR5PxDl9YOEcvuqfnBBW4v
+6nav9WmuBpdaLZcLzTodATAmrNr2A+TfMghkgeo2hQwDQWrvEGsqpMv2WZu/+hta
+ngUzsFQw0Xndd2vgyL5SN1h+sFMj/mLOXLu1BoUY4AgIzUi8HdfLklUzV/5CSoEN
+0+4CCuFL0vHegV67+oufP+wKGjAL3hXTdV4R+Kl+T31AAwbkLzvlXv9C9eaZAvMm
+wPfismJT0+JbKhfjeFyEzBS/+g2s19BJ
 -----END CERTIFICATE-----
diff --git a/net/data/ssl/certificates/eku-test-root.pem b/net/data/ssl/certificates/eku-test-root.pem
index 8b71c8c..c233e40 100644
--- a/net/data/ssl/certificates/eku-test-root.pem
+++ b/net/data/ssl/certificates/eku-test-root.pem
@@ -1,75 +1,77 @@
 Certificate:
     Data:
         Version: 3 (0x2)
-        Serial Number: 16790907397325450812 (0xe9054950da0fee3c)
-    Signature Algorithm: sha1WithRSAEncryption
-        Issuer: CN=2048 RSA Test Root CA
+        Serial Number:
+            55:72:d4:d8:57:fc:9c:cf:51:b8:6c:e7:a7:d1:a6:f3:b3:f1:25:fa
+        Signature Algorithm: sha256WithRSAEncryption
+        Issuer: CN = 2048 RSA Test Root CA
         Validity
-            Not Before: Aug 14 02:46:30 2014 GMT
-            Not After : Aug 11 02:46:30 2024 GMT
-        Subject: CN=2048 RSA Test Root CA
+            Not Before: Feb 17 17:08:50 2023 GMT
+            Not After : Feb 14 17:08:50 2033 GMT
+        Subject: CN = 2048 RSA Test Root CA
         Subject Public Key Info:
             Public Key Algorithm: rsaEncryption
                 Public-Key: (2048 bit)
                 Modulus:
-                    00:9c:22:79:a9:f8:68:98:41:fc:84:fb:f3:1f:73:
-                    0f:e0:70:35:c6:04:bb:8d:c0:48:ca:2a:f1:d3:16:
-                    3b:8a:71:77:68:ba:c1:48:3c:77:12:ef:4c:d1:1d:
-                    da:c1:98:dd:6d:46:50:b6:d7:a3:f3:7e:dc:8c:6b:
-                    d2:db:05:bb:77:55:bf:bd:a6:f0:d8:9d:ff:2c:04:
-                    1e:24:25:c2:0b:b8:e7:42:a1:c2:42:83:70:16:f1:
-                    4a:ef:c0:68:d8:81:f4:41:51:dd:70:31:5a:5f:47:
-                    6a:55:a8:3e:53:9a:71:ef:8e:d4:a6:79:8f:e4:a2:
-                    79:34:d7:ad:b1:07:0f:21:90:a7:ff:12:c2:9d:8f:
-                    26:19:66:c5:34:15:c9:d9:3f:9a:62:9f:37:bb:79:
-                    1e:9e:24:16:a6:01:28:c6:99:54:75:91:f3:c2:5a:
-                    87:7d:99:9e:23:f0:e0:f2:97:50:b4:3f:99:21:e7:
-                    b1:c4:87:58:5f:ba:9a:a5:4b:39:38:64:4b:ba:b8:
-                    67:fe:dd:c8:96:19:8a:7d:7c:44:fe:ff:39:15:61:
-                    fb:7e:fa:e7:43:18:5a:cf:96:1d:f4:ff:9d:26:68:
-                    27:c8:67:80:9a:ca:a3:f5:45:5f:fa:93:6c:65:53:
-                    e3:e8:84:c1:39:7c:e5:01:61:01:99:12:fd:02:5f:
-                    26:27
+                    00:d0:bc:d5:52:0a:01:4c:04:65:37:00:73:17:af:
+                    1a:53:d5:0a:b1:9c:c0:5b:20:ff:c0:b4:bd:f2:d4:
+                    b2:1d:2c:a5:c3:d7:eb:68:b7:f7:ab:2d:b3:6c:00:
+                    c2:7a:b0:f7:5e:b1:8b:29:92:79:08:37:f6:ec:46:
+                    2d:66:29:7c:66:8c:d4:54:f0:35:4d:ad:1e:e6:ef:
+                    4f:43:9d:96:2f:78:49:66:eb:ea:4f:b9:e9:eb:27:
+                    33:6e:03:1f:3b:c1:e4:6d:bb:a9:68:62:1c:fd:78:
+                    0d:57:f2:58:89:16:4f:8d:1d:2d:94:2a:ea:b8:1a:
+                    1b:57:b8:ad:8f:67:44:23:04:7f:c9:1a:1c:4a:f3:
+                    19:15:1a:39:a6:fc:c9:17:ae:5e:40:97:e2:b3:ec:
+                    02:4c:1b:65:0e:99:6a:d4:fe:c7:04:56:14:0c:8e:
+                    0f:ee:e8:fb:4e:63:c4:cf:4a:36:ac:f5:a6:f7:1d:
+                    26:07:e9:33:a9:f3:31:ee:1d:27:fc:15:10:a5:83:
+                    7e:59:26:6e:24:32:87:af:19:31:6c:99:24:05:d9:
+                    55:71:03:b8:b3:b6:75:f8:ee:04:ff:04:7f:b0:b6:
+                    57:ad:24:e3:a6:f9:46:2c:f7:b5:53:31:d6:49:c6:
+                    c3:1e:7f:a4:f0:6d:47:32:c9:8a:ff:f3:b6:7b:fd:
+                    d3:7f
                 Exponent: 65537 (0x10001)
         X509v3 extensions:
             X509v3 Basic Constraints: critical
                 CA:TRUE
             X509v3 Subject Key Identifier: 
-                51:48:E1:39:42:1C:AC:B2:1D:E5:45:A1:D1:79:16:32:81:F8:FE:5E
+                D5:28:55:87:C7:A3:BF:D7:C4:CE:BE:3D:01:D2:BE:8B:7C:E4:E2:E2
             X509v3 Key Usage: critical
                 Certificate Sign, CRL Sign
-    Signature Algorithm: sha1WithRSAEncryption
-         2f:65:1c:d8:d7:7d:2d:3d:a6:21:e6:18:cb:c5:2c:d1:b7:61:
-         77:f6:dc:8e:c0:18:7b:55:f8:ad:09:43:47:c9:2a:c5:00:80:
-         ad:9c:61:77:d2:00:27:c2:2f:af:d8:60:30:a1:e1:0c:c3:f0:
-         fb:32:5d:33:be:0a:44:d7:fc:59:9e:18:58:9e:82:56:0a:84:
-         64:8f:a6:15:a8:e8:7f:6e:7d:97:b0:7a:df:25:3c:98:ce:d8:
-         b6:de:72:3d:d9:a5:71:53:be:a6:a1:9d:f9:6c:10:16:ef:49:
-         39:5c:58:0d:b7:40:63:6e:b9:71:41:5a:66:6e:fe:89:70:7b:
-         e5:6f:98:96:64:43:fe:37:90:c6:49:ba:1b:84:58:da:bc:31:
-         bd:c1:cd:ee:1a:53:62:44:d1:61:99:b9:06:43:2b:d4:75:4c:
-         7f:af:6e:d3:2f:44:75:95:2c:0d:65:5e:a5:e8:d5:0d:9e:89:
-         cc:2c:0a:5e:96:4b:0a:95:56:bc:18:98:14:d2:6f:64:76:ff:
-         e6:9e:ce:ec:66:cb:26:fa:f3:8c:a4:e6:82:fb:24:cb:4d:38:
-         9d:54:7b:12:0c:15:63:c0:75:4a:aa:cd:ac:6b:3e:86:d0:ab:
-         2e:9c:48:55:e2:8d:9e:b4:fa:eb:e0:7f:a1:b5:d6:4c:13:9c:
-         01:5a:03:1b
+    Signature Algorithm: sha256WithRSAEncryption
+    Signature Value:
+        17:28:d3:4e:0f:70:db:86:b8:08:e2:0d:92:d1:4c:f6:32:70:
+        52:6b:3f:1f:96:a0:bd:a4:d4:f8:ed:b5:a4:9e:0a:ef:45:09:
+        e4:1f:8a:af:50:2d:03:4e:1c:ed:46:71:3d:91:d6:a9:0f:8f:
+        bb:4f:38:a4:3c:18:2f:ff:1f:07:9e:17:06:a6:9a:f9:4d:01:
+        f6:79:c9:aa:fe:01:90:79:dd:d3:eb:6d:ea:0b:b9:6c:df:9c:
+        1f:4f:31:25:70:71:58:9c:62:6a:5a:85:7d:8f:20:ae:97:d4:
+        e0:69:8a:79:4d:48:34:fe:c0:99:98:c3:33:ce:f3:07:80:c2:
+        4b:95:a3:2e:ba:cb:ee:d7:4d:a3:e1:88:9e:71:8b:64:47:83:
+        02:02:72:b0:46:ca:4f:e3:b1:f4:2c:f1:65:58:09:7a:81:18:
+        b7:35:55:50:6a:37:0a:0f:87:d3:d2:b2:2f:6f:93:e1:7d:e2:
+        ca:50:1f:a9:a4:6d:bc:40:1a:6e:d4:55:5e:99:e0:9c:77:7c:
+        b0:06:d3:7b:0b:cb:a2:9a:3a:9a:06:b6:59:3d:50:d5:d0:7b:
+        e0:51:c4:88:38:24:ba:80:3c:f0:42:07:24:c3:5c:e2:b5:6e:
+        bf:8b:37:b1:df:e9:7e:6e:33:d6:b2:af:d5:51:62:eb:b0:5a:
+        52:54:52:a3
 -----BEGIN CERTIFICATE-----
-MIIDBTCCAe2gAwIBAgIJAOkFSVDaD+48MA0GCSqGSIb3DQEBBQUAMCAxHjAcBgNV
-BAMMFTIwNDggUlNBIFRlc3QgUm9vdCBDQTAeFw0xNDA4MTQwMjQ2MzBaFw0yNDA4
-MTEwMjQ2MzBaMCAxHjAcBgNVBAMMFTIwNDggUlNBIFRlc3QgUm9vdCBDQTCCASIw
-DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAJwiean4aJhB/IT78x9zD+BwNcYE
-u43ASMoq8dMWO4pxd2i6wUg8dxLvTNEd2sGY3W1GULbXo/N+3Ixr0tsFu3dVv72m
-8Nid/ywEHiQlwgu450KhwkKDcBbxSu/AaNiB9EFR3XAxWl9HalWoPlOace+O1KZ5
-j+SieTTXrbEHDyGQp/8Swp2PJhlmxTQVydk/mmKfN7t5Hp4kFqYBKMaZVHWR88Ja
-h32ZniPw4PKXULQ/mSHnscSHWF+6mqVLOThkS7q4Z/7dyJYZin18RP7/ORVh+376
-50MYWs+WHfT/nSZoJ8hngJrKo/VFX/qTbGVT4+iEwTl85QFhAZkS/QJfJicCAwEA
-AaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUUUjhOUIcrLId5UWh0XkW
-MoH4/l4wDgYDVR0PAQH/BAQDAgEGMA0GCSqGSIb3DQEBBQUAA4IBAQAvZRzY130t
-PaYh5hjLxSzRt2F39tyOwBh7VfitCUNHySrFAICtnGF30gAnwi+v2GAwoeEMw/D7
-Ml0zvgpE1/xZnhhYnoJWCoRkj6YVqOh/bn2XsHrfJTyYzti23nI92aVxU76moZ35
-bBAW70k5XFgNt0BjbrlxQVpmbv6JcHvlb5iWZEP+N5DGSbobhFjavDG9wc3uGlNi
-RNFhmbkGQyvUdUx/r27TL0R1lSwNZV6l6NUNnonMLApelksKlVa8GJgU0m9kdv/m
-ns7sZssm+vOMpOaC+yTLTTidVHsSDBVjwHVKqs2saz6G0KsunEhV4o2etPrr4H+h
-tdZME5wBWgMb
+MIIDEDCCAfigAwIBAgIUVXLU2Ff8nM9RuGznp9Gm87PxJfowDQYJKoZIhvcNAQEL
+BQAwIDEeMBwGA1UEAwwVMjA0OCBSU0EgVGVzdCBSb290IENBMB4XDTIzMDIxNzE3
+MDg1MFoXDTMzMDIxNDE3MDg1MFowIDEeMBwGA1UEAwwVMjA0OCBSU0EgVGVzdCBS
+b290IENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0LzVUgoBTARl
+NwBzF68aU9UKsZzAWyD/wLS98tSyHSylw9fraLf3qy2zbADCerD3XrGLKZJ5CDf2
+7EYtZil8ZozUVPA1Ta0e5u9PQ52WL3hJZuvqT7np6yczbgMfO8HkbbupaGIc/XgN
+V/JYiRZPjR0tlCrquBobV7itj2dEIwR/yRocSvMZFRo5pvzJF65eQJfis+wCTBtl
+Dplq1P7HBFYUDI4P7uj7TmPEz0o2rPWm9x0mB+kzqfMx7h0n/BUQpYN+WSZuJDKH
+rxkxbJkkBdlVcQO4s7Z1+O4E/wR/sLZXrSTjpvlGLPe1UzHWScbDHn+k8G1HMsmK
+//O2e/3TfwIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBTVKFWH
+x6O/18TOvj0B0r6LfOTi4jAOBgNVHQ8BAf8EBAMCAQYwDQYJKoZIhvcNAQELBQAD
+ggEBABco004PcNuGuAjiDZLRTPYycFJrPx+WoL2k1PjttaSeCu9FCeQfiq9QLQNO
+HO1GcT2R1qkPj7tPOKQ8GC//HweeFwammvlNAfZ5yar+AZB53dPrbeoLuWzfnB9P
+MSVwcVicYmpahX2PIK6X1OBpinlNSDT+wJmYwzPO8weAwkuVoy66y+7XTaPhiJ5x
+i2RHgwICcrBGyk/jsfQs8WVYCXqBGLc1VVBqNwoPh9PSsi9vk+F94spQH6mkbbxA
+Gm7UVV6Z4Jx3fLAG03sLy6KaOpoGtlk9UNXQe+BRxIg4JLqAPPBCByTDXOK1br+L
+N7Hf6X5uM9ayr9VRYuuwWlJUUqM=
 -----END CERTIFICATE-----
diff --git a/net/data/ssl/certificates/non-crit-codeSigning-chain.pem b/net/data/ssl/certificates/non-crit-codeSigning-chain.pem
index d09f1cf9..6c539a6 100644
--- a/net/data/ssl/certificates/non-crit-codeSigning-chain.pem
+++ b/net/data/ssl/certificates/non-crit-codeSigning-chain.pem
@@ -1,62 +1,63 @@
------BEGIN RSA PRIVATE KEY-----
-MIIEpQIBAAKCAQEA2f3ZUc459VPIfpNTUI40YfF1kwO+0B+mfKA74arroenNaykU
-dR6azriRbYUzPtjLbIuu/NDXBc4Msv47X9EVTVs0ozjMgOOcTGVitrE/4Sww9anT
-+VBWU9Tdp5ZRIRsZPvZzyEpb/MxOJKHX0JGsCPfpBJxRWQUNVChh0NVkOJyAMZiG
-gmGkSeg6/d9xNy/2rNoIpNgfnLEhu7FxyB0940wQYA5prnyL/S/qfab4V40LuGZX
-CrvWQ+twkx2rpQb4HRTx2YGtK/5YSh41hEoGThY/SZtXFhMtQyRSa6sKuhT2YaWH
-MaEb9jbIeq3POXovhTSecA0Tw7bG0CtZEzwZVQIDAQABAoIBAQClUTsPLAuOPmTN
-gSLs83tMT9avkGaT5XzYBJiFEp8yImJTg0rtazFR1m0Llrl/TuAuyFwDhMmcsF+3
-GtCiYKj8ClAH/JoyoOq2kSjkjdV5CY9zrsB/0Wo2lzcl0fxi0+84baTu312VgMc+
-RrKpjN/fyUqg4X9buFYcXaeYvUwNFVO7SWc1PIZO4AEqf1eYS8nfjShAiYFUj7cX
-5rg8SBabVyrvCnvBWXc/DVnAUqbOWF3R17om3uRWY4tiVI9qpvi8RWQDTP3f/hLw
-SqlLJEAr9w0x7VHNr5EN1HC+TcbQlW73dVTuHY8SQVRxzyde3BHB7/YOaqTt6YKK
-wSxqxrG9AoGBAPrU0WZ4webJv/cTfUDvJfgrkWphBhnT8/w5wSH0zBuRONktT6oX
-3y9Hjy+HR/+uRQO0Nywbb3+ZKkZeUUWylu1FCkwO/zLlRefNnHV2CRBoMnFgJtoY
-TttUUNzzJmExjTEu3qPJ8TbzTA8vdk+387g9Rzbb3mjbcwvKxkjSuGhjAoGBAN57
-y5adtf551eyjRixEAQVFe5Iq0dUrp5T2bND4tHZzm6IYzbPEos8CHTs6tbHZGbws
-w7zKxg0I7A/ZqRH3ngwdasaL7WrtTDNoAOkCvnqTjuxnoF0569Ko4sjiWVvcznZU
-b5SInRfbHVT8g71DlahaRVQt0Rn8kmlvqEPeUvjnAoGBAJfz0R84zI6ZbfeqENkD
-h4b+Lcu6F04SPt5vxnZhrDyPD1dRwc8TQxuLSEzMsWtNEXYa+Ml5nWQ5T4jtnmKQ
-vCnlB0XoV+VnS6APyVbHONp9pQFV9HNvAmaQf6Q6kOeUcyp2cF3c+ooFffA9GnlU
-wQq95KRxMh1nxBxCrTh0n05tAoGARo29l0r2PvgGFiAFDd6W8EQDluvLVS2d3Eh1
-Y6OrHvE0hqgU+5A9DSaffHv2yKqPVbRgcktfmRyeN7yPuCntTew6QzJ2nPUZuCeg
-OkRrgVWv+lo2aboHheuW15uoONCCDNZj+BeGsd0DpULayDdZi2TtHW/WIsaM67DE
-DJnBeDsCgYEAxWINst8mfuR2pKlVbQ8mMgo/LwKWqQgnpWpbf0gGMjdUpAoN5dh/
-l4VmO63Z/dAOyxQFAsqWS3pme2RfC2U94ZUOfFH58gLSNX7LWtBTZEhKLMs6Hfgr
-aDJeuEEIrB+3snvqEWMztpxwHszx/YrDXwEbri557ve2UB2NtOcP3dc=
------END RSA PRIVATE KEY-----
+-----BEGIN PRIVATE KEY-----
+MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCgluiYfh9lhgwx
+d5rJhUpPoY+WJ67pPa1tdCTkeD3jowQ/MhNatUxMy/7O76pvBegUtrq29MPLcmvA
+LeJYDcIJmh48pY/KrEUADZcDmrogQXQViCuaH/7ceKRxZu0FO7qBreyxgcu9nHkD
+SP1X7ZefhEhOOKDsRXiQWpXTyDC/PeL/BMXkdoPGROEAx2RSyXETTCtby7HGsZ+w
+nwXvdHUObpGmLErqiARhG39w7ZF40yV1hqy0DGw6CIV1q+JqLFMhZus+xSWkmFB4
+V06haO2D0Xgl7t2prN9c7OQHJ+fQP/D+cL2kzbdJO7MGq5biEoXMCsnpm0hCAu+v
+Hl/kE5xJAgMBAAECggEAHjVtITx+evpdolORwE1s1nB5ooEqB9G0pVWXGAxfdgol
+Ix8wIVzf8sVgFDzk5ngpuXXLR13hVAzc5JytCqvh360IeZeaZkGERFeKHCE32Jf/
+dop0S0ywBHjEVFnMhfK/qaPWVejo8uzznbDWCXuWDWmUsXQ5H2ENJjiIHTv4IY09
+0DXiySiaeI1iiyToiv6Dt9dVpPuleJbY0LlRfZ1WrkcEB9r1kQqzJOdqM5iuDYWa
+dCO9u8fbWPGRacL3ystrZqB0Od3s9G6h09D5Iv8QXpYHWp4E/Oea3kYKyFUDgPpH
+1wH6fE9fER6RKOKyG1HBZL12HlNZnPv1v033Rs6WRQKBgQC3LnaYo1TkHX0j4t1X
+iQisRWtE8espt1ZsxIErqiz+fJlHhQCJq2Lqz/uLHmqaM6M2ffVuXoFM8vZzlyXw
+v23EPc8HGEhvw6iTm3uCdTQ2nXZbb4Gbw5lYOF6PDJfAhnn3XwaUUVCL67PAZvwq
+j4hWKzmrfrrv8KRgvso9NB6fHQKBgQDgbVxLGofqhoE/tuoCfZMCC0vI3PZ5503B
+XvTXg/J9lKsycwx74qKWBDchqG+MF1GQOwufDvx44QAcW/gNsdd6SJ/yLlf69m6V
+HSGh3S3pUfFwT4wzY/rKbh0g8DSXm6J7BrMwID+RAwMjKzst1tqjCBH6HnNEHIfP
+HRrJAwgOHQKBgQCcFUkUckeJP987TrlPNwJe15+5VXENUJyhfSabMBu6lCx/FkMJ
+CzHz5lftiHNJBSrS6azQ6FHAYV3BzE6VvmcnSYs3/mbqZIslitxIotlkl/Mbof2L
+3bSxyQY5WX+MmokeUKfohQje0G2PSbEgCsEeuyIekJN0k1Vc4fStBdX5uQKBgAuG
+0mSxGiX7fovtMxupo3FJbz0DzEz6ik3SOLUQ+9VjW1+d9RgvzbXyxXofEouZbwD/
+Z1tmA6WZuM28E4NwjOak8EIaCz7ChW93LZEIsSD4qnPgQg0pp2naOfjFHY5j2faD
+o5RnM5yZEQIvaDy0ekpBUdsM0VLAPGFw1z1XwIQpAoGBALaN5OhHVZfy+RqTy+k/
+Sa+eO/vghkdapdZwfnFYhJzD+sZKRBUiNwzZrAcNfijN/sTIcnMWbMcD6TSdh1qZ
+RLd2m/U0T9avw0/aog+IXldkm7VjKf7+V+RIzoGOxmtCI1zVP9uldDYJDKAMabiu
+kdec1ozHgh6Pg2G2d5nkPgI9
+-----END PRIVATE KEY-----
 Certificate:
     Data:
-        Version: 1 (0x0)
+        Version: 3 (0x2)
         Serial Number: 1 (0x1)
-    Signature Algorithm: sha256WithRSAEncryption
+        Signature Algorithm: sha256WithRSAEncryption
         Issuer: CN=2048 RSA Test Root CA
         Validity
-            Not Before: Aug 14 02:46:30 2014 GMT
-            Not After : Aug 11 02:46:30 2024 GMT
+            Not Before: Feb 17 17:08:50 2023 GMT
+            Not After : Feb 14 17:08:50 2033 GMT
         Subject: C=US, ST=California, L=Mountain View, O=Test CA, CN=127.0.0.1
         Subject Public Key Info:
             Public Key Algorithm: rsaEncryption
                 Public-Key: (2048 bit)
                 Modulus:
-                    00:d9:fd:d9:51:ce:39:f5:53:c8:7e:93:53:50:8e:
-                    34:61:f1:75:93:03:be:d0:1f:a6:7c:a0:3b:e1:aa:
-                    eb:a1:e9:cd:6b:29:14:75:1e:9a:ce:b8:91:6d:85:
-                    33:3e:d8:cb:6c:8b:ae:fc:d0:d7:05:ce:0c:b2:fe:
-                    3b:5f:d1:15:4d:5b:34:a3:38:cc:80:e3:9c:4c:65:
-                    62:b6:b1:3f:e1:2c:30:f5:a9:d3:f9:50:56:53:d4:
-                    dd:a7:96:51:21:1b:19:3e:f6:73:c8:4a:5b:fc:cc:
-                    4e:24:a1:d7:d0:91:ac:08:f7:e9:04:9c:51:59:05:
-                    0d:54:28:61:d0:d5:64:38:9c:80:31:98:86:82:61:
-                    a4:49:e8:3a:fd:df:71:37:2f:f6:ac:da:08:a4:d8:
-                    1f:9c:b1:21:bb:b1:71:c8:1d:3d:e3:4c:10:60:0e:
-                    69:ae:7c:8b:fd:2f:ea:7d:a6:f8:57:8d:0b:b8:66:
-                    57:0a:bb:d6:43:eb:70:93:1d:ab:a5:06:f8:1d:14:
-                    f1:d9:81:ad:2b:fe:58:4a:1e:35:84:4a:06:4e:16:
-                    3f:49:9b:57:16:13:2d:43:24:52:6b:ab:0a:ba:14:
-                    f6:61:a5:87:31:a1:1b:f6:36:c8:7a:ad:cf:39:7a:
-                    2f:85:34:9e:70:0d:13:c3:b6:c6:d0:2b:59:13:3c:
-                    19:55
+                    00:a0:96:e8:98:7e:1f:65:86:0c:31:77:9a:c9:85:
+                    4a:4f:a1:8f:96:27:ae:e9:3d:ad:6d:74:24:e4:78:
+                    3d:e3:a3:04:3f:32:13:5a:b5:4c:4c:cb:fe:ce:ef:
+                    aa:6f:05:e8:14:b6:ba:b6:f4:c3:cb:72:6b:c0:2d:
+                    e2:58:0d:c2:09:9a:1e:3c:a5:8f:ca:ac:45:00:0d:
+                    97:03:9a:ba:20:41:74:15:88:2b:9a:1f:fe:dc:78:
+                    a4:71:66:ed:05:3b:ba:81:ad:ec:b1:81:cb:bd:9c:
+                    79:03:48:fd:57:ed:97:9f:84:48:4e:38:a0:ec:45:
+                    78:90:5a:95:d3:c8:30:bf:3d:e2:ff:04:c5:e4:76:
+                    83:c6:44:e1:00:c7:64:52:c9:71:13:4c:2b:5b:cb:
+                    b1:c6:b1:9f:b0:9f:05:ef:74:75:0e:6e:91:a6:2c:
+                    4a:ea:88:04:61:1b:7f:70:ed:91:78:d3:25:75:86:
+                    ac:b4:0c:6c:3a:08:85:75:ab:e2:6a:2c:53:21:66:
+                    eb:3e:c5:25:a4:98:50:78:57:4e:a1:68:ed:83:d1:
+                    78:25:ee:dd:a9:ac:df:5c:ec:e4:07:27:e7:d0:3f:
+                    f0:fe:70:bd:a4:cd:b7:49:3b:b3:06:ab:96:e2:12:
+                    85:cc:0a:c9:e9:9b:48:42:02:ef:af:1e:5f:e4:13:
+                    9c:49
                 Exponent: 65537 (0x10001)
         X509v3 extensions:
             X509v3 Subject Alternative Name: 
@@ -64,42 +65,46 @@
             X509v3 Basic Constraints: critical
                 CA:FALSE
             X509v3 Subject Key Identifier: 
-                47:76:6C:80:D1:4C:7A:B4:31:22:B8:50:62:FC:6B:D3:FD:B1:55:FA
+                01:1C:A6:B0:CB:64:17:AB:8E:90:63:E0:B6:16:19:C6:06:C1:C2:C7
             X509v3 Extended Key Usage: 
                 Code Signing
+            X509v3 Authority Key Identifier: 
+                D5:28:55:87:C7:A3:BF:D7:C4:CE:BE:3D:01:D2:BE:8B:7C:E4:E2:E2
     Signature Algorithm: sha256WithRSAEncryption
-         37:61:d4:3b:d5:b6:77:53:54:17:d7:d7:78:4f:06:34:40:60:
-         dd:35:cf:fc:c5:07:c0:2c:86:72:c1:10:a1:3d:6e:af:53:8c:
-         de:b1:69:b9:00:8b:8b:d5:21:45:66:8f:53:98:78:d1:86:dc:
-         8b:12:f3:b6:4a:e7:ae:19:b5:75:b1:7b:e6:27:dc:82:0d:44:
-         8e:15:c7:fb:57:7b:78:4d:6d:2f:2a:c7:cc:ec:9e:15:be:47:
-         2e:e3:7f:bd:09:a2:f5:4f:90:15:b5:6f:3a:ec:cc:a1:00:db:
-         91:a8:8a:c2:d3:1f:d9:20:b0:8e:79:5f:2a:31:c6:48:45:6c:
-         f0:c3:f5:78:ea:92:21:49:82:fe:02:b6:e4:58:52:9b:98:31:
-         c0:a3:bd:9c:5d:a6:32:7d:e2:7f:8b:57:25:2a:5a:a4:64:64:
-         f9:20:4d:6d:be:95:81:6a:3f:ce:7a:a3:c8:e5:26:d2:c0:95:
-         ce:90:db:b0:90:7e:f9:fe:2b:96:06:ca:72:2d:c9:a3:c0:6c:
-         84:1a:8a:f3:1a:93:09:2f:34:ec:a8:c7:88:79:eb:2e:a6:77:
-         51:5d:79:00:db:8f:a4:5a:e5:58:7f:94:bb:2b:4c:24:16:56:
-         4a:7a:49:3b:d9:b1:77:b0:21:c9:ba:73:96:68:8a:59:69:a5:
-         96:4d:b7:a1
+    Signature Value:
+        bc:03:2d:ad:04:e4:0e:64:20:1e:bd:fb:04:6a:63:d5:8c:51:
+        a0:d6:2d:4d:7d:97:f5:0f:42:56:09:58:d6:f4:df:46:01:01:
+        f3:62:78:a7:57:76:28:5a:85:fd:b1:34:37:06:7f:1f:7e:d1:
+        d9:1a:54:78:a2:4a:ec:90:69:bf:08:3e:c7:df:63:1c:89:d3:
+        ca:30:61:e3:f6:0a:92:66:68:8f:6b:6a:0b:01:4c:ca:90:e5:
+        13:b4:89:92:2e:56:bd:84:6f:9c:ff:19:5b:75:27:7f:9e:cc:
+        62:1c:06:e6:94:30:fe:d3:59:1b:2c:10:79:7c:0e:1a:29:be:
+        c9:14:02:56:f9:eb:cd:a6:33:10:75:fa:d7:61:23:d0:16:cf:
+        c9:e9:58:7b:51:8c:1f:84:3f:32:ca:89:91:90:da:5f:7d:35:
+        0c:f5:1c:c3:b6:03:ad:f9:5f:49:79:54:55:86:98:fa:69:b5:
+        fb:47:5b:b1:99:c8:f0:5f:b8:af:a6:b4:63:1b:53:e6:47:78:
+        e3:40:df:63:21:f2:51:88:c4:24:b5:51:33:bc:b1:f5:7e:42:
+        89:b3:ff:3a:d7:e4:02:e2:23:c4:82:98:74:d2:2c:ee:8e:71:
+        58:80:b7:0f:24:dc:11:83:02:13:91:7d:0f:8f:b4:81:20:c5:
+        ee:55:8c:7b
 -----BEGIN CERTIFICATE-----
-MIIDSzCCAjMCAQEwDQYJKoZIhvcNAQELBQAwIDEeMBwGA1UEAwwVMjA0OCBSU0Eg
-VGVzdCBSb290IENBMB4XDTE0MDgxNDAyNDYzMFoXDTI0MDgxMTAyNDYzMFowYDEL
-MAkGA1UEBhMCVVMxEzARBgNVBAgMCkNhbGlmb3JuaWExFjAUBgNVBAcMDU1vdW50
-YWluIFZpZXcxEDAOBgNVBAoMB1Rlc3QgQ0ExEjAQBgNVBAMMCTEyNy4wLjAuMTCC
-ASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANn92VHOOfVTyH6TU1CONGHx
-dZMDvtAfpnygO+Gq66HpzWspFHUems64kW2FMz7Yy2yLrvzQ1wXODLL+O1/RFU1b
-NKM4zIDjnExlYraxP+EsMPWp0/lQVlPU3aeWUSEbGT72c8hKW/zMTiSh19CRrAj3
-6QScUVkFDVQoYdDVZDicgDGYhoJhpEnoOv3fcTcv9qzaCKTYH5yxIbuxccgdPeNM
-EGAOaa58i/0v6n2m+FeNC7hmVwq71kPrcJMdq6UG+B0U8dmBrSv+WEoeNYRKBk4W
-P0mbVxYTLUMkUmurCroU9mGlhzGhG/Y2yHqtzzl6L4U0nnANE8O2xtArWRM8GVUC
-AwEAAaNVMFMwDwYDVR0RBAgwBocEfwAAATAMBgNVHRMBAf8EAjAAMB0GA1UdDgQW
-BBRHdmyA0Ux6tDEiuFBi/GvT/bFV+jATBgNVHSUEDDAKBggrBgEFBQcDAzANBgkq
-hkiG9w0BAQsFAAOCAQEAN2HUO9W2d1NUF9fXeE8GNEBg3TXP/MUHwCyGcsEQoT1u
-r1OM3rFpuQCLi9UhRWaPU5h40YbcixLztkrnrhm1dbF75ifcgg1EjhXH+1d7eE1t
-LyrHzOyeFb5HLuN/vQmi9U+QFbVvOuzMoQDbkaiKwtMf2SCwjnlfKjHGSEVs8MP1
-eOqSIUmC/gK25FhSm5gxwKO9nF2mMn3if4tXJSpapGRk+SBNbb6VgWo/znqjyOUm
-0sCVzpDbsJB++f4rlgbKci3Jo8BshBqK8xqTCS807KjHiHnrLqZ3UV15ANuPpFrl
-WH+UuytMJBZWSnpJO9mxd7AhybpzlmiKWWmllk23oQ==
+MIIDcTCCAlmgAwIBAgIBATANBgkqhkiG9w0BAQsFADAgMR4wHAYDVQQDDBUyMDQ4
+IFJTQSBUZXN0IFJvb3QgQ0EwHhcNMjMwMjE3MTcwODUwWhcNMzMwMjE0MTcwODUw
+WjBgMQswCQYDVQQGEwJVUzETMBEGA1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwN
+TW91bnRhaW4gVmlldzEQMA4GA1UECgwHVGVzdCBDQTESMBAGA1UEAwwJMTI3LjAu
+MC4xMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAoJbomH4fZYYMMXea
+yYVKT6GPlieu6T2tbXQk5Hg946MEPzITWrVMTMv+zu+qbwXoFLa6tvTDy3JrwC3i
+WA3CCZoePKWPyqxFAA2XA5q6IEF0FYgrmh/+3HikcWbtBTu6ga3ssYHLvZx5A0j9
+V+2Xn4RITjig7EV4kFqV08gwvz3i/wTF5HaDxkThAMdkUslxE0wrW8uxxrGfsJ8F
+73R1Dm6RpixK6ogEYRt/cO2ReNMldYastAxsOgiFdaviaixTIWbrPsUlpJhQeFdO
+oWjtg9F4Je7dqazfXOzkByfn0D/w/nC9pM23STuzBquW4hKFzArJ6ZtIQgLvrx5f
+5BOcSQIDAQABo3YwdDAPBgNVHREECDAGhwR/AAABMAwGA1UdEwEB/wQCMAAwHQYD
+VR0OBBYEFAEcprDLZBerjpBj4LYWGcYGwcLHMBMGA1UdJQQMMAoGCCsGAQUFBwMD
+MB8GA1UdIwQYMBaAFNUoVYfHo7/XxM6+PQHSvot85OLiMA0GCSqGSIb3DQEBCwUA
+A4IBAQC8Ay2tBOQOZCAevfsEamPVjFGg1i1NfZf1D0JWCVjW9N9GAQHzYninV3Yo
+WoX9sTQ3Bn8fftHZGlR4okrskGm/CD7H32McidPKMGHj9gqSZmiPa2oLAUzKkOUT
+tImSLla9hG+c/xlbdSd/nsxiHAbmlDD+01kbLBB5fA4aKb7JFAJW+evNpjMQdfrX
+YSPQFs/J6Vh7UYwfhD8yyomRkNpffTUM9RzDtgOt+V9JeVRVhpj6abX7R1uxmcjw
+X7ivprRjG1PmR3jjQN9jIfJRiMQktVEzvLH1fkKJs/861+QC4iPEgph00izujnFY
+gLcPJNwRgwITkX0Pj7SBIMXuVYx7
 -----END CERTIFICATE-----
diff --git a/net/socket/udp_client_socket.cc b/net/socket/udp_client_socket.cc
index 5203b05..5402c107 100644
--- a/net/socket/udp_client_socket.cc
+++ b/net/socket/udp_client_socket.cc
@@ -178,4 +178,9 @@
 #endif
 }
 
+void UDPClientSocket::AdoptOpenedSocket(AddressFamily address_family,
+                                        SocketDescriptor socket) {
+  socket_.AdoptOpenedSocket(address_family, socket);
+}
+
 }  // namespace net
diff --git a/net/socket/udp_client_socket.h b/net/socket/udp_client_socket.h
index cdfadb5..8999d0e 100644
--- a/net/socket/udp_client_socket.h
+++ b/net/socket/udp_client_socket.h
@@ -9,6 +9,7 @@
 
 #include "net/base/net_export.h"
 #include "net/socket/datagram_client_socket.h"
+#include "net/socket/socket_descriptor.h"
 #include "net/socket/udp_socket.h"
 #include "net/traffic_annotation/network_traffic_annotation.h"
 
@@ -74,6 +75,9 @@
   void SetIOSNetworkServiceType(int ios_network_service_type) override;
   void SetDontClose(bool dont_close) override;
 
+  // Takes ownership of an opened but unconnected and unbound `socket`.
+  void AdoptOpenedSocket(AddressFamily address_family, SocketDescriptor socket);
+
  private:
   UDPSocket socket_;
   // The network the socket is currently bound to.
diff --git a/net/socket/udp_socket_posix.cc b/net/socket/udp_socket_posix.cc
index 9dce5d5..a3149177e 100644
--- a/net/socket/udp_socket_posix.cc
+++ b/net/socket/udp_socket_posix.cc
@@ -154,10 +154,33 @@
   if (owned_socket_count.empty())
     return ERR_INSUFFICIENT_RESOURCES;
 
+  owned_socket_count_ = std::move(owned_socket_count);
   addr_family_ = ConvertAddressFamily(address_family);
   socket_ = CreatePlatformSocket(addr_family_, SOCK_DGRAM, 0);
-  if (socket_ == kInvalidSocket)
+  if (socket_ == kInvalidSocket) {
+    owned_socket_count_.Reset();
     return MapSystemError(errno);
+  }
+
+  return ConfigureOpenedSocket();
+}
+
+int UDPSocketPosix::AdoptOpenedSocket(AddressFamily address_family,
+                                      int socket) {
+  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+  DCHECK_EQ(socket_, kInvalidSocket);
+  auto owned_socket_count = TryAcquireGlobalUDPSocketCount();
+  if (owned_socket_count.empty()) {
+    return ERR_INSUFFICIENT_RESOURCES;
+  }
+
+  owned_socket_count_ = std::move(owned_socket_count);
+  socket_ = socket;
+  addr_family_ = ConvertAddressFamily(address_family);
+  return ConfigureOpenedSocket();
+}
+
+int UDPSocketPosix::ConfigureOpenedSocket() {
 #if BUILDFLAG(IS_APPLE) && !BUILDFLAG(CRONET_BUILD)
   PCHECK(change_fdguard_np(socket_, nullptr, 0, &kSocketFdGuard,
                            GUARD_CLOSE | GUARD_DUP, nullptr) == 0);
@@ -171,7 +194,6 @@
   if (tag_ != SocketTag())
     tag_.Apply(socket_);
 
-  owned_socket_count_ = std::move(owned_socket_count);
   return OK;
 }
 
diff --git a/net/socket/udp_socket_posix.h b/net/socket/udp_socket_posix.h
index 306feb8a..b6f3841 100644
--- a/net/socket/udp_socket_posix.h
+++ b/net/socket/udp_socket_posix.h
@@ -275,6 +275,11 @@
   // Sets "don't close" flag for the socket.
   void SetDontClose(bool dont_close);
 
+  // Takes ownership of `socket`, which should be a socket descriptor opened
+  // with the specified address family. The socket should only be created but
+  // not bound or connected to an address.
+  int AdoptOpenedSocket(AddressFamily address_family, int socket);
+
  private:
   enum SocketOptions {
     SOCKET_OPTION_MULTICAST_LOOP = 1 << 0
@@ -368,6 +373,9 @@
   // Binds to a random port on |address|.
   int RandomBind(const IPAddress& address);
 
+  // Sets `socket_hash_` and `tag_` on opened `socket_`.
+  int ConfigureOpenedSocket();
+
   int socket_;
 
   // Hash of |socket_| to verify that it is not corrupted when calling close().
diff --git a/net/socket/udp_socket_unittest.cc b/net/socket/udp_socket_unittest.cc
index 33ccfe0..41828e3 100644
--- a/net/socket/udp_socket_unittest.cc
+++ b/net/socket/udp_socket_unittest.cc
@@ -42,6 +42,13 @@
 #include "testing/gtest/include/gtest/gtest.h"
 #include "testing/platform_test.h"
 
+#if !BUILDFLAG(IS_WIN)
+#include <netinet/in.h>
+#include <sys/socket.h>
+#else
+#include <winsock2.h>
+#endif
+
 #if BUILDFLAG(IS_ANDROID)
 #include "base/android/build_info.h"
 #include "net/android/network_change_notifier_factory_android.h"
@@ -449,6 +456,57 @@
   EXPECT_FALSE(socket.is_connected());
 }
 
+// Similar to ConnectFail but UDPSocket adopts an opened socket instead of
+// opening one directly.
+TEST_F(UDPSocketTest, AdoptedSocket) {
+  auto socketfd =
+      CreatePlatformSocket(ConvertAddressFamily(ADDRESS_FAMILY_IPV4),
+                           SOCK_DGRAM, AF_UNIX ? 0 : IPPROTO_UDP);
+  UDPSocket socket(DatagramSocket::DEFAULT_BIND, nullptr, NetLogSource());
+
+  EXPECT_THAT(socket.AdoptOpenedSocket(ADDRESS_FAMILY_IPV4, socketfd), IsOk());
+
+  // Connect to an IPv6 address should fail since the socket was created for
+  // IPv4.
+  EXPECT_THAT(socket.Connect(net::IPEndPoint(IPAddress::IPv6Localhost(), 53)),
+              Not(IsOk()));
+
+  // Make sure that UDPSocket actually closed the socket.
+  EXPECT_FALSE(socket.is_connected());
+}
+
+// Tests that UDPSocket updates the global counter correctly.
+TEST_F(UDPSocketTest, LimitAdoptSocket) {
+  ASSERT_EQ(0, GetGlobalUDPSocketCountForTesting());
+  {
+    // Creating a platform socket does not increase count.
+    auto socketfd =
+        CreatePlatformSocket(ConvertAddressFamily(ADDRESS_FAMILY_IPV4),
+                             SOCK_DGRAM, AF_UNIX ? 0 : IPPROTO_UDP);
+    ASSERT_EQ(0, GetGlobalUDPSocketCountForTesting());
+
+    // Simply allocating a UDPSocket does not increase count.
+    UDPSocket socket(DatagramSocket::DEFAULT_BIND, nullptr, NetLogSource());
+    EXPECT_EQ(0, GetGlobalUDPSocketCountForTesting());
+
+    // Calling AdoptOpenedSocket() allocates the socket and increases the global
+    // counter.
+    EXPECT_THAT(socket.AdoptOpenedSocket(ADDRESS_FAMILY_IPV4, socketfd),
+                IsOk());
+    EXPECT_EQ(1, GetGlobalUDPSocketCountForTesting());
+
+    // Connect to an IPv6 address should fail since the socket was created for
+    // IPv4.
+    EXPECT_THAT(socket.Connect(net::IPEndPoint(IPAddress::IPv6Localhost(), 53)),
+                Not(IsOk()));
+
+    // That Connect() failed doesn't change the global counter.
+    EXPECT_EQ(1, GetGlobalUDPSocketCountForTesting());
+  }
+  // Finally, destroying UDPSocket decrements the global counter.
+  EXPECT_EQ(0, GetGlobalUDPSocketCountForTesting());
+}
+
 // In this test, we verify that connect() on a socket will have the effect
 // of filtering reads on this socket only to data read from the destination
 // we connected to.
diff --git a/net/socket/udp_socket_win.cc b/net/socket/udp_socket_win.cc
index bebf140a..8961503b 100644
--- a/net/socket/udp_socket_win.cc
+++ b/net/socket/udp_socket_win.cc
@@ -263,19 +263,40 @@
   if (owned_socket_count.empty())
     return ERR_INSUFFICIENT_RESOURCES;
 
+  owned_socket_count_ = std::move(owned_socket_count);
   addr_family_ = ConvertAddressFamily(address_family);
   socket_ = CreatePlatformSocket(addr_family_, SOCK_DGRAM, IPPROTO_UDP);
-  if (socket_ == INVALID_SOCKET)
+  if (socket_ == INVALID_SOCKET) {
+    owned_socket_count_.Reset();
     return MapSystemError(WSAGetLastError());
+  }
+  ConfigureOpenedSocket();
+  return OK;
+}
+
+int UDPSocketWin::AdoptOpenedSocket(AddressFamily address_family,
+                                    SOCKET socket) {
+  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+  auto owned_socket_count = TryAcquireGlobalUDPSocketCount();
+  if (owned_socket_count.empty()) {
+    return ERR_INSUFFICIENT_RESOURCES;
+  }
+
+  owned_socket_count_ = std::move(owned_socket_count);
+  addr_family_ = ConvertAddressFamily(address_family);
+  socket_ = socket;
+  ConfigureOpenedSocket();
+  return OK;
+}
+
+void UDPSocketWin::ConfigureOpenedSocket() {
+  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
   if (!use_non_blocking_io_) {
     core_ = base::MakeRefCounted<Core>(this);
   } else {
     read_write_event_.Set(WSACreateEvent());
     WSAEventSelect(socket_, read_write_event_.Get(), FD_READ | FD_WRITE);
   }
-
-  owned_socket_count_ = std::move(owned_socket_count);
-  return OK;
 }
 
 void UDPSocketWin::Close() {
diff --git a/net/socket/udp_socket_win.h b/net/socket/udp_socket_win.h
index 4aa218c..5374c291 100644
--- a/net/socket/udp_socket_win.h
+++ b/net/socket/udp_socket_win.h
@@ -350,6 +350,11 @@
   // Apply |tag| to this socket.
   void ApplySocketTag(const SocketTag& tag);
 
+  // Takes ownership of `socket`, which should be a socket descriptor opened
+  // with the specified address family. The socket should only be created but
+  // not bound or connected to an address.
+  int AdoptOpenedSocket(AddressFamily address_family, SOCKET socket);
+
  private:
   enum SocketOptions {
     SOCKET_OPTION_MULTICAST_LOOP = 1 << 0
@@ -406,6 +411,9 @@
   int SetMulticastOptions();
   int DoBind(const IPEndPoint& address);
 
+  // Configures opened `socket_` depending on whether it uses nonblocking IO.
+  void ConfigureOpenedSocket();
+
   // This is provided to allow QwaveApi mocking in tests. |UDPSocketWin| method
   // implementations should call |GetQwaveApi()| instead of
   // |QwaveApi::GetDefault()| directly.
diff --git a/services/network/public/cpp/network_switches.cc b/services/network/public/cpp/network_switches.cc
index 718d5eb..f774c06a8 100644
--- a/services/network/public/cpp/network_switches.cc
+++ b/services/network/public/cpp/network_switches.cc
@@ -17,8 +17,6 @@
 // causing them to attempt an unauthenticated SSL/TLS session. This is intended
 // for use when testing various service URLs (eg: kPromoServerURL, kSbURLPrefix,
 // kSyncServiceURL, etc).
-// TODO(crbug.com/1417189): Remove this flag if the alternative solution
-// implemented for crbug.com/1221565 covers all needs.
 const char kIgnoreUrlFetcherCertRequests[] = "ignore-urlfetcher-cert-requests";
 
 // A set of public key hashes for which to ignore certificate-related errors.
diff --git a/sql/recover_module/payload.cc b/sql/recover_module/payload.cc
index f7e87da..ad3fac8 100644
--- a/sql/recover_module/payload.cc
+++ b/sql/recover_module/payload.cc
@@ -203,9 +203,16 @@
   return true;
 }
 
+bool LeafPayloadReader::IsInitialized() const {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  return page_id_ != DatabasePageReader::kInvalidPageId;
+}
+
 bool LeafPayloadReader::ReadPayload(int64_t offset,
                                     int64_t size,
                                     uint8_t* buffer) {
+  DCHECK(IsInitialized())
+      << "Initialize() not called, or last call did not succeed";
   DCHECK_GE(offset, 0);
   DCHECK_LT(offset, payload_size_);
   DCHECK_GT(size, 0);
@@ -249,6 +256,11 @@
 
   // The read is entirely in overflow pages.
   DCHECK_GE(offset, inline_payload_size_);
+  if (max_overflow_payload_size_ <= 0) {
+    // `max_overflow_payload_size_` should have been set in Initialize() if it's
+    // to be used here. See https://crbug.com/1417151.
+    return false;
+  }
   while (size > 0) {
     const int overflow_page_index =
         (offset - inline_payload_size_) / max_overflow_payload_size_;
@@ -286,7 +298,7 @@
 
 const uint8_t* LeafPayloadReader::ReadInlinePayload() {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-  DCHECK(page_id_ != DatabasePageReader::kInvalidPageId)
+  DCHECK(IsInitialized())
       << "Initialize() not called, or last call did not succeed";
 
   if (db_reader_->ReadPage(page_id_) != SQLITE_OK)
diff --git a/sql/recover_module/payload.h b/sql/recover_module/payload.h
index dc6a329..3028af02 100644
--- a/sql/recover_module/payload.h
+++ b/sql/recover_module/payload.h
@@ -51,6 +51,9 @@
   // class can be called.
   bool Initialize(int64_t payload_size, int payload_offset);
 
+  // True if the last call to Initialize succeeded.
+  bool IsInitialized() const;
+
   // The number of payload bytes that are stored on the B-tree page.
   //
   // The return value is guaranteed to be non-negative and at most
@@ -109,29 +112,29 @@
   const raw_ptr<DatabasePageReader> db_reader_;
 
   // Total size of the current payload.
-  int64_t payload_size_;
+  int64_t payload_size_ = 0;
 
   // The ID of the B-tree page containing the current payload's inline bytes.
   //
   // Set to kInvalidPageId if the reader wasn't successfully initialized.
-  int page_id_;
+  int page_id_ = DatabasePageReader::kInvalidPageId;
 
   // The start of the current payload's inline bytes on the B-tree page.
   //
   // Large payloads extend past the B-tree page containing the payload, via
   // overflow pages.
-  int inline_payload_offset_;
+  int inline_payload_offset_ = 0;
 
   // Number of bytes in the current payload stored in its B-tree page.
   //
   // The rest of the payload is stored on overflow pages.
-  int inline_payload_size_;
+  int inline_payload_size_ = 0;
 
   // Number of overflow pages used by the payload.
-  int overflow_page_count_;
+  int overflow_page_count_ = 0;
 
   // Number of bytes in each overflow page that stores the payload.
-  int max_overflow_payload_size_;
+  int max_overflow_payload_size_ = 0;
 
   // Page IDs for all the payload's overflow pages, in order.
   //
diff --git a/sql/recover_module/record.cc b/sql/recover_module/record.cc
index 773cb2a9..109fb97 100644
--- a/sql/recover_module/record.cc
+++ b/sql/recover_module/record.cc
@@ -154,6 +154,12 @@
   return true;
 }
 
+bool RecordReader::IsInitialized() const {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  return value_headers_.size() == static_cast<size_t>(column_count_) &&
+         payload_reader_->IsInitialized();
+}
+
 ValueType RecordReader::GetValueType(int column_index) const {
   DCHECK(IsInitialized());
   DCHECK_GE(column_index, 0);
diff --git a/sql/recover_module/record.h b/sql/recover_module/record.h
index af437fe..c039ae669 100644
--- a/sql/recover_module/record.h
+++ b/sql/recover_module/record.h
@@ -90,10 +90,7 @@
   bool Initialize();
 
   // True if the last call to Initialize succeeded.
-  bool IsInitialized() const {
-    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-    return value_headers_.size() == static_cast<size_t>(column_count_);
-  }
+  bool IsInitialized() const;
 
   // The type of a value in the record. |column_index| is 0-based.
   ValueType GetValueType(int column_index) const;
diff --git a/storage/browser/blob/blob_registry_impl_unittest.cc b/storage/browser/blob/blob_registry_impl_unittest.cc
index 4a78619..00652e0 100644
--- a/storage/browser/blob/blob_registry_impl_unittest.cc
+++ b/storage/browser/blob/blob_registry_impl_unittest.cc
@@ -86,7 +86,7 @@
           base::SequencedTaskRunner::GetCurrentDefault());
     }
     auto delegate = std::make_unique<MockBlobRegistryDelegate>();
-    delegate_ptr_ = delegate.get();
+    delegate_ptr_ = delegate->AsWeakPtr();
     registry_impl_->Bind(registry_.BindNewPipeAndPassReceiver(),
                          std::move(delegate));
 
@@ -199,7 +199,7 @@
   BlobUrlRegistry url_registry_;
   std::unique_ptr<BlobRegistryImpl> registry_impl_;
   mojo::Remote<blink::mojom::BlobRegistry> registry_;
-  raw_ptr<MockBlobRegistryDelegate> delegate_ptr_;
+  base::WeakPtr<MockBlobRegistryDelegate> delegate_ptr_;
   scoped_refptr<base::SequencedTaskRunner> bytes_provider_runner_;
 
   size_t reply_request_count_ = 0;
diff --git a/storage/browser/test/mock_blob_registry_delegate.h b/storage/browser/test/mock_blob_registry_delegate.h
index 081bcad..87d0ed33 100644
--- a/storage/browser/test/mock_blob_registry_delegate.h
+++ b/storage/browser/test/mock_blob_registry_delegate.h
@@ -5,11 +5,14 @@
 #ifndef STORAGE_BROWSER_TEST_MOCK_BLOB_REGISTRY_DELEGATE_H_
 #define STORAGE_BROWSER_TEST_MOCK_BLOB_REGISTRY_DELEGATE_H_
 
+#include "base/memory/weak_ptr.h"
 #include "storage/browser/blob/blob_registry_impl.h"
 
 namespace storage {
 
-class MockBlobRegistryDelegate : public BlobRegistryImpl::Delegate {
+class MockBlobRegistryDelegate
+    : public BlobRegistryImpl::Delegate,
+      public base::SupportsWeakPtr<MockBlobRegistryDelegate> {
  public:
   MockBlobRegistryDelegate() = default;
   ~MockBlobRegistryDelegate() override = default;
diff --git a/testing/buildbot/chromium.chromiumos.json b/testing/buildbot/chromium.chromiumos.json
index 539cb81f..0b1848d 100644
--- a/testing/buildbot/chromium.chromiumos.json
+++ b/testing/buildbot/chromium.chromiumos.json
@@ -5818,9 +5818,9 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.interactive_ui_tests.filter;../../testing/buildbot/filters/linux-lacros.interactive_ui_tests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v112.0.5600.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v112.0.5602.0/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 112.0.5600.0",
+        "description": "Run with ash-chrome version 112.0.5602.0",
         "isolate_profile_data": true,
         "merge": {
           "args": [],
@@ -5832,8 +5832,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v112.0.5600.0",
-              "revision": "version:112.0.5600.0"
+              "location": "lacros_version_skew_tests_v112.0.5602.0",
+              "revision": "version:112.0.5602.0"
             }
           ],
           "dimension_sets": [
@@ -5989,9 +5989,9 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v112.0.5600.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v112.0.5602.0/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 112.0.5600.0",
+        "description": "Run with ash-chrome version 112.0.5602.0",
         "isolate_profile_data": true,
         "merge": {
           "args": [],
@@ -6003,8 +6003,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v112.0.5600.0",
-              "revision": "version:112.0.5600.0"
+              "location": "lacros_version_skew_tests_v112.0.5602.0",
+              "revision": "version:112.0.5602.0"
             }
           ],
           "dimension_sets": [
@@ -6141,9 +6141,9 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v112.0.5600.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v112.0.5602.0/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 112.0.5600.0",
+        "description": "Run with ash-chrome version 112.0.5602.0",
         "isolate_profile_data": true,
         "merge": {
           "args": [],
@@ -6155,8 +6155,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v112.0.5600.0",
-              "revision": "version:112.0.5600.0"
+              "location": "lacros_version_skew_tests_v112.0.5602.0",
+              "revision": "version:112.0.5602.0"
             }
           ],
           "dimension_sets": [
diff --git a/testing/buildbot/chromium.coverage.json b/testing/buildbot/chromium.coverage.json
index afe23f0..4ae4cae 100644
--- a/testing/buildbot/chromium.coverage.json
+++ b/testing/buildbot/chromium.coverage.json
@@ -20297,9 +20297,9 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.interactive_ui_tests.filter;../../testing/buildbot/filters/linux-lacros.interactive_ui_tests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v112.0.5600.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v112.0.5602.0/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 112.0.5600.0",
+        "description": "Run with ash-chrome version 112.0.5602.0",
         "isolate_profile_data": true,
         "merge": {
           "args": [],
@@ -20311,8 +20311,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v112.0.5600.0",
-              "revision": "version:112.0.5600.0"
+              "location": "lacros_version_skew_tests_v112.0.5602.0",
+              "revision": "version:112.0.5602.0"
             }
           ],
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com",
@@ -20438,9 +20438,9 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v112.0.5600.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v112.0.5602.0/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 112.0.5600.0",
+        "description": "Run with ash-chrome version 112.0.5602.0",
         "isolate_profile_data": true,
         "merge": {
           "args": [],
@@ -20452,8 +20452,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v112.0.5600.0",
-              "revision": "version:112.0.5600.0"
+              "location": "lacros_version_skew_tests_v112.0.5602.0",
+              "revision": "version:112.0.5602.0"
             }
           ],
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
@@ -20565,9 +20565,9 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v112.0.5600.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v112.0.5602.0/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 112.0.5600.0",
+        "description": "Run with ash-chrome version 112.0.5602.0",
         "isolate_profile_data": true,
         "merge": {
           "args": [],
@@ -20579,8 +20579,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v112.0.5600.0",
-              "revision": "version:112.0.5600.0"
+              "location": "lacros_version_skew_tests_v112.0.5602.0",
+              "revision": "version:112.0.5602.0"
             }
           ],
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com",
diff --git a/testing/buildbot/chromium.fyi.json b/testing/buildbot/chromium.fyi.json
index 89dddfc..d475b5d 100644
--- a/testing/buildbot/chromium.fyi.json
+++ b/testing/buildbot/chromium.fyi.json
@@ -7502,6 +7502,197 @@
       }
     ]
   },
+  "ios-blink-dbg-fyi": {
+    "additional_compile_targets": [
+      "all"
+    ],
+    "isolated_scripts": [
+      {
+        "args": [
+          "--platform",
+          "iPad Pro (12.9-inch) (3rd generation)",
+          "--version",
+          "16.2",
+          "--out-dir",
+          "${ISOLATED_OUTDIR}",
+          "--xcode-build-version",
+          "14c18",
+          "--xctest"
+        ],
+        "isolate_name": "blink_unittests",
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
+        },
+        "name": "blink_unittests iPad Pro (12.9-inch) (3rd generation) 16.2",
+        "resultdb": {
+          "enable": true,
+          "has_native_resultdb_integration": true
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "cipd_packages": [
+            {
+              "cipd_package": "infra/tools/mac_toolchain/${platform}",
+              "location": ".",
+              "revision": "git_revision:723fc1a6c8cdf2631a57851f5610e598db0c1de1"
+            }
+          ],
+          "named_caches": [
+            {
+              "name": "xcode_ios_14c18",
+              "path": "Xcode.app"
+            },
+            {
+              "name": "runtime_ios_16_2",
+              "path": "Runtime-ios-16.2"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test_id_prefix": "ninja://third_party/blink/renderer/controller:blink_unittests/",
+        "variant_id": "iPad Pro (12.9-inch) (3rd generation) 16.2"
+      },
+      {
+        "args": [
+          "--platform",
+          "iPhone X",
+          "--version",
+          "16.2",
+          "--out-dir",
+          "${ISOLATED_OUTDIR}",
+          "--xcode-build-version",
+          "14c18",
+          "--xctest"
+        ],
+        "isolate_name": "blink_unittests",
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
+        },
+        "name": "blink_unittests iPhone X 16.2",
+        "resultdb": {
+          "enable": true,
+          "has_native_resultdb_integration": true
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "cipd_packages": [
+            {
+              "cipd_package": "infra/tools/mac_toolchain/${platform}",
+              "location": ".",
+              "revision": "git_revision:723fc1a6c8cdf2631a57851f5610e598db0c1de1"
+            }
+          ],
+          "named_caches": [
+            {
+              "name": "xcode_ios_14c18",
+              "path": "Xcode.app"
+            },
+            {
+              "name": "runtime_ios_16_2",
+              "path": "Runtime-ios-16.2"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test_id_prefix": "ninja://third_party/blink/renderer/controller:blink_unittests/",
+        "variant_id": "iPhone X 16.2"
+      },
+      {
+        "args": [
+          "--platform",
+          "iPad Pro (12.9-inch) (3rd generation)",
+          "--version",
+          "16.2",
+          "--out-dir",
+          "${ISOLATED_OUTDIR}",
+          "--xcode-build-version",
+          "14c18",
+          "--xctest"
+        ],
+        "isolate_name": "content_unittests",
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
+        },
+        "name": "content_unittests iPad Pro (12.9-inch) (3rd generation) 16.2",
+        "resultdb": {
+          "enable": true,
+          "has_native_resultdb_integration": true
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "cipd_packages": [
+            {
+              "cipd_package": "infra/tools/mac_toolchain/${platform}",
+              "location": ".",
+              "revision": "git_revision:723fc1a6c8cdf2631a57851f5610e598db0c1de1"
+            }
+          ],
+          "named_caches": [
+            {
+              "name": "xcode_ios_14c18",
+              "path": "Xcode.app"
+            },
+            {
+              "name": "runtime_ios_16_2",
+              "path": "Runtime-ios-16.2"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test_id_prefix": "ninja://content/test:content_unittests/",
+        "variant_id": "iPad Pro (12.9-inch) (3rd generation) 16.2"
+      },
+      {
+        "args": [
+          "--platform",
+          "iPhone X",
+          "--version",
+          "16.2",
+          "--out-dir",
+          "${ISOLATED_OUTDIR}",
+          "--xcode-build-version",
+          "14c18",
+          "--xctest"
+        ],
+        "isolate_name": "content_unittests",
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
+        },
+        "name": "content_unittests iPhone X 16.2",
+        "resultdb": {
+          "enable": true,
+          "has_native_resultdb_integration": true
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "cipd_packages": [
+            {
+              "cipd_package": "infra/tools/mac_toolchain/${platform}",
+              "location": ".",
+              "revision": "git_revision:723fc1a6c8cdf2631a57851f5610e598db0c1de1"
+            }
+          ],
+          "named_caches": [
+            {
+              "name": "xcode_ios_14c18",
+              "path": "Xcode.app"
+            },
+            {
+              "name": "runtime_ios_16_2",
+              "path": "Runtime-ios-16.2"
+            }
+          ],
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test_id_prefix": "ninja://content/test:content_unittests/",
+        "variant_id": "iPhone X 16.2"
+      }
+    ]
+  },
   "ios-fieldtrial-rel": {
     "isolated_scripts": [
       {
@@ -56985,9 +57176,9 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.interactive_ui_tests.filter;../../testing/buildbot/filters/linux-lacros.interactive_ui_tests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v112.0.5600.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v112.0.5602.0/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 112.0.5600.0",
+        "description": "Run with ash-chrome version 112.0.5602.0",
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -56998,8 +57189,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v112.0.5600.0",
-              "revision": "version:112.0.5600.0"
+              "location": "lacros_version_skew_tests_v112.0.5602.0",
+              "revision": "version:112.0.5602.0"
             }
           ],
           "dimension_sets": [
@@ -57156,9 +57347,9 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v112.0.5600.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v112.0.5602.0/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 112.0.5600.0",
+        "description": "Run with ash-chrome version 112.0.5602.0",
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -57169,8 +57360,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v112.0.5600.0",
-              "revision": "version:112.0.5600.0"
+              "location": "lacros_version_skew_tests_v112.0.5602.0",
+              "revision": "version:112.0.5602.0"
             }
           ],
           "dimension_sets": [
@@ -57308,9 +57499,9 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v112.0.5600.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v112.0.5602.0/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 112.0.5600.0",
+        "description": "Run with ash-chrome version 112.0.5602.0",
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -57321,8 +57512,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v112.0.5600.0",
-              "revision": "version:112.0.5600.0"
+              "location": "lacros_version_skew_tests_v112.0.5602.0",
+              "revision": "version:112.0.5602.0"
             }
           ],
           "dimension_sets": [
@@ -58846,9 +59037,9 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.interactive_ui_tests.filter;../../testing/buildbot/filters/linux-lacros.interactive_ui_tests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v112.0.5600.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v112.0.5602.0/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 112.0.5600.0",
+        "description": "Run with ash-chrome version 112.0.5602.0",
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -58859,8 +59050,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v112.0.5600.0",
-              "revision": "version:112.0.5600.0"
+              "location": "lacros_version_skew_tests_v112.0.5602.0",
+              "revision": "version:112.0.5602.0"
             }
           ],
           "dimension_sets": [
@@ -59017,9 +59208,9 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v112.0.5600.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v112.0.5602.0/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 112.0.5600.0",
+        "description": "Run with ash-chrome version 112.0.5602.0",
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -59030,8 +59221,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v112.0.5600.0",
-              "revision": "version:112.0.5600.0"
+              "location": "lacros_version_skew_tests_v112.0.5602.0",
+              "revision": "version:112.0.5602.0"
             }
           ],
           "dimension_sets": [
@@ -59169,9 +59360,9 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v112.0.5600.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v112.0.5602.0/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 112.0.5600.0",
+        "description": "Run with ash-chrome version 112.0.5602.0",
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -59182,8 +59373,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v112.0.5600.0",
-              "revision": "version:112.0.5600.0"
+              "location": "lacros_version_skew_tests_v112.0.5602.0",
+              "revision": "version:112.0.5602.0"
             }
           ],
           "dimension_sets": [
@@ -59955,9 +60146,9 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.interactive_ui_tests.filter;../../testing/buildbot/filters/linux-lacros.interactive_ui_tests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v112.0.5600.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v112.0.5602.0/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 112.0.5600.0",
+        "description": "Run with ash-chrome version 112.0.5602.0",
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -59968,8 +60159,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v112.0.5600.0",
-              "revision": "version:112.0.5600.0"
+              "location": "lacros_version_skew_tests_v112.0.5602.0",
+              "revision": "version:112.0.5602.0"
             }
           ],
           "dimension_sets": [
diff --git a/testing/buildbot/chromium.memory.json b/testing/buildbot/chromium.memory.json
index bbb153c..ed17daf 100644
--- a/testing/buildbot/chromium.memory.json
+++ b/testing/buildbot/chromium.memory.json
@@ -18533,12 +18533,12 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.interactive_ui_tests.filter;../../testing/buildbot/filters/linux-lacros.interactive_ui_tests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v112.0.5600.0/test_ash_chrome",
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v112.0.5602.0/test_ash_chrome",
           "--test-launcher-print-test-stdio=always",
           "--combine-ash-logs-on-bots",
           "--asan-symbolize-output"
         ],
-        "description": "Run with ash-chrome version 112.0.5600.0",
+        "description": "Run with ash-chrome version 112.0.5602.0",
         "isolate_profile_data": true,
         "merge": {
           "args": [],
@@ -18550,8 +18550,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v112.0.5600.0",
-              "revision": "version:112.0.5600.0"
+              "location": "lacros_version_skew_tests_v112.0.5602.0",
+              "revision": "version:112.0.5602.0"
             }
           ],
           "dimension_sets": [
@@ -18724,12 +18724,12 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v112.0.5600.0/test_ash_chrome",
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v112.0.5602.0/test_ash_chrome",
           "--test-launcher-print-test-stdio=always",
           "--combine-ash-logs-on-bots",
           "--asan-symbolize-output"
         ],
-        "description": "Run with ash-chrome version 112.0.5600.0",
+        "description": "Run with ash-chrome version 112.0.5602.0",
         "isolate_profile_data": true,
         "merge": {
           "args": [],
@@ -18741,8 +18741,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v112.0.5600.0",
-              "revision": "version:112.0.5600.0"
+              "location": "lacros_version_skew_tests_v112.0.5602.0",
+              "revision": "version:112.0.5602.0"
             }
           ],
           "dimension_sets": [
@@ -18891,12 +18891,12 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v112.0.5600.0/test_ash_chrome",
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v112.0.5602.0/test_ash_chrome",
           "--test-launcher-print-test-stdio=always",
           "--combine-ash-logs-on-bots",
           "--asan-symbolize-output"
         ],
-        "description": "Run with ash-chrome version 112.0.5600.0",
+        "description": "Run with ash-chrome version 112.0.5602.0",
         "isolate_profile_data": true,
         "merge": {
           "args": [],
@@ -18908,8 +18908,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v112.0.5600.0",
-              "revision": "version:112.0.5600.0"
+              "location": "lacros_version_skew_tests_v112.0.5602.0",
+              "revision": "version:112.0.5602.0"
             }
           ],
           "dimension_sets": [
diff --git a/testing/buildbot/internal.chromeos.fyi.json b/testing/buildbot/internal.chromeos.fyi.json
index 08dc63cd..08798ff 100644
--- a/testing/buildbot/internal.chromeos.fyi.json
+++ b/testing/buildbot/internal.chromeos.fyi.json
@@ -1099,7 +1099,6 @@
         "args": [
           "--magic-vm-cache=magic_cros_vm_cache"
         ],
-        "experiment_percentage": 100,
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
diff --git a/testing/buildbot/test_suites.pyl b/testing/buildbot/test_suites.pyl
index 80e6c5a4..0e66b454 100644
--- a/testing/buildbot/test_suites.pyl
+++ b/testing/buildbot/test_suites.pyl
@@ -612,7 +612,6 @@
         'mixins': [
           'has_native_resultdb_integration',
         ],
-        'experiment_percentage': 100,
       },
     },
 
@@ -3903,6 +3902,11 @@
       'headless_unittests': {},
     },
 
+    'ios_blink_tests': {
+      'blink_unittests': {},
+      'content_unittests': {},
+    },
+
     'ios_clang_tests': {
       'absl_hardening_tests': {},
       'base_unittests': {},
@@ -6933,6 +6937,15 @@
       }
     },
 
+    'ios_blink_dbg_tests': {
+      'ios_blink_tests': {
+        'variants': [
+          'SIM_IPAD_PRO_3RD_GEN_16_2',
+          'SIM_IPHONE_X_16_2',
+        ]
+      },
+    },
+
     'ios_clang_tot_device_tests': {
       'ios_clang_tests': {
         'variants': [
diff --git a/testing/buildbot/variants.pyl b/testing/buildbot/variants.pyl
index 2ffda780..543cdb3 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_v112.0.5600.0/test_ash_chrome',
+      '--ash-chrome-path-override=../../lacros_version_skew_tests_v112.0.5602.0/test_ash_chrome',
     ],
-    'description': 'Run with ash-chrome version 112.0.5600.0',
+    'description': 'Run with ash-chrome version 112.0.5602.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_v112.0.5600.0',
-          'revision': 'version:112.0.5600.0',
+          'location': 'lacros_version_skew_tests_v112.0.5602.0',
+          'revision': 'version:112.0.5602.0',
         },
       ],
     },
diff --git a/testing/buildbot/waterfalls.pyl b/testing/buildbot/waterfalls.pyl
index a23a0e84..5327958 100644
--- a/testing/buildbot/waterfalls.pyl
+++ b/testing/buildbot/waterfalls.pyl
@@ -3207,6 +3207,21 @@
           'isolated_scripts': 'chromeos_isolated_scripts',
         },
       },
+      'ios-blink-dbg-fyi': {
+        'additional_compile_targets': [
+          'all'
+        ],
+        'mixins': [
+          'has_native_resultdb_integration',
+          'mac_toolchain',
+          'out_dir_arg',
+          'xcode_14_main',
+          'xctest',
+        ],
+        'test_suites': {
+          'isolated_scripts': 'ios_blink_dbg_tests'
+        },
+      },
       'ios-fieldtrial-rel': {
         'mixins': [
           'finch-chromium-swarming-pool',
diff --git a/testing/variations/fieldtrial_testing_config.json b/testing/variations/fieldtrial_testing_config.json
index 724f62ea..e4908eb 100644
--- a/testing/variations/fieldtrial_testing_config.json
+++ b/testing/variations/fieldtrial_testing_config.json
@@ -4658,7 +4658,9 @@
                 {
                     "name": "Enabled",
                     "enable_features": [
-                        "ShoppingList"
+                        "IPH_PriceNotificationsWhileBrowsing",
+                        "ShoppingList",
+                        "kSmartSortingPriceTrackingDestination"
                     ]
                 }
             ]
diff --git a/third_party/blink/common/features.cc b/third_party/blink/common/features.cc
index 56b1540..1b147ad 100644
--- a/third_party/blink/common/features.cc
+++ b/third_party/blink/common/features.cc
@@ -708,6 +708,23 @@
              "WebviewAccelerateSmallCanvases",
              base::FEATURE_DISABLED_BY_DEFAULT);
 
+BASE_FEATURE(
+    kCanvas2DHibernation,
+    "Canvas2DHibernation",
+#if BUILDFLAG(IS_MAC)
+    // Canvas hibernation is not always enabled on MacOS X due to a bug that
+    // causes content loss. TODO: Find a better fix for crbug.com/588434
+    base::FeatureState::FEATURE_DISABLED_BY_DEFAULT
+#else
+    base::FeatureState::FEATURE_ENABLED_BY_DEFAULT
+#endif
+);
+
+// Whether to losslessly compress the resulting image after canvas hibernation.
+BASE_FEATURE(kCanvasCompressHibernatedImage,
+             "CanvasCompressHibernatedImage",
+             base::FEATURE_DISABLED_BY_DEFAULT);
+
 // Whether to aggressively free resources for canvases in background pages.
 BASE_FEATURE(kCanvasFreeMemoryWhenHidden,
              "CanvasFreeMemoryWhenHidden",
@@ -1492,7 +1509,7 @@
 
 BASE_FEATURE(kSerializeAccessibilityPostLifecycle,
              "SerializeAccessibilityPostLifeycle",
-             base::FEATURE_DISABLED_BY_DEFAULT);
+             base::FEATURE_ENABLED_BY_DEFAULT);
 
 BASE_FEATURE(kThreadedPreloadScanner,
              "ThreadedPreloadScanner",
diff --git a/third_party/blink/common/interest_group/auction_config.cc b/third_party/blink/common/interest_group/auction_config.cc
index 45308cd..83e0cd8 100644
--- a/third_party/blink/common/interest_group/auction_config.cc
+++ b/third_party/blink/common/interest_group/auction_config.cc
@@ -74,6 +74,9 @@
   if (non_shared_params.buyer_timeouts.is_promise()) {
     ++total;
   }
+  if (non_shared_params.buyer_cumulative_timeouts.is_promise()) {
+    ++total;
+  }
   if (direct_from_seller_signals.is_promise()) {
     ++total;
   }
diff --git a/third_party/blink/common/interest_group/auction_config_mojom_traits.cc b/third_party/blink/common/interest_group/auction_config_mojom_traits.cc
index df2f63f..9d598b7 100644
--- a/third_party/blink/common/interest_group/auction_config_mojom_traits.cc
+++ b/third_party/blink/common/interest_group/auction_config_mojom_traits.cc
@@ -131,6 +131,7 @@
       !data.ReadSellerTimeout(&out->seller_timeout) ||
       !data.ReadPerBuyerSignals(&out->per_buyer_signals) ||
       !data.ReadBuyerTimeouts(&out->buyer_timeouts) ||
+      !data.ReadBuyerCumulativeTimeouts(&out->buyer_cumulative_timeouts) ||
       !data.ReadPerBuyerGroupLimits(&out->per_buyer_group_limits) ||
       !data.ReadPerBuyerPrioritySignals(&out->per_buyer_priority_signals) ||
       !data.ReadAllBuyersPrioritySignals(&out->all_buyers_priority_signals) ||
diff --git a/third_party/blink/common/interest_group/auction_config_mojom_traits_test.cc b/third_party/blink/common/interest_group/auction_config_mojom_traits_test.cc
index 60184e6..033d7f38 100644
--- a/third_party/blink/common/interest_group/auction_config_mojom_traits_test.cc
+++ b/third_party/blink/common/interest_group/auction_config_mojom_traits_test.cc
@@ -54,16 +54,16 @@
                 const AuctionConfig::NonSharedParams& b) {
   return std::tie(a.interest_group_buyers, a.auction_signals, a.seller_signals,
                   a.seller_timeout, a.per_buyer_signals, a.buyer_timeouts,
-                  a.per_buyer_group_limits, a.all_buyers_group_limit,
-                  a.per_buyer_priority_signals, a.all_buyers_priority_signals,
-                  a.auction_report_buyer_keys, a.auction_report_buyers,
-                  a.component_auctions) ==
+                  a.buyer_cumulative_timeouts, a.per_buyer_group_limits,
+                  a.all_buyers_group_limit, a.per_buyer_priority_signals,
+                  a.all_buyers_priority_signals, a.auction_report_buyer_keys,
+                  a.auction_report_buyers, a.component_auctions) ==
          std::tie(b.interest_group_buyers, b.auction_signals, b.seller_signals,
                   b.seller_timeout, b.per_buyer_signals, b.buyer_timeouts,
-                  b.per_buyer_group_limits, b.all_buyers_group_limit,
-                  b.per_buyer_priority_signals, b.all_buyers_priority_signals,
-                  b.auction_report_buyer_keys, b.auction_report_buyers,
-                  b.component_auctions);
+                  b.buyer_cumulative_timeouts, b.per_buyer_group_limits,
+                  b.all_buyers_group_limit, b.per_buyer_priority_signals,
+                  b.all_buyers_priority_signals, b.auction_report_buyer_keys,
+                  b.auction_report_buyers, b.component_auctions);
 }
 
 bool operator==(const AuctionConfig& a, const AuctionConfig& b) {
@@ -138,6 +138,14 @@
       AuctionConfig::MaybePromiseBuyerTimeouts::FromValue(
           std::move(buyer_timeouts));
 
+  AuctionConfig::BuyerTimeouts buyer_cumulative_timeouts;
+  buyer_cumulative_timeouts.per_buyer_timeouts.emplace();
+  (*buyer_cumulative_timeouts.per_buyer_timeouts)[buyer] = base::Seconds(432);
+  buyer_cumulative_timeouts.all_buyers_timeout = base::Seconds(234);
+  non_shared_params.buyer_cumulative_timeouts =
+      AuctionConfig::MaybePromiseBuyerTimeouts::FromValue(
+          std::move(buyer_cumulative_timeouts));
+
   non_shared_params.per_buyer_group_limits[buyer] = 10;
   non_shared_params.all_buyers_group_limit = 11;
   non_shared_params.per_buyer_priority_signals.emplace();
diff --git a/third_party/blink/public/common/features.h b/third_party/blink/public/common/features.h
index 30d3c51..6fddd69 100644
--- a/third_party/blink/public/common/features.h
+++ b/third_party/blink/public/common/features.h
@@ -270,6 +270,10 @@
 
 BLINK_COMMON_EXPORT BASE_DECLARE_FEATURE(kWebviewAccelerateSmallCanvases);
 
+BLINK_COMMON_EXPORT BASE_DECLARE_FEATURE(kCanvas2DHibernation);
+
+BLINK_COMMON_EXPORT BASE_DECLARE_FEATURE(kCanvasCompressHibernatedImage);
+
 BLINK_COMMON_EXPORT BASE_DECLARE_FEATURE(kCanvasFreeMemoryWhenHidden);
 
 BLINK_COMMON_EXPORT BASE_DECLARE_FEATURE(kCreateImageBitmapOrientationNone);
diff --git a/third_party/blink/public/common/interest_group/auction_config.h b/third_party/blink/public/common/interest_group/auction_config.h
index 4770ad8..cd415a1 100644
--- a/third_party/blink/public/common/interest_group/auction_config.h
+++ b/third_party/blink/public/common/interest_group/auction_config.h
@@ -193,6 +193,11 @@
     // Values restrict the runtime of generateBid() scripts.
     MaybePromiseBuyerTimeouts buyer_timeouts;
 
+    // Collective timeouts for all interest groups with the same buyer. Includes
+    // launching worklet processes, loading scripts and signals, and running
+    // the buyer's generateBid() functions.
+    MaybePromiseBuyerTimeouts buyer_cumulative_timeouts;
+
     // Values restrict the number of bidding interest groups for a particular
     // buyer that can participate in an auction. Values must be greater than 0.
     base::flat_map<url::Origin, std::uint16_t> per_buyer_group_limits;
diff --git a/third_party/blink/public/common/interest_group/auction_config_mojom_traits.h b/third_party/blink/public/common/interest_group/auction_config_mojom_traits.h
index debd9e73..0049df90 100644
--- a/third_party/blink/public/common/interest_group/auction_config_mojom_traits.h
+++ b/third_party/blink/public/common/interest_group/auction_config_mojom_traits.h
@@ -211,6 +211,12 @@
     return params.buyer_timeouts;
   }
 
+  static const blink::AuctionConfig::MaybePromiseBuyerTimeouts&
+  buyer_cumulative_timeouts(
+      const blink::AuctionConfig::NonSharedParams& params) {
+    return params.buyer_cumulative_timeouts;
+  }
+
   static const base::flat_map<url::Origin, std::uint16_t>&
   per_buyer_group_limits(const blink::AuctionConfig::NonSharedParams& params) {
     return params.per_buyer_group_limits;
diff --git a/third_party/blink/public/mojom/interest_group/ad_auction_service.mojom b/third_party/blink/public/mojom/interest_group/ad_auction_service.mojom
index 4c7803a..6356fe2 100644
--- a/third_party/blink/public/mojom/interest_group/ad_auction_service.mojom
+++ b/third_party/blink/public/mojom/interest_group/ad_auction_service.mojom
@@ -31,6 +31,14 @@
   kSellerSignals
 };
 
+// Represents which field that uses the AuctionAdConfigBuyerTimeouts struct is
+// getting a concrete value provided after the promise that was originally
+// passed in resolved.
+enum AuctionAdConfigBuyerTimeoutField {
+  kPerBuyerTimeouts,
+  kPerBuyerCumulativeTimeouts
+};
+
 // Used to provide a way of aborting a call to AdAuctionService.RunAdAuction
 interface AbortableAdAuction {
   // These methods should be called to provide a value for part of auction
@@ -48,9 +56,11 @@
       map<url.mojom.Origin, string>? per_buyer_signals);
 
   // Used to provide result of resolving a promise specifying
-  // `per_buyer_timeouts` field of an AuctionConfig.
+  // `per_buyer_timeouts` or `per_buyer_cumulative_timeouts` field of an
+  // AuctionConfig.
   ResolvedBuyerTimeoutsPromise(
       AuctionAdConfigAuctionId auction,
+      AuctionAdConfigBuyerTimeoutField field,
       AuctionAdConfigBuyerTimeouts buyer_timeouts);
 
   // Used to provide result of resolving a promise specifying
diff --git a/third_party/blink/public/mojom/interest_group/interest_group_types.mojom b/third_party/blink/public/mojom/interest_group/interest_group_types.mojom
index c8c0cf86..9b145b6b 100644
--- a/third_party/blink/public/mojom/interest_group/interest_group_types.mojom
+++ b/third_party/blink/public/mojom/interest_group/interest_group_types.mojom
@@ -307,8 +307,14 @@
 
   AuctionAdConfigMaybePromisePerBuyerSignals per_buyer_signals;
 
+  // Timeouts for individual interest group worklet Javascript execution.
   AuctionAdConfigMaybePromiseBuyerTimeouts buyer_timeouts;
 
+  // Cumulative timeouts for all interest groups with the same buyer. Includes
+  // launching worklet processes, loading scripts and signals, and running
+  // the buyer's generateBid() functions.
+  AuctionAdConfigMaybePromiseBuyerTimeouts buyer_cumulative_timeouts;
+
   // Keys of `per_buyer_group_limits` must be valid HTTPS origins. Values
   // restrict the number of bidding interest groups for a particular buyer
   // that can participate in an auction. Values must be greater than 0.
diff --git a/third_party/blink/renderer/controller/blink_unittests_bundle_data.filelist b/third_party/blink/renderer/controller/blink_unittests_bundle_data.filelist
index fbbf3007..962ca26 100644
--- a/third_party/blink/renderer/controller/blink_unittests_bundle_data.filelist
+++ b/third_party/blink/renderer/controller/blink_unittests_bundle_data.filelist
@@ -448,6 +448,313 @@
 ../../../../media/test/data/webm_vorbis_track_entry
 ../../../../media/test/data/webm_vp8_track_entry
 ../../../../media/test/data/yellow_pink_gradient_lossless.webp
+../../web_tests/images/resources/0colors.ico
+../../web_tests/images/resources/0x0.bmp
+../../web_tests/images/resources/12-55.jpg
+../../web_tests/images/resources/182.jpg
+../../web_tests/images/resources/1bit.ico
+../../web_tests/images/resources/1xint32_min.bmp
+../../web_tests/images/resources/2-comp.jpg
+../../web_tests/images/resources/2-dht.jpg
+../../web_tests/images/resources/23-55.jpg
+../../web_tests/images/resources/2entries.ico
+../../web_tests/images/resources/32bit.ico
+../../web_tests/images/resources/5000x5000.png
+../../web_tests/images/resources/55.jpg
+../../web_tests/images/resources/57.jpg
+../../web_tests/images/resources/58.jpg
+../../web_tests/images/resources/59.jpg
+../../web_tests/images/resources/8bit.ico
+../../web_tests/images/resources/animated-10color-fast.gif
+../../web_tests/images/resources/animated-10color.gif
+../../web_tests/images/resources/animated-gif-with-offsets.gif
+../../web_tests/images/resources/animated.gif
+../../web_tests/images/resources/animated2.gif
+../../web_tests/images/resources/apng00.png
+../../web_tests/images/resources/apng01.png
+../../web_tests/images/resources/apng08-ref.png
+../../web_tests/images/resources/apng18.png
+../../web_tests/images/resources/apng24-ref.png
+../../web_tests/images/resources/apng24.png
+../../web_tests/images/resources/apng26-ref.png
+../../web_tests/images/resources/apng26.png
+../../web_tests/images/resources/avif/README.md
+../../web_tests/images/resources/avif/alpha-mask-full-range-10bpc.avif
+../../web_tests/images/resources/avif/alpha-mask-full-range-12bpc.avif
+../../web_tests/images/resources/avif/alpha-mask-full-range-8bpc.avif
+../../web_tests/images/resources/avif/alpha-mask-limited-range-10bpc.avif
+../../web_tests/images/resources/avif/alpha-mask-limited-range-12bpc.avif
+../../web_tests/images/resources/avif/alpha-mask-limited-range-8bpc.avif
+../../web_tests/images/resources/avif/dice_444_10b_grid4x3.avif
+../../web_tests/images/resources/avif/gracehopper_422_12b_grid2x4.avif
+../../web_tests/images/resources/avif/gray1024x704.avif
+../../web_tests/images/resources/avif/green-no-alpha-ispe.avif
+../../web_tests/images/resources/avif/red-at-12-oclock-with-color-profile-10bpc.avif
+../../web_tests/images/resources/avif/red-at-12-oclock-with-color-profile-12bpc.avif
+../../web_tests/images/resources/avif/red-at-12-oclock-with-color-profile-8bpc.avif
+../../web_tests/images/resources/avif/red-at-12-oclock-with-color-profile-lossy.avif
+../../web_tests/images/resources/avif/red-at-12-oclock-with-color-profile-truncated.avif
+../../web_tests/images/resources/avif/red-at-12-oclock-with-color-profile-with-wrong-frame-header.avif
+../../web_tests/images/resources/avif/red-full-range-420-10bpc.avif
+../../web_tests/images/resources/avif/red-full-range-420-12bpc.avif
+../../web_tests/images/resources/avif/red-full-range-420-8bpc.avif
+../../web_tests/images/resources/avif/red-full-range-angle-1-420-8bpc.avif
+../../web_tests/images/resources/avif/red-full-range-angle-2-mode-0-420-8bpc.avif
+../../web_tests/images/resources/avif/red-full-range-angle-3-mode-1-420-8bpc.avif
+../../web_tests/images/resources/avif/red-full-range-bt2020-hlg-444-10bpc.avif
+../../web_tests/images/resources/avif/red-full-range-bt2020-hlg-444-12bpc.avif
+../../web_tests/images/resources/avif/red-full-range-bt2020-pq-444-10bpc.avif
+../../web_tests/images/resources/avif/red-full-range-bt2020-pq-444-12bpc.avif
+../../web_tests/images/resources/avif/red-full-range-bt709-444-8bpc.avif
+../../web_tests/images/resources/avif/red-full-range-mode-0-420-8bpc.avif
+../../web_tests/images/resources/avif/red-full-range-mode-1-420-8bpc.avif
+../../web_tests/images/resources/avif/red-full-range-unspecified-420-8bpc.avif
+../../web_tests/images/resources/avif/red-limited-range-420-10bpc.avif
+../../web_tests/images/resources/avif/red-limited-range-420-12bpc.avif
+../../web_tests/images/resources/avif/red-limited-range-420-8bpc.avif
+../../web_tests/images/resources/avif/red-limited-range-422-10bpc.avif
+../../web_tests/images/resources/avif/red-limited-range-422-12bpc.avif
+../../web_tests/images/resources/avif/red-limited-range-422-8bpc.avif
+../../web_tests/images/resources/avif/red-limited-range-444-10bpc.avif
+../../web_tests/images/resources/avif/red-limited-range-444-12bpc.avif
+../../web_tests/images/resources/avif/red-limited-range-444-8bpc.avif
+../../web_tests/images/resources/avif/red-unsupported-transfer.avif
+../../web_tests/images/resources/avif/red-with-alpha-10bpc.avif
+../../web_tests/images/resources/avif/red-with-alpha-12bpc.avif
+../../web_tests/images/resources/avif/red-with-alpha-8bpc.avif
+../../web_tests/images/resources/avif/red-with-profile-10bpc.avif
+../../web_tests/images/resources/avif/red-with-profile-12bpc.avif
+../../web_tests/images/resources/avif/red-with-profile-8bpc.avif
+../../web_tests/images/resources/avif/red.png
+../../web_tests/images/resources/avif/silver-full-range-srgb-420-8bpc.avif
+../../web_tests/images/resources/avif/silver.png
+../../web_tests/images/resources/avif/star-animated-10bpc-with-alpha.avif
+../../web_tests/images/resources/avif/star-animated-10bpc.avif
+../../web_tests/images/resources/avif/star-animated-12bpc-with-alpha.avif
+../../web_tests/images/resources/avif/star-animated-12bpc.avif
+../../web_tests/images/resources/avif/star-animated-8bpc-with-alpha.avif
+../../web_tests/images/resources/avif/star-animated-8bpc.avif
+../../web_tests/images/resources/avif/tiger_3layer_1res.avif
+../../web_tests/images/resources/avif/tiger_3layer_3res.avif
+../../web_tests/images/resources/avif/tiger_420_8b_grid1x13.avif
+../../web_tests/images/resources/bad-png.png
+../../web_tests/images/resources/bad.ico
+../../web_tests/images/resources/base-test-resources/success.png
+../../web_tests/images/resources/blue-10.png
+../../web_tests/images/resources/blue-100.png
+../../web_tests/images/resources/blue-wheel-srgb-color-profile.jpg
+../../web_tests/images/resources/blue-wheel-srgb-color-profile.png
+../../web_tests/images/resources/blue-wheel-srgb-color-profile.webp
+../../web_tests/images/resources/border-image-srgb-color-profile.png
+../../web_tests/images/resources/border-image.png
+../../web_tests/images/resources/boston.gif
+../../web_tests/images/resources/broken-image-with-invalid-format.png
+../../web_tests/images/resources/bug106024.jpg
+../../web_tests/images/resources/bug653075.ico
+../../web_tests/images/resources/busted-oval.png
+../../web_tests/images/resources/cHRM_color_spin.png
+../../web_tests/images/resources/cat.jpg
+../../web_tests/images/resources/cicp_pq.png
+../../web_tests/images/resources/cmyk-jpeg.jpg
+../../web_tests/images/resources/color-checker-adobe-color-profile.png
+../../web_tests/images/resources/color-checker-munsell-chart.js
+../../web_tests/images/resources/color-checker-srgb-color-profile.png
+../../web_tests/images/resources/color-profile-image-data-url.svg
+../../web_tests/images/resources/color-profile-image-foreign-object.svg
+../../web_tests/images/resources/color-profile-mask-image.svg
+../../web_tests/images/resources/count-down-color-test.gif
+../../web_tests/images/resources/count-down-color-test.png
+../../web_tests/images/resources/count-down-color-test.webp
+../../web_tests/images/resources/crbug.364830.webp
+../../web_tests/images/resources/crbug702934.png
+../../web_tests/images/resources/crbug752898.bmp
+../../web_tests/images/resources/crbug779261.gif
+../../web_tests/images/resources/crbug807324.png
+../../web_tests/images/resources/crbug827754.png
+../../web_tests/images/resources/cropped_mandrill.jpg
+../../web_tests/images/resources/cs-uma-cmyk-jfif-marker.jpg
+../../web_tests/images/resources/cs-uma-cmyk-no-jfif-or-adobe-markers.jpg
+../../web_tests/images/resources/cs-uma-cmyk-unknown-transform.jpg
+../../web_tests/images/resources/cs-uma-cmyk.jpg
+../../web_tests/images/resources/cs-uma-grayscale.jpg
+../../web_tests/images/resources/cs-uma-rgb-non-interleaved.jpg
+../../web_tests/images/resources/cs-uma-rgb-unknown-transform.jpg
+../../web_tests/images/resources/cs-uma-rgb.jpg
+../../web_tests/images/resources/cs-uma-two-channels-jfif-marker.jpg
+../../web_tests/images/resources/cs-uma-ycbcr-410.jpg
+../../web_tests/images/resources/cs-uma-ycbcr-411.jpg
+../../web_tests/images/resources/cs-uma-ycbcr-420-both-jfif-adobe.jpg
+../../web_tests/images/resources/cs-uma-ycbcr-420-non-interleaved.jpg
+../../web_tests/images/resources/cs-uma-ycbcr-420.jpg
+../../web_tests/images/resources/cs-uma-ycbcr-422.jpg
+../../web_tests/images/resources/cs-uma-ycbcr-440.jpg
+../../web_tests/images/resources/cs-uma-ycbcr-444.jpg
+../../web_tests/images/resources/cs-uma-ycbcr-other.jpg
+../../web_tests/images/resources/cs-uma-ycck.jpg
+../../web_tests/images/resources/dice.png
+../../web_tests/images/resources/empty-frame.png
+../../web_tests/images/resources/favicon.ico
+../../web_tests/images/resources/flip.webp
+../../web_tests/images/resources/flowchart.jpg
+../../web_tests/images/resources/full2loop.gif
+../../web_tests/images/resources/gif-loop-count.gif
+../../web_tests/images/resources/gif-loop-count.png
+../../web_tests/images/resources/gracehopper.bmp
+../../web_tests/images/resources/gracehopper.jpg
+../../web_tests/images/resources/gracehopper.png
+../../web_tests/images/resources/green-10.png
+../../web_tests/images/resources/green-100.png
+../../web_tests/images/resources/green-24x24.jpg
+../../web_tests/images/resources/green-256x256.jpg
+../../web_tests/images/resources/green-circle.svg
+../../web_tests/images/resources/green-half-stripe.png
+../../web_tests/images/resources/green.jpg
+../../web_tests/images/resources/green.png
+../../web_tests/images/resources/green_rectangle.pdf
+../../web_tests/images/resources/greenbox-3frames.cur
+../../web_tests/images/resources/grid-large.png
+../../web_tests/images/resources/grid-small.png
+../../web_tests/images/resources/grid-transparent.png
+../../web_tests/images/resources/half-circles.svg
+../../web_tests/images/resources/half_scan-2.jpg
+../../web_tests/images/resources/half_scan.jpg
+../../web_tests/images/resources/icc-v2-gbr-420-height-not-whole-mcu.jpg
+../../web_tests/images/resources/icc-v2-gbr-420-width-not-whole-mcu.jpg
+../../web_tests/images/resources/icc-v2-gbr-422-whole-mcus.jpg
+../../web_tests/images/resources/icc-v2-gbr.jpg
+../../web_tests/images/resources/icon-without-and-bitmap.ico
+../../web_tests/images/resources/invalid-animated-webp.webp
+../../web_tests/images/resources/invalid-animated-webp2.webp
+../../web_tests/images/resources/invalid-animated-webp3.webp
+../../web_tests/images/resources/invalid-animated-webp4.webp
+../../web_tests/images/resources/invalid.jpg
+../../web_tests/images/resources/invalid_vp8_vp8x.webp
+../../web_tests/images/resources/jpeg-height-exif-orientation.jpg
+../../web_tests/images/resources/large-gif-checkerboard.gif
+../../web_tests/images/resources/large-size-image-crash.jpeg
+../../web_tests/images/resources/large.webp
+../../web_tests/images/resources/missing-eoi.jpg
+../../web_tests/images/resources/missing-plte-before-trns.png
+../../web_tests/images/resources/motion-jpeg-single-frame.jpg
+../../web_tests/images/resources/mu.png
+../../web_tests/images/resources/non-interleaved_progressive-1.jpg
+../../web_tests/images/resources/non-interleaved_progressive.jpg
+../../web_tests/images/resources/oval.png
+../../web_tests/images/resources/palatted-color-png-gamma-one-color-profile.png
+../../web_tests/images/resources/pdf_test_landscape.pdf
+../../web_tests/images/resources/pixel-crack-image-background-webkit-transform-scale.png
+../../web_tests/images/resources/pixelated-hdpi.png
+../../web_tests/images/resources/png-16bit/2x2_16bit_AdobeRGB_opaque.png
+../../web_tests/images/resources/png-16bit/2x2_16bit_AdobeRGB_transparent.png
+../../web_tests/images/resources/png-16bit/2x2_16bit_DisplayP3_opaque.png
+../../web_tests/images/resources/png-16bit/2x2_16bit_DisplayP3_transparent.png
+../../web_tests/images/resources/png-16bit/2x2_16bit_ProPhoto_opaque.png
+../../web_tests/images/resources/png-16bit/2x2_16bit_ProPhoto_transparent.png
+../../web_tests/images/resources/png-16bit/2x2_16bit_Rec2020_opaque.png
+../../web_tests/images/resources/png-16bit/2x2_16bit_Rec2020_transparent.png
+../../web_tests/images/resources/png-16bit/2x2_16bit_e-sRGB_opaque.png
+../../web_tests/images/resources/png-16bit/2x2_16bit_e-sRGB_transparent.png
+../../web_tests/images/resources/png-16bit/2x2_16bit_interlaced_AdobeRGB_opaque.png
+../../web_tests/images/resources/png-16bit/2x2_16bit_interlaced_AdobeRGB_transparent.png
+../../web_tests/images/resources/png-16bit/2x2_16bit_interlaced_DisplayP3_opaque.png
+../../web_tests/images/resources/png-16bit/2x2_16bit_interlaced_DisplayP3_transparent.png
+../../web_tests/images/resources/png-16bit/2x2_16bit_interlaced_ProPhoto_opaque.png
+../../web_tests/images/resources/png-16bit/2x2_16bit_interlaced_ProPhoto_transparent.png
+../../web_tests/images/resources/png-16bit/2x2_16bit_interlaced_Rec2020_opaque.png
+../../web_tests/images/resources/png-16bit/2x2_16bit_interlaced_Rec2020_transparent.png
+../../web_tests/images/resources/png-16bit/2x2_16bit_interlaced_e-sRGB_opaque.png
+../../web_tests/images/resources/png-16bit/2x2_16bit_interlaced_e-sRGB_transparent.png
+../../web_tests/images/resources/png-16bit/2x2_16bit_interlaced_sRGB_opaque.png
+../../web_tests/images/resources/png-16bit/2x2_16bit_interlaced_sRGB_transparent.png
+../../web_tests/images/resources/png-16bit/2x2_16bit_sRGB_opaque.png
+../../web_tests/images/resources/png-16bit/2x2_16bit_sRGB_transparent.png
+../../web_tests/images/resources/png-16bit/2x2_8bit_AdobeRGB_opaque.png
+../../web_tests/images/resources/png-16bit/2x2_8bit_AdobeRGB_transparent.png
+../../web_tests/images/resources/png-16bit/2x2_8bit_DisplayP3_opaque.png
+../../web_tests/images/resources/png-16bit/2x2_8bit_DisplayP3_transparent.png
+../../web_tests/images/resources/png-16bit/2x2_8bit_ProPhoto_opaque.png
+../../web_tests/images/resources/png-16bit/2x2_8bit_ProPhoto_transparent.png
+../../web_tests/images/resources/png-16bit/2x2_8bit_Rec2020_opaque.png
+../../web_tests/images/resources/png-16bit/2x2_8bit_Rec2020_transparent.png
+../../web_tests/images/resources/png-16bit/2x2_8bit_e-sRGB_opaque.png
+../../web_tests/images/resources/png-16bit/2x2_8bit_e-sRGB_transparent.png
+../../web_tests/images/resources/png-16bit/2x2_8bit_sRGB_opaque.png
+../../web_tests/images/resources/png-16bit/2x2_8bit_sRGB_transparent.png
+../../web_tests/images/resources/png-animated-idat-not-part-of-animation.png
+../../web_tests/images/resources/png-animated-idat-part-of-animation.png
+../../web_tests/images/resources/png-animated-three-independent-frames.png
+../../web_tests/images/resources/png-color-profile-and-gamma-chunk.png
+../../web_tests/images/resources/png-extra-row-crash.png
+../../web_tests/images/resources/png-in-ico.ico
+../../web_tests/images/resources/png-simple.png
+../../web_tests/images/resources/png_per_row_alpha.png
+../../web_tests/images/resources/pngfuzz/border-image.png
+../../web_tests/images/resources/pngfuzz/oval.png
+../../web_tests/images/resources/pngfuzz/png-animated-idat-not-part-of-animation.png
+../../web_tests/images/resources/pngfuzz/png-animated-idat-part-of-animation.png
+../../web_tests/images/resources/pngfuzz/png-animated-three-independent-frames.png
+../../web_tests/images/resources/pngfuzz/png-simple.png
+../../web_tests/images/resources/quicksort.gif
+../../web_tests/images/resources/red-10.png
+../../web_tests/images/resources/red-100.png
+../../web_tests/images/resources/red-at-12-oclock-with-color-profile.jpg
+../../web_tests/images/resources/red-at-12-oclock-with-color-profile.png
+../../web_tests/images/resources/red-half-stripe.png
+../../web_tests/images/resources/rgb-jpeg-blue.jpg
+../../web_tests/images/resources/rgb-jpeg-green.jpg
+../../web_tests/images/resources/rgb-jpeg-red.jpg
+../../web_tests/images/resources/rgb-jpeg-with-adobe-marker-only.jpg
+../../web_tests/images/resources/rgb-png-with-cmyk-color-profile.png
+../../web_tests/images/resources/short-app-extension-string.gif
+../../web_tests/images/resources/size-failure.b186640109.webp
+../../web_tests/images/resources/size-failure.gif
+../../web_tests/images/resources/small-square-with-colorspin-profile.jpg
+../../web_tests/images/resources/sprite_all_fragments_same.png
+../../web_tests/images/resources/sprite_alternate_fragments_same.png
+../../web_tests/images/resources/stripes-large.png
+../../web_tests/images/resources/stripes-small.png
+../../web_tests/images/resources/test-load.jpg
+../../web_tests/images/resources/test.svg
+../../web_tests/images/resources/test.webp
+../../web_tests/images/resources/test2.webp
+../../web_tests/images/resources/test3.webp
+../../web_tests/images/resources/truncated.webp
+../../web_tests/images/resources/truncated2.webp
+../../web_tests/images/resources/twitter_favicon.ico
+../../web_tests/images/resources/webp-animated-icc-xmp.webp
+../../web_tests/images/resources/webp-animated-large.webp
+../../web_tests/images/resources/webp-animated-no-blend.webp
+../../web_tests/images/resources/webp-animated-opaque.webp
+../../web_tests/images/resources/webp-animated-semitransparent1.webp
+../../web_tests/images/resources/webp-animated-semitransparent2.webp
+../../web_tests/images/resources/webp-animated-semitransparent3.webp
+../../web_tests/images/resources/webp-animated-semitransparent4.webp
+../../web_tests/images/resources/webp-animated.webp
+../../web_tests/images/resources/webp-color-no-profile-lossy.webp
+../../web_tests/images/resources/webp-color-profile-crash.webp
+../../web_tests/images/resources/webp-color-profile-lossless.webp
+../../web_tests/images/resources/webp-color-profile-lossy-alpha.webp
+../../web_tests/images/resources/webp-color-profile-lossy.webp
+../../web_tests/images/resources/wrong-block-length.gif
+../../web_tests/images/resources/wrong-frame-dimensions.ico
+../../web_tests/images/resources/ycbcr-420-fast-int-progressive.jpg
+../../web_tests/images/resources/ycbcr-420-float-progressive.jpg
+../../web_tests/images/resources/ycbcr-420-float.jpg
+../../web_tests/images/resources/ycbcr-420-progressive-smooth.jpg
+../../web_tests/images/resources/ycbcr-420-progressive.jpg
+../../web_tests/images/resources/ycbcr-420.jpg
+../../web_tests/images/resources/ycbcr-422-float.jpg
+../../web_tests/images/resources/ycbcr-422.jpg
+../../web_tests/images/resources/ycbcr-440-float.jpg
+../../web_tests/images/resources/ycbcr-440.jpg
+../../web_tests/images/resources/ycbcr-444-float.jpg
+../../web_tests/images/resources/ycbcr-444.jpg
+../../web_tests/images/resources/ycbcr-progressive-000.jpg
+../../web_tests/images/resources/ycbcr-progressive-001.jpg
+../../web_tests/images/resources/ycbcr-progressive-002.jpg
+../../web_tests/images/resources/ycbcr-progressive-003.jpg
+../../web_tests/images/resources/ycbcr-with-cmyk-color-profile.jpg
+../../web_tests/images/resources/ycbcr-with-no-color-profile.jpg
 ../core/animation/test_data/animation-in-main-frame.html
 ../core/animation/test_data/custom-property.html
 ../core/animation/test_data/inactive-animations.html
diff --git a/third_party/blink/renderer/controller/blink_unittests_bundle_data.globlist b/third_party/blink/renderer/controller/blink_unittests_bundle_data.globlist
index 49b82dfe..b6122c53 100644
--- a/third_party/blink/renderer/controller/blink_unittests_bundle_data.globlist
+++ b/third_party/blink/renderer/controller/blink_unittests_bundle_data.globlist
@@ -8,6 +8,6 @@
 ../core/animation/test_data/*
 ../core/paint/test_data/*
 ../core/testing/data/**
-../../web_tests/impages/resources/**
+../../web_tests/images/resources/**
 ../../../../media/test/data/**
 
diff --git a/third_party/blink/renderer/core/css/resolver/style_resolver_test.cc b/third_party/blink/renderer/core/css/resolver/style_resolver_test.cc
index 0904a4a..324c140 100644
--- a/third_party/blink/renderer/core/css/resolver/style_resolver_test.cc
+++ b/third_party/blink/renderer/core/css/resolver/style_resolver_test.cc
@@ -2138,14 +2138,14 @@
   UpdateAllLifecyclePhasesForTest();
 
   EXPECT_FALSE(IsBackdropInert(html));
-  EXPECT_EQ(body->GetPseudoElement(kPseudoIdBackdrop), nullptr);
+  EXPECT_FALSE(IsBackdropInert(body));
   EXPECT_EQ(dialog->GetPseudoElement(kPseudoIdBackdrop), nullptr);
 
   dialog->showModal(exception_state);
   UpdateAllLifecyclePhasesForTest();
 
   EXPECT_FALSE(IsBackdropInert(html));
-  EXPECT_EQ(body->GetPseudoElement(kPseudoIdBackdrop), nullptr);
+  EXPECT_FALSE(IsBackdropInert(body));
   EXPECT_FALSE(IsBackdropInert(dialog));
 }
 
diff --git a/third_party/blink/renderer/core/css/selector_checker.cc b/third_party/blink/renderer/core/css/selector_checker.cc
index deb9dd1e..71fe817 100644
--- a/third_party/blink/renderer/core/css/selector_checker.cc
+++ b/third_party/blink/renderer/core/css/selector_checker.cc
@@ -1680,7 +1680,7 @@
     case CSSSelector::kPseudoFullscreen:
     // fall through
     case CSSSelector::kPseudoFullScreen:
-      return Fullscreen::IsFullscreenElement(element);
+      return Fullscreen::IsFullscreenFlagSetFor(element);
     case CSSSelector::kPseudoFullScreenAncestor:
       return element.ContainsFullScreenElement();
     case CSSSelector::kPseudoPaused: {
diff --git a/third_party/blink/renderer/core/dom/build.gni b/third_party/blink/renderer/core/dom/build.gni
index 92b63c1..39520fb 100644
--- a/third_party/blink/renderer/core/dom/build.gni
+++ b/third_party/blink/renderer/core/dom/build.gni
@@ -66,6 +66,8 @@
   "css_toggle.h",
   "css_toggle_event.cc",
   "css_toggle_event.h",
+  "css_toggle_inference.cc",
+  "css_toggle_inference.h",
   "css_toggle_map.cc",
   "css_toggle_map.h",
   "css_toggle_traversal.h",
diff --git a/third_party/blink/renderer/core/dom/css_toggle.cc b/third_party/blink/renderer/core/dom/css_toggle.cc
index 610bfc48..6b2819cd 100644
--- a/third_party/blink/renderer/core/dom/css_toggle.cc
+++ b/third_party/blink/renderer/core/dom/css_toggle.cc
@@ -14,6 +14,7 @@
 #include "third_party/blink/renderer/bindings/core/v8/v8_union_stringsequence_unsignedlong.h"
 #include "third_party/blink/renderer/core/css/style_change_reason.h"
 #include "third_party/blink/renderer/core/dom/css_toggle_event.h"
+#include "third_party/blink/renderer/core/dom/css_toggle_inference.h"
 #include "third_party/blink/renderer/core/dom/css_toggle_map.h"
 #include "third_party/blink/renderer/core/dom/css_toggle_traversal.h"
 #include "third_party/blink/renderer/core/dom/document.h"
@@ -341,6 +342,8 @@
       SetElementNeedsStyleRecalc(e, when, reason);
     }
   }
+
+  toggle_element->GetDocument().EnsureCSSToggleInference().MarkNeedsRebuild();
 }
 
 void CSSToggle::SetLaterSiblingsNeedStyleRecalc(Element* toggle_element,
@@ -354,6 +357,8 @@
       break;
     SetElementNeedsStyleRecalc(e, when, reason);
   }
+
+  toggle_element->GetDocument().EnsureCSSToggleInference().MarkNeedsRebuild();
 }
 
 const ToggleRoot* CSSToggle::FindToggleSpecifier() const {
diff --git a/third_party/blink/renderer/core/dom/css_toggle_inference.cc b/third_party/blink/renderer/core/dom/css_toggle_inference.cc
new file mode 100644
index 0000000..85f2bc43
--- /dev/null
+++ b/third_party/blink/renderer/core/dom/css_toggle_inference.cc
@@ -0,0 +1,594 @@
+// 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 "third_party/blink/renderer/core/dom/css_toggle_inference.h"
+
+#include <ostream>
+#include <utility>
+
+#include "base/check.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
+#include "third_party/blink/renderer/core/dom/css_toggle.h"
+#include "third_party/blink/renderer/core/dom/css_toggle_map.h"
+#include "third_party/blink/renderer/core/dom/css_toggle_traversal.h"
+#include "third_party/blink/renderer/core/dom/document.h"
+#include "third_party/blink/renderer/core/dom/element.h"
+#include "third_party/blink/renderer/core/html/html_element.h"
+#include "third_party/blink/renderer/platform/heap/collection_support/heap_hash_map.h"
+#include "third_party/blink/renderer/platform/heap/collection_support/heap_hash_set.h"
+
+namespace blink {
+
+void CSSToggleInference::Trace(Visitor* visitor) const {
+  visitor->Trace(document_);
+  visitor->Trace(element_roles_);
+}
+
+void CSSToggleInference::RebuildIfNeeded() {
+  if (needs_rebuild_) {
+    needs_rebuild_ = false;
+    Rebuild();
+  }
+  DCHECK(!needs_rebuild_);
+}
+
+namespace {
+
+// Check the root+trigger condition for whether something is tab-ish,
+// but not the toggle-visibility on a sibling part.
+bool MightBeTabIsh(Element* toggle_root) {
+  DCHECK(toggle_root->GetToggleMap());
+  const ComputedStyle* root_style = toggle_root->GetComputedStyle();
+  if (!root_style) {
+    return false;
+  }
+  const ToggleTriggerList* triggers = root_style->ToggleTrigger();
+  if (!triggers) {
+    return false;
+  }
+
+  for (const auto& [toggle_name, toggle] :
+       toggle_root->GetToggleMap()->Toggles()) {
+    for (const ToggleTrigger& trigger : triggers->Triggers()) {
+      if (trigger.Name() == toggle_name) {
+        return true;
+      }
+    }
+  }
+  return false;
+}
+
+absl::optional<AtomicString> IsTabsIsh(Element* container) {
+  const ComputedStyle* container_style = container->GetComputedStyle();
+  DCHECK(container_style);
+  const ToggleGroupList* container_groups = container_style->ToggleGroup();
+  DCHECK(container_groups);
+
+  for (const ToggleGroup& toggle_group : container_groups->Groups()) {
+    unsigned child_count = 0;
+    unsigned child_tab_count = 0;
+    unsigned child_panel_count = 0;
+    for (Element& child : ElementTraversal::ChildrenOf(*container)) {
+      const ComputedStyle* child_style = child.GetComputedStyle();
+      if (!child_style) {
+        continue;
+      }
+      ++child_count;
+
+      bool has_trigger = false;
+      const ToggleTriggerList* child_triggers = child_style->ToggleTrigger();
+      const ToggleRootList* child_roots = child_style->ToggleRoot();
+      if (child_triggers && child_roots) {
+        for (const ToggleTrigger& child_trigger : child_triggers->Triggers()) {
+          if (child_trigger.Name() == toggle_group.Name()) {
+            for (const ToggleRoot& child_root : child_roots->Roots()) {
+              if (child_root.Name() == toggle_group.Name() &&
+                  child_root.IsGroup()) {
+                has_trigger = true;
+                break;
+              }
+            }
+            if (has_trigger) {
+              break;
+            }
+          }
+        }
+      }
+
+      bool has_visibility =
+          child_style->ToggleVisibility() == toggle_group.Name();
+
+      if (has_trigger ^ has_visibility) {
+        if (has_trigger) {
+          ++child_tab_count;
+        } else {
+          DCHECK(has_visibility);
+          ++child_panel_count;
+        }
+      }
+    }
+    // TODO(dbaron): Should we have a condition that the number of tabs
+    // and panels are similar or equal?
+    if (child_tab_count > 0 && child_panel_count > 0 &&
+        (child_tab_count + child_panel_count) * 2 > child_count) {
+      return toggle_group.Name();
+    }
+  }
+  return absl::nullopt;
+}
+
+struct DisclosureIshInfo {
+  STACK_ALLOCATED();
+
+ public:
+  AtomicString toggle_name;
+  Element* trigger_element;
+};
+
+// Check whether something is disclosure-ish.  Assumes that
+// MightBeTabIsh was already called and false.  Returns nullopt to
+// indicate that it's not disclosure-ish, or info if it is.
+absl::optional<DisclosureIshInfo> IsDisclosureIsh(Element* toggle_root) {
+  DCHECK(toggle_root->GetToggleMap());
+  for (const auto& [toggle_name, toggle] :
+       toggle_root->GetToggleMap()->Toggles()) {
+    // We want to find out if we have exactly one descendant with
+    // toggle-trigger (for this name) and exactly one descendant with
+    // toggle-visibility (for this name), and those descendants are
+    // different.
+    Element* trigger_element = nullptr;
+    bool found_visibility = false;
+    bool found_duplicate_or_same_descendant = false;
+
+    for (Element* e :
+         CSSToggleScopeRange(toggle_root, toggle_name, toggle->Scope())) {
+      const ComputedStyle* style = e->GetComputedStyle();
+      if (!style) {
+        continue;
+      }
+
+      bool e_has_trigger = false;
+      if (const ToggleTriggerList* triggers = style->ToggleTrigger()) {
+        for (const ToggleTrigger& trigger : triggers->Triggers()) {
+          if (trigger.Name() == toggle_name) {
+            e_has_trigger = true;
+            break;
+          }
+        }
+      }
+      bool e_has_visibility = style->ToggleVisibility() == toggle_name;
+
+      if (e_has_trigger && e_has_visibility) {
+        found_duplicate_or_same_descendant = true;
+        break;
+      }
+
+      if (e_has_trigger) {
+        if (trigger_element) {
+          found_duplicate_or_same_descendant = true;
+          break;
+        }
+        trigger_element = e;
+      }
+
+      if (e_has_visibility) {
+        if (found_visibility) {
+          found_duplicate_or_same_descendant = true;
+          break;
+        }
+        found_visibility = true;
+      }
+    }
+
+    if (found_duplicate_or_same_descendant) {
+      continue;
+    }
+    if (trigger_element && found_visibility) {
+      return DisclosureIshInfo({toggle_name, trigger_element});
+    }
+  }
+  return absl::nullopt;
+}
+
+bool IsAccordionIsh(
+    Element* container,
+    const HeapHashMap<Member<Element>, AtomicString>& disclosure_ish_elements) {
+  unsigned child_element_count = 0;
+  unsigned child_disclosure_ish_count = 0;
+  for (Element& child : ElementTraversal::ChildrenOf(*container)) {
+    ++child_element_count;
+    if (disclosure_ish_elements.Contains(&child)) {
+      ++child_disclosure_ish_count;
+    }
+  }
+
+  // Define an element as accordion-ish if more than half of its
+  // children are disclosure-ish, and there are at least 2
+  // disclosure-ish children.
+  return child_disclosure_ish_count >= 2 &&
+         child_disclosure_ish_count * 2 > child_element_count;
+}
+
+}  // namespace
+
+void CSSToggleInference::Rebuild() {
+  DCHECK(!needs_rebuild_) << "Rebuild should be called via RebuildIfNeeded";
+
+  element_roles_.clear();
+
+  const HeapHashSet<WeakMember<Element>> elements_with_toggles =
+      document_->ElementsWithCSSToggles();
+
+  // TODO(dbaron): There are various things here that count siblings and
+  // have thresholds at 50%+1.  We don't currently invalidate (i.e.,
+  // set needs_rebuild_) for many of the cases that could change the
+  // result of these checks, since we only invalidate in response to
+  // changes on things that have toggles.
+  //
+  // In fact, there are probably other conditions that we don't properly
+  // invalidate for as well.  Before this code is every used for
+  // something we ship, we should verify that all the conditions tested
+  // here are properly invalidated.
+
+  // Build the set of disclosure-ish elements (and retain the toggle
+  // names associated with them, and the triggers, in two separate hash
+  // maps (for now) so that we don't have to make a new garbage
+  // collected struct).  Simultaneously build a too-broad set of
+  // tabs-ish elements that we will reduce later.
+  HeapHashMap<Member<Element>, AtomicString> disclosure_ish_elements;
+  HeapHashMap<Member<Element>, Member<Element>> disclosure_ish_element_triggers;
+  HeapHashSet<Member<Element>> maybe_tabs_ish_elements;
+  for (Element* toggle_root : elements_with_toggles) {
+    DCHECK(!disclosure_ish_elements.Contains(toggle_root));
+    if (MightBeTabIsh(toggle_root)) {
+      Element* parent = toggle_root->parentElement();
+      if (parent) {
+        const ComputedStyle* parent_style = parent->GetComputedStyle();
+        if (parent_style && parent_style->ToggleGroup()) {
+          maybe_tabs_ish_elements.insert(parent);
+        }
+      }
+    } else if (absl::optional<DisclosureIshInfo> info_option =
+                   IsDisclosureIsh(toggle_root)) {
+      disclosure_ish_elements.insert(toggle_root, info_option->toggle_name);
+      disclosure_ish_element_triggers.insert(toggle_root,
+                                             info_option->trigger_element);
+    }
+  }
+
+  // Build the set of accordion-ish elements by adding the parent of
+  // everything disclosure-ish, and then removing those that are not
+  // accordion-ish.  (This is a convenient way to test each parent once,
+  // even when it has multiple disclosure-ish children.)
+  HeapHashSet<Member<Element>> accordion_ish_elements;
+  for (Element* disclosure_ish : disclosure_ish_elements.Keys()) {
+    Element* parent = disclosure_ish->parentElement();
+    if (parent) {
+      accordion_ish_elements.insert(parent);
+    }
+  }
+  {
+    HeapHashSet<Member<Element>> trimmed_accordion_ish_elements;
+    for (Element* e : accordion_ish_elements) {
+      if (IsAccordionIsh(e, disclosure_ish_elements)) {
+        trimmed_accordion_ish_elements.insert(e);
+      }
+    }
+    std::swap(accordion_ish_elements, trimmed_accordion_ish_elements);
+  }
+
+  // Reduce the set of tabs-ish elements and record the toggle names.
+  HeapHashMap<Member<Element>, AtomicString> tabs_ish_elements;
+  for (Element* e : maybe_tabs_ish_elements) {
+    absl::optional<AtomicString> tabs_toggle_name = IsTabsIsh(e);
+    if (tabs_toggle_name) {
+      tabs_ish_elements.insert(e, *tabs_toggle_name);
+    }
+  }
+
+  // Now go through and assign the roles.
+  for (Element* toggle_root : elements_with_toggles) {
+    bool find_trigger_and_make_it_a_button = false;
+
+    Element* parent = toggle_root->parentElement();
+    const auto disclosure_ish_iter = disclosure_ish_elements.find(toggle_root);
+    if (disclosure_ish_iter != disclosure_ish_elements.end()) {
+      if (parent && accordion_ish_elements.Contains(parent)) {
+        CSSToggleRole parent_role;
+        {  // scope for lifetime of add_result
+          auto add_result = element_roles_.insert(parent, CSSToggleRole::kNone);
+          // We might have already handled parent as the parent of a
+          // different one of its children.
+          if (add_result.is_new_entry) {
+            // Determine whether we're looking at accordion or tree.
+            //
+            // Do this by walking the scopes of the children that are
+            // disclosure-ish, and re-examining their panels (the elements
+            // with toggle-visibility).
+            unsigned panel_count = 0;
+            unsigned accordion_ish_panel_count = 0;
+            // For accordions, we want to check that the disclosure-ish
+            // child actually has the same toggle name as the group
+            // established by the accordion-ish element.  So we maintain
+            // separate counts for each toggle name.
+            HashMap<AtomicString, unsigned> panel_with_toggle_group_counts;
+            for (Element& child : ElementTraversal::ChildrenOf(*parent)) {
+              if (disclosure_ish_elements.Contains(&child)) {
+                DCHECK(child.GetToggleMap());
+                for (const auto& [toggle_name, toggle] :
+                     child.GetToggleMap()->Toggles()) {
+                  for (Element* e : CSSToggleScopeRange(&child, toggle_name,
+                                                        toggle->Scope())) {
+                    const ComputedStyle* e_style = e->GetComputedStyle();
+                    if (!e_style) {
+                      continue;
+                    }
+                    if (e_style->ToggleVisibility() == toggle_name) {
+                      ++panel_count;
+                      if (toggle->IsGroup()) {
+                        auto count_add_result =
+                            panel_with_toggle_group_counts.insert(toggle_name,
+                                                                  0);
+                        DCHECK(count_add_result.is_new_entry ==
+                               (count_add_result.stored_value->value == 0));
+                        ++count_add_result.stored_value->value;
+                      } else {
+                        if (accordion_ish_elements.Contains(e)) {
+                          ++accordion_ish_panel_count;
+                        }
+                      }
+                    }
+                  }
+                }
+              }
+            }
+            DCHECK_GE(panel_count, 2u) << "unexpect IsAccordionIsh result";
+            bool enough_accordions =
+                accordion_ish_panel_count * 2 > panel_count;
+            bool enough_toggle_groups = false;
+            if (const ComputedStyle* parent_style =
+                    parent->GetComputedStyle()) {
+              if (const ToggleGroupList* parent_groups =
+                      parent_style->ToggleGroup()) {
+                for (const ToggleGroup& group : parent_groups->Groups()) {
+                  auto iter = panel_with_toggle_group_counts.find(group.Name());
+                  if (iter != panel_with_toggle_group_counts.end()) {
+                    if (iter->value * 2 > panel_count) {
+                      enough_toggle_groups = true;
+                    }
+                  }
+                }
+              }
+            }
+            if (enough_accordions ^ enough_toggle_groups) {
+              if (enough_accordions) {
+                DCHECK(!enough_toggle_groups);
+                add_result.stored_value->value = CSSToggleRole::kTree;
+              } else {
+                DCHECK(enough_toggle_groups);
+                add_result.stored_value->value = CSSToggleRole::kAccordion;
+              }
+            } else {
+              // TODO(dbaron): For now, we don't know what this is, but
+              // maybe we could do better.
+              add_result.stored_value->value = CSSToggleRole::kNone;
+            }
+          }
+          parent_role = add_result.stored_value->value;
+        }
+        switch (parent_role) {
+          case CSSToggleRole::kTree:
+          case CSSToggleRole::kTreeGroup: {
+            // TODO(dbaron): This role assignment is likely wrong!  Need to
+            // examine closely!
+            Element* trigger_element =
+                disclosure_ish_element_triggers.at(toggle_root);
+            element_roles_.insert(toggle_root, CSSToggleRole::kTreeGroup);
+            // TODO(dbaron): What if the trigger is just part of the tree item?
+            element_roles_.insert(trigger_element, CSSToggleRole::kTreeItem);
+            break;
+          }
+          case CSSToggleRole::kAccordion: {
+            Element* trigger_element =
+                disclosure_ish_element_triggers.at(toggle_root);
+            element_roles_.insert(toggle_root, CSSToggleRole::kAccordionItem);
+            element_roles_.insert(trigger_element,
+                                  CSSToggleRole::kAccordionItemButton);
+            break;
+          }
+          case CSSToggleRole::kNone:
+            find_trigger_and_make_it_a_button = true;
+            break;
+          default:
+            // This could happen if some other part of the assignment
+            // logic touches it.
+            // TODO(dbaron): Do we want to do this?
+            find_trigger_and_make_it_a_button = true;
+            break;
+        }
+      } else {
+        const AtomicString& toggle_name = disclosure_ish_iter->value;
+        DCHECK(toggle_root->GetToggleMap());
+        CSSToggle* toggle =
+            toggle_root->GetToggleMap()->Toggles().at(toggle_name);
+        DCHECK(toggle);
+        if (toggle->IsGroup()) {
+          // This is not a detectable pattern.
+          find_trigger_and_make_it_a_button = true;
+        } else {
+          // Find the panel (which we previously found when marking the
+          // element as disclosure-ish) to determine whether this is a
+          // popup or disclosure.
+          for (Element* e :
+               CSSToggleScopeRange(toggle_root, toggle_name, toggle->Scope())) {
+            const ComputedStyle* e_style = e->GetComputedStyle();
+            if (e_style && e_style->ToggleVisibility() == toggle_name) {
+              bool positioned_or_popover = e_style->HasOutOfFlowPosition();
+              if (!positioned_or_popover) {
+                HTMLElement* e_html = DynamicTo<HTMLElement>(e);
+                positioned_or_popover =
+                    e_html && e_html->PopoverType() != PopoverValueType::kNone;
+              }
+              Element* trigger_element =
+                  disclosure_ish_element_triggers.at(toggle_root);
+              if (positioned_or_popover) {
+                element_roles_.insert(trigger_element,
+                                      CSSToggleRole::kButtonWithPopup);
+              } else {
+                element_roles_.insert(toggle_root, CSSToggleRole::kDisclosure);
+                element_roles_.insert(trigger_element,
+                                      CSSToggleRole::kDisclosureButton);
+              }
+              break;
+            }
+          }
+        }
+      }
+    } else if (CSSToggle* tabs_ish_toggle = [&]() -> CSSToggle* {
+                 if (!parent) {
+                   return nullptr;
+                 }
+                 auto elements_iter = tabs_ish_elements.find(parent);
+                 if (elements_iter == tabs_ish_elements.end()) {
+                   return nullptr;
+                 }
+                 AtomicString toggle_name = elements_iter->value;
+                 DCHECK(toggle_root->GetToggleMap());
+                 ToggleMap& map = toggle_root->GetToggleMap()->Toggles();
+                 auto toggles_iter = map.find(toggle_name);
+                 if (toggles_iter == map.end()) {
+                   return nullptr;
+                 }
+                 CSSToggle* toggle = toggles_iter->value;
+                 DCHECK(toggle);
+                 DCHECK_EQ(toggle->Name(), toggle_name);
+                 return toggle;
+               }()) {
+      element_roles_.insert(parent, CSSToggleRole::kTabContainer);
+      element_roles_.insert(toggle_root, CSSToggleRole::kTab);
+
+      for (Element* e :
+           CSSToggleScopeRange(toggle_root, tabs_ish_toggle->Name(),
+                               tabs_ish_toggle->Scope())) {
+        const ComputedStyle* e_style = e->GetComputedStyle();
+        if (e_style && e_style->ToggleVisibility() == tabs_ish_toggle->Name()) {
+          element_roles_.insert(e, CSSToggleRole::kTabPanel);
+        }
+      }
+    } else {
+      CSSToggleMap* toggle_map = toggle_root->GetToggleMap();
+      DCHECK(toggle_map);
+      const CSSToggle* toggle_with_trigger = nullptr;
+      if (const ComputedStyle* style = toggle_root->GetComputedStyle()) {
+        for (const auto& [toggle_name, toggle] : toggle_map->Toggles()) {
+          if (const ToggleTriggerList* triggers = style->ToggleTrigger()) {
+            for (const ToggleTrigger& trigger : triggers->Triggers()) {
+              if (toggle_name == trigger.Name()) {
+                DCHECK_EQ(toggle_name, toggle->Name());
+                toggle_with_trigger = toggle;
+                break;
+              }
+            }
+          }
+          if (toggle_with_trigger) {
+            break;
+          }
+        }
+      }
+
+      if (toggle_with_trigger) {
+        bool found_visibility = false;
+        const AtomicString& toggle_name = toggle_with_trigger->Name();
+        for (Element* e : CSSToggleScopeRange(toggle_root, toggle_name,
+                                              toggle_with_trigger->Scope())) {
+          const ComputedStyle* e_style = e->GetComputedStyle();
+          if (e_style && e_style->ToggleVisibility() == toggle_name) {
+            found_visibility = true;
+            break;
+          }
+        }
+
+        CSSToggleRole parent_role = CSSToggleRole::kNone;
+        if (found_visibility) {
+          // TODO(dbaron): The current spec draft says that this should
+          // be "no pattern detected", but it seems bad to fail to
+          // report something.  I'm going to report button instead.
+          element_roles_.insert(toggle_root, CSSToggleRole::kButton);
+        } else if (toggle_with_trigger->IsGroup()) {
+          element_roles_.insert(toggle_root, CSSToggleRole::kRadioItem);
+          if (const ComputedStyle* parent_style = parent->GetComputedStyle()) {
+            if (const ToggleGroupList* parent_groups =
+                    parent_style->ToggleGroup()) {
+              for (const ToggleGroup& group : parent_groups->Groups()) {
+                if (toggle_with_trigger->Name() == group.Name()) {
+                  parent_role = CSSToggleRole::kRadioGroup;
+                  break;
+                }
+              }
+            }
+          }
+        } else {
+          // TODO(dbaron): Maybe this should be Switch, depending on
+          // device.
+          element_roles_.insert(toggle_root, CSSToggleRole::kCheckbox);
+          // TODO(dbaron): We should only set parent_role here when
+          // there are multiple checkbox siblings with few non-checkbox
+          // siblings!
+          parent_role = CSSToggleRole::kCheckboxGroup;
+        }
+
+        // TODO(dbaron): Figure out if we need to exclude additional
+        // cases where the parent would be assigned a different role (in
+        // order to ensure that hash map processing order doesn't affect
+        // the result).
+        if (parent_role != CSSToggleRole::kNone && parent &&
+            !accordion_ish_elements.Contains(parent)) {
+          auto parent_add_result = element_roles_.insert(parent, parent_role);
+          // prefer checkbox group to radio group if some children
+          // lead to either
+          if (parent_add_result.stored_value->value != parent_role &&
+              parent_add_result.stored_value->value ==
+                  CSSToggleRole::kRadioGroup) {
+            parent_add_result.stored_value->value = parent_role;
+          }
+        }
+      } else {
+        find_trigger_and_make_it_a_button = true;
+      }
+    }
+
+    if (find_trigger_and_make_it_a_button) {
+      CSSToggleMap* toggle_map = toggle_root->GetToggleMap();
+      DCHECK(toggle_map);
+      for (const auto& [toggle_name, toggle] : toggle_map->Toggles()) {
+        for (Element* e :
+             CSSToggleScopeRange(toggle_root, toggle_name, toggle->Scope())) {
+          if (const ComputedStyle* e_style = e->GetComputedStyle()) {
+            if (const ToggleTriggerList* triggers = e_style->ToggleTrigger()) {
+              for (const ToggleTrigger& trigger : triggers->Triggers()) {
+                if (trigger.Name() == toggle_name) {
+                  element_roles_.insert(e, CSSToggleRole::kButton);
+                  break;
+                }
+              }
+            }
+          }
+        }
+      }
+    }
+  }
+}
+
+CSSToggleRole CSSToggleInference::RoleForElement(blink::Element* element) {
+  RebuildIfNeeded();
+
+  auto iter = element_roles_.find(element);
+  if (iter == element_roles_.end()) {
+    return CSSToggleRole::kNone;
+  }
+
+  return iter->value;
+}
+
+}  // namespace blink
diff --git a/third_party/blink/renderer/core/dom/css_toggle_inference.h b/third_party/blink/renderer/core/dom/css_toggle_inference.h
new file mode 100644
index 0000000..ac679a31
--- /dev/null
+++ b/third_party/blink/renderer/core/dom/css_toggle_inference.h
@@ -0,0 +1,104 @@
+// Copyright 2022 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_DOM_CSS_TOGGLE_INFERENCE_H_
+#define THIRD_PARTY_BLINK_RENDERER_CORE_DOM_CSS_TOGGLE_INFERENCE_H_
+
+#include <cstdint>
+
+#include "third_party/blink/renderer/core/core_export.h"
+#include "third_party/blink/renderer/platform/heap/collection_support/heap_hash_map.h"
+#include "third_party/blink/renderer/platform/heap/garbage_collected.h"
+#include "third_party/blink/renderer/platform/heap/member.h"
+
+namespace blink {
+
+class Document;
+class Element;
+
+enum class CSSToggleRole : uint8_t {
+  kNone,
+  kAccordion,
+  kAccordionItem,
+  kAccordionItemButton,
+  kButton,
+  kButtonWithPopup,
+  kCheckbox,
+  kCheckboxGroup,
+  kDisclosure,
+  kDisclosureButton,
+  kListbox,
+  kListboxItem,
+  kRadioGroup,
+  kRadioItem,
+  kTab,
+  kTabContainer,
+  kTabPanel,
+  kTree,
+  kTreeGroup,
+  kTreeItem,
+};
+
+/**
+ * This is a prototype of the inference algorithm being designed in
+ * https://github.com/tabatkins/css-toggle/issues/41 .  Its purpose is
+ * to infer information that can be relatively easily mapped to ARIA
+ * roles and properties based on CSS Toggle properties (toggle-root,
+ * toggle-trigger, toggle-group, and toggle-visibility) and the DOM tree
+ * relationships of the elements with those properties.  It can infer
+ * roles on elements that lack those properties; for example, it might
+ * infer a role for an element that is the parent of a set of elements
+ * with those properties.
+ *
+ * The output of the algorithm should be a mapping from DOM element to
+ * its inferred role and properties (if any).  (When toggles are not
+ * used, inferred roles and properties should not be produced.)
+ *
+ * These outputs are intended to be used both for ARIA roles/properties
+ * and for default keyboard behaviors given to these elements.
+ *
+ * This implementation is intended as a prototype and its performance
+ * characteristics are not intended to be usable in a shipping
+ * implementation.  A sufficiently performant implementation may need to
+ * have the toggles code maintain data structures that represent the
+ * relationships between the toggles (rather than the current code that
+ * searches the tree when necessary).
+ */
+
+class CORE_EXPORT CSSToggleInference final
+    : public GarbageCollected<CSSToggleInference> {
+ public:
+  explicit CSSToggleInference(Document* document) : document_(document) {}
+
+  void Trace(Visitor* visitor) const;
+
+  void MarkNeedsRebuild() { needs_rebuild_ = true; }
+
+  // This returns role information for the element that is inferred from
+  // the patterns of using CSS toggles (see comments above describing
+  // this class).
+  //
+  // These roles have some relationship to ARIA roles but are not the
+  // same.
+  //
+  // This role information is somewhat expensive to rebuild, and is
+  // information that does *not* change when toggle state changes.
+  CSSToggleRole RoleForElement(blink::Element* element);
+
+  // TODO(dbaron): Add a separate API here for property (e.g., state)
+  // information that *does* sometimes change when toggle state is
+  // changed.
+
+ private:
+  void RebuildIfNeeded();
+  void Rebuild();
+
+  bool needs_rebuild_ = true;
+  Member<Document> document_;
+  HeapHashMap<Member<Element>, CSSToggleRole> element_roles_;
+};
+
+}  // namespace blink
+
+#endif  // THIRD_PARTY_BLINK_RENDERER_CORE_DOM_CSS_TOGGLE_INFERENCE_H_
diff --git a/third_party/blink/renderer/core/dom/css_toggle_map.cc b/third_party/blink/renderer/core/dom/css_toggle_map.cc
index e975e17..5fc02e7 100644
--- a/third_party/blink/renderer/core/dom/css_toggle_map.cc
+++ b/third_party/blink/renderer/core/dom/css_toggle_map.cc
@@ -4,6 +4,7 @@
 
 #include "third_party/blink/renderer/core/dom/css_toggle_map.h"
 
+#include "third_party/blink/renderer/core/dom/css_toggle_inference.h"
 #include "third_party/blink/renderer/core/dom/document.h"
 #include "third_party/blink/renderer/core/dom/element.h"
 #include "third_party/blink/renderer/core/dom/element_rare_data_field.h"
@@ -39,10 +40,12 @@
   // ElementsWithCSSToggles() from things that can happen mid-move.
   DCHECK(old_document.ElementsWithCSSToggles().Contains(element));
   old_document.ElementsWithCSSToggles().erase(element);
+  old_document.EnsureCSSToggleInference().MarkNeedsRebuild();
 
-  auto add_result =
-      element->GetDocument().ElementsWithCSSToggles().insert(element);
+  Document& new_document = element->GetDocument();
+  auto add_result = new_document.ElementsWithCSSToggles().insert(element);
   DCHECK(add_result.is_new_entry);
+  new_document.EnsureCSSToggleInference().MarkNeedsRebuild();
 }
 
 void CSSToggleMap::CreateToggles(const ToggleRootList* toggle_roots) {
diff --git a/third_party/blink/renderer/core/dom/document.cc b/third_party/blink/renderer/core/dom/document.cc
index 4eb4b6b..a6e4aa6 100644
--- a/third_party/blink/renderer/core/dom/document.cc
+++ b/third_party/blink/renderer/core/dom/document.cc
@@ -124,6 +124,7 @@
 #include "third_party/blink/renderer/core/dom/cdata_section.h"
 #include "third_party/blink/renderer/core/dom/comment.h"
 #include "third_party/blink/renderer/core/dom/context_features.h"
+#include "third_party/blink/renderer/core/dom/css_toggle_inference.h"
 #include "third_party/blink/renderer/core/dom/document_data.h"
 #include "third_party/blink/renderer/core/dom/document_fragment.h"
 #include "third_party/blink/renderer/core/dom/document_init.h"
@@ -2442,6 +2443,13 @@
   return true;
 }
 
+CSSToggleInference& Document::EnsureCSSToggleInference() {
+  if (!css_toggle_inference_) {
+    css_toggle_inference_ = MakeGarbageCollected<CSSToggleInference>(this);
+  }
+  return *css_toggle_inference_;
+}
+
 void Document::ApplyScrollRestorationLogic() {
   DCHECK(View());
   // This function is not re-entrant. However, the places that invoke this are
@@ -8608,6 +8616,7 @@
   visitor->Trace(popovers_waiting_to_hide_);
   visitor->Trace(elements_with_css_toggles_);
   visitor->Trace(elements_needing_style_recalc_for_toggle_);
+  visitor->Trace(css_toggle_inference_);
   visitor->Trace(load_event_delay_timer_);
   visitor->Trace(plugin_loading_timer_);
   visitor->Trace(elem_sheet_);
diff --git a/third_party/blink/renderer/core/dom/document.h b/third_party/blink/renderer/core/dom/document.h
index 255c64e3..11ba2d1 100644
--- a/third_party/blink/renderer/core/dom/document.h
+++ b/third_party/blink/renderer/core/dom/document.h
@@ -131,6 +131,7 @@
 class BeforeUnloadEventListener;
 class CDATASection;
 class CSSStyleSheet;
+class CSSToggleInference;
 class CanvasFontCache;
 class ChromeClient;
 class Comment;
@@ -1567,6 +1568,8 @@
   // Call SetNeedsStyleRecalc for elements from AddToRecalcStyleForToggle;
   // return whether any calls were made.
   bool SetNeedsStyleRecalcForToggles();
+  CSSToggleInference* GetCSSToggleInference() { return css_toggle_inference_; }
+  CSSToggleInference& EnsureCSSToggleInference();
 
   // A non-null template_document_host_ implies that |this| was created by
   // EnsureTemplateDocument().
@@ -2414,6 +2417,8 @@
   // Elements that need to be restyled because a toggle was created on them,
   // or a prior sibling, during the previous restyle.
   HeapHashSet<Member<Element>> elements_needing_style_recalc_for_toggle_;
+  // The inference engine for CSS toggles.
+  Member<CSSToggleInference> css_toggle_inference_;
 
   int load_event_delay_count_;
 
diff --git a/third_party/blink/renderer/core/events/toggle_event.h b/third_party/blink/renderer/core/events/toggle_event.h
index b51ea83..a5fa756 100644
--- a/third_party/blink/renderer/core/events/toggle_event.h
+++ b/third_party/blink/renderer/core/events/toggle_event.h
@@ -15,18 +15,17 @@
   DEFINE_WRAPPERTYPEINFO();
 
  public:
-  static ToggleEvent* Create() { return MakeGarbageCollected<ToggleEvent>(); }
   static ToggleEvent* Create(const AtomicString& type,
                              const ToggleEventInit* initializer) {
     return MakeGarbageCollected<ToggleEvent>(type, initializer);
   }
-  static ToggleEvent* CreateBubble(const AtomicString& type,
-                                   Event::Cancelable cancelable,
-                                   const String& old_state,
-                                   const String& new_state) {
+  static ToggleEvent* Create(const AtomicString& type,
+                             Event::Cancelable cancelable,
+                             const String& old_state,
+                             const String& new_state) {
     auto* event = MakeGarbageCollected<ToggleEvent>(type, cancelable, old_state,
                                                     new_state);
-    event->SetBubbles(true);
+    DCHECK(!event->bubbles());
     return event;
   }
 
diff --git a/third_party/blink/renderer/core/frame/local_dom_window.h b/third_party/blink/renderer/core/frame/local_dom_window.h
index e04ea0a2..cef49d1 100644
--- a/third_party/blink/renderer/core/frame/local_dom_window.h
+++ b/third_party/blink/renderer/core/frame/local_dom_window.h
@@ -34,6 +34,7 @@
 #include "services/metrics/public/cpp/ukm_source_id.h"
 #include "services/network/public/mojom/content_security_policy.mojom-blink.h"
 #include "third_party/blink/public/common/frame/fullscreen_request_token.h"
+#include "third_party/blink/public/common/frame/history_user_activation_state.h"
 #include "third_party/blink/public/common/frame/payment_request_token.h"
 #include "third_party/blink/public/common/metrics/post_message_counter.h"
 #include "third_party/blink/public/common/scheduler/task_attribution_id.h"
diff --git a/third_party/blink/renderer/core/frame/visual_viewport.cc b/third_party/blink/renderer/core/frame/visual_viewport.cc
index 255328f..0ffd6827 100644
--- a/third_party/blink/renderer/core/frame/visual_viewport.cc
+++ b/third_party/blink/renderer/core/frame/visual_viewport.cc
@@ -169,7 +169,7 @@
         GetChromeClient()->GetDeviceEmulationTransform();
     if (!device_emulation_transform.IsIdentity()) {
       TransformPaintPropertyNode::State state{{device_emulation_transform}};
-      state.in_subtree_of_page_scale = false;
+      state.flags.in_subtree_of_page_scale = false;
       if (!device_emulation_transform_node_) {
         device_emulation_transform_node_ = TransformPaintPropertyNode::Create(
             *transform_parent, std::move(state));
@@ -189,7 +189,7 @@
     DCHECK(!transform_parent->Unalias().IsInSubtreeOfPageScale());
 
     TransformPaintPropertyNode::State state;
-    state.in_subtree_of_page_scale = false;
+    state.flags.in_subtree_of_page_scale = false;
     // TODO(crbug.com/877794) Should create overscroll elasticity transform node
     // based on settings.
     if (!overscroll_elasticity_transform_node_) {
@@ -214,7 +214,7 @@
     TransformPaintPropertyNode::State state;
     if (scale_ != 1.f)
       state.transform_and_origin.matrix = gfx::Transform::MakeScale(scale_);
-    state.in_subtree_of_page_scale = false;
+    state.flags.in_subtree_of_page_scale = false;
     state.direct_compositing_reasons = CompositingReason::kViewport;
     state.compositor_element_id = page_scale_element_id_;
 
diff --git a/third_party/blink/renderer/core/fullscreen/fullscreen.cc b/third_party/blink/renderer/core/fullscreen/fullscreen.cc
index c91a612..f075acd 100644
--- a/third_party/blink/renderer/core/fullscreen/fullscreen.cc
+++ b/third_party/blink/renderer/core/fullscreen/fullscreen.cc
@@ -159,7 +159,7 @@
 };
 
 using ElementMetaParamsMap =
-    HeapHashMap<WeakMember<Element>, Member<const MetaParams>>;
+    HeapHashMap<WeakMember<const Element>, Member<const MetaParams>>;
 
 ElementMetaParamsMap& FullscreenParamsMap() {
   DEFINE_STATIC_LOCAL(Persistent<ElementMetaParamsMap>, map,
@@ -167,22 +167,22 @@
   return *map;
 }
 
-bool HasFullscreenFlag(Element& element) {
+bool HasFullscreenFlag(const Element& element) {
   return FullscreenParamsMap().Contains(&element);
 }
 
-void SetFullscreenFlag(Element& element,
+void SetFullscreenFlag(const Element& element,
                        FullscreenRequestType request_type,
                        const FullscreenOptions* options) {
   FullscreenParamsMap().insert(
       &element, MakeGarbageCollected<MetaParams>(request_type, options));
 }
 
-void UnsetFullscreenFlag(Element& element) {
+void UnsetFullscreenFlag(const Element& element) {
   FullscreenParamsMap().erase(&element);
 }
 
-FullscreenRequestType GetRequestType(Element& element) {
+FullscreenRequestType GetRequestType(const Element& element) {
   return FullscreenParamsMap().find(&element)->value->request_type();
 }
 
@@ -1153,6 +1153,10 @@
   // layer. This is done in Element::RemovedFrom.
 }
 
+bool Fullscreen::IsFullscreenFlagSetFor(const Element& element) {
+  return HasFullscreenFlag(element);
+}
+
 void Fullscreen::Trace(Visitor* visitor) const {
   visitor->Trace(pending_requests_);
   visitor->Trace(pending_exits_);
diff --git a/third_party/blink/renderer/core/fullscreen/fullscreen.h b/third_party/blink/renderer/core/fullscreen/fullscreen.h
index a529d3f..b1a3790 100644
--- a/third_party/blink/renderer/core/fullscreen/fullscreen.h
+++ b/third_party/blink/renderer/core/fullscreen/fullscreen.h
@@ -63,9 +63,14 @@
 
   static Element* FullscreenElementFrom(Document&);
   static Element* FullscreenElementForBindingFrom(TreeScope&);
+  // Returns true if the Element is the topmost element in its document's top
+  // layer whose fullscreen flag is set.
   static bool IsFullscreenElement(const Element&);
   static bool IsInFullscreenElementStack(const Element&);
   static bool HasFullscreenElements();
+  // Returns true if the Element's fullscreen flag is set. A Document may have
+  // multiple elements with the fullscreen flag set.
+  static bool IsFullscreenFlagSetFor(const Element&);
 
   static void RequestFullscreen(Element&);
   static ScriptPromise RequestFullscreen(
diff --git a/third_party/blink/renderer/core/html/html_element.cc b/third_party/blink/renderer/core/html/html_element.cc
index 4743bfe6..08c286b 100644
--- a/third_party/blink/renderer/core/html/html_element.cc
+++ b/third_party/blink/renderer/core/html/html_element.cc
@@ -1376,10 +1376,10 @@
   }
 
   // Fire the "opening" beforetoggle event.
-  auto* event = ToggleEvent::CreateBubble(
+  auto* event = ToggleEvent::Create(
       event_type_names::kBeforetoggle, Event::Cancelable::kYes,
       /*old_state*/ "closed", /*new_state*/ "open");
-  DCHECK(event->bubbles());
+  DCHECK(!event->bubbles());
   DCHECK(event->cancelable());
   DCHECK_EQ(event->oldState(), "closed");
   DCHECK_EQ(event->newState(), "open");
@@ -1467,12 +1467,12 @@
   } else {
     GetPopoverData()->setPendingToggleEventStartedClosed(true);
   }
-  after_event = ToggleEvent::CreateBubble(event_type_names::kToggle,
-                                          Event::Cancelable::kNo, old_state,
-                                          /*new_state*/ "open");
+  after_event = ToggleEvent::Create(event_type_names::kToggle,
+                                    Event::Cancelable::kNo, old_state,
+                                    /*new_state*/ "open");
   DCHECK_EQ(after_event->newState(), "open");
   DCHECK_EQ(after_event->oldState(), old_state);
-  DCHECK(after_event->bubbles());
+  DCHECK(!after_event->bubbles());
   DCHECK(!after_event->cancelable());
   after_event->SetTarget(this);
   GetPopoverData()->setPendingToggleEventTask(PostCancellableTask(
@@ -1624,10 +1624,10 @@
   }
 
   // Fire the "closing" beforetoggle event.
-  auto* event = ToggleEvent::CreateBubble(
+  auto* event = ToggleEvent::Create(
       event_type_names::kBeforetoggle, Event::Cancelable::kNo,
       /*old_state*/ "open", /*new_state*/ "closed");
-  DCHECK(event->bubbles());
+  DCHECK(!event->bubbles());
   DCHECK(!event->cancelable());
   DCHECK_EQ(event->oldState(), "open");
   DCHECK_EQ(event->newState(), "closed");
@@ -1677,12 +1677,12 @@
   } else {
     GetPopoverData()->setPendingToggleEventStartedClosed(false);
   }
-  after_event = ToggleEvent::CreateBubble(event_type_names::kToggle,
-                                          Event::Cancelable::kNo, old_state,
-                                          /*new_state*/ "closed");
+  after_event = ToggleEvent::Create(event_type_names::kToggle,
+                                    Event::Cancelable::kNo, old_state,
+                                    /*new_state*/ "closed");
   DCHECK_EQ(after_event->newState(), "closed");
   DCHECK_EQ(after_event->oldState(), old_state);
-  DCHECK(after_event->bubbles());
+  DCHECK(!after_event->bubbles());
   DCHECK(!after_event->cancelable());
   after_event->SetTarget(this);
   GetPopoverData()->setPendingToggleEventTask(PostCancellableTask(
diff --git a/third_party/blink/renderer/core/layout/layout_object.cc b/third_party/blink/renderer/core/layout/layout_object.cc
index 17a39e0..d7a5ad0 100644
--- a/third_party/blink/renderer/core/layout/layout_object.cc
+++ b/third_party/blink/renderer/core/layout/layout_object.cc
@@ -42,6 +42,7 @@
 #include "third_party/blink/renderer/core/css/style_change_reason.h"
 #include "third_party/blink/renderer/core/css/style_engine.h"
 #include "third_party/blink/renderer/core/display_lock/display_lock_utilities.h"
+#include "third_party/blink/renderer/core/dom/css_toggle_inference.h"
 #include "third_party/blink/renderer/core/dom/css_toggle_map.h"
 #include "third_party/blink/renderer/core/dom/element_traversal.h"
 #include "third_party/blink/renderer/core/dom/first_letter_pseudo_element.h"
@@ -3114,6 +3115,12 @@
     }
   }
 
+  if (old_style &&
+      (old_style->ToggleTrigger() != StyleRef().ToggleTrigger() ||
+       old_style->ToggleVisibility() != StyleRef().ToggleVisibility())) {
+    GetDocument().EnsureCSSToggleInference().MarkNeedsRebuild();
+  }
+
   if (StyleRef().AnchorName())
     MarkMayHaveAnchorQuery();
 }
diff --git a/third_party/blink/renderer/core/navigation_api/navigation_api.cc b/third_party/blink/renderer/core/navigation_api/navigation_api.cc
index 9133670..61ed0bb 100644
--- a/third_party/blink/renderer/core/navigation_api/navigation_api.cc
+++ b/third_party/blink/renderer/core/navigation_api/navigation_api.cc
@@ -756,8 +756,8 @@
     return DispatchResult::kContinue;
   }
 
-  auto* script_state = ToScriptStateForMainWorld(window_->GetFrame());
-  DCHECK(script_state);
+  LocalFrame* frame = window_->GetFrame();
+  auto* script_state = ToScriptStateForMainWorld(frame);
   ScriptState::Scope scope(script_state);
 
   if (params->frame_load_type == WebFrameLoadType::kBackForward &&
@@ -796,8 +796,15 @@
   }
   init->setDestination(destination);
 
+  bool should_allow_traversal_cancellation =
+      RuntimeEnabledFeatures::NavigateEventCancelableTraversalsEnabled() &&
+      params->frame_load_type == WebFrameLoadType::kBackForward &&
+      params->event_type != NavigateEventType::kCrossDocument &&
+      frame->IsMainFrame() &&
+      (!params->is_browser_initiated || frame->IsHistoryUserActivationActive());
   init->setCancelable(params->frame_load_type !=
-                      WebFrameLoadType::kBackForward);
+                          WebFrameLoadType::kBackForward ||
+                      should_allow_traversal_cancellation);
   init->setCanIntercept(
       CanChangeToUrlForHistoryApi(params->url, window_->GetSecurityOrigin(),
                                   current_url) &&
@@ -840,8 +847,13 @@
   DispatchEvent(*navigate_event);
 
   if (navigate_event->defaultPrevented()) {
-    if (!navigate_event->signal()->aborted())
+    if (!navigate_event->signal()->aborted()) {
+      if (params->frame_load_type == WebFrameLoadType::kBackForward &&
+          window_->GetFrame()) {
+        window_->GetFrame()->ConsumeHistoryUserActivation();
+      }
       FinalizeWithAbortedNavigationError(script_state, ongoing_navigation_);
+    }
     return DispatchResult::kAbort;
   }
 
diff --git a/third_party/blink/renderer/core/navigation_api/navigation_api.h b/third_party/blink/renderer/core/navigation_api/navigation_api.h
index b438c7da..01b0447 100644
--- a/third_party/blink/renderer/core/navigation_api/navigation_api.h
+++ b/third_party/blink/renderer/core/navigation_api/navigation_api.h
@@ -23,6 +23,8 @@
 
 namespace blink {
 
+class DOMException;
+class HistoryItem;
 class NavigationApiNavigation;
 class NavigationUpdateCurrentEntryOptions;
 class NavigationHistoryEntry;
@@ -32,8 +34,7 @@
 class NavigationResult;
 class NavigationOptions;
 class NavigationTransition;
-class DOMException;
-class HistoryItem;
+class RegisteredEventListener;
 class SerializedScriptValue;
 
 class CORE_EXPORT NavigationApi final
diff --git a/third_party/blink/renderer/core/navigation_api/navigation_api_test.cc b/third_party/blink/renderer/core/navigation_api/navigation_api_test.cc
index bef09df..c6a2667 100644
--- a/third_party/blink/renderer/core/navigation_api/navigation_api_test.cc
+++ b/third_party/blink/renderer/core/navigation_api/navigation_api_test.cc
@@ -6,6 +6,7 @@
 
 #include "testing/gtest/include/gtest/gtest.h"
 #include "third_party/blink/public/mojom/commit_result/commit_result.mojom-blink.h"
+#include "third_party/blink/public/platform/web_runtime_features.h"
 #include "third_party/blink/renderer/core/frame/frame_test_helpers.h"
 #include "third_party/blink/renderer/core/loader/document_loader.h"
 #include "third_party/blink/renderer/core/loader/frame_load_request.h"
@@ -14,6 +15,17 @@
 
 namespace blink {
 
+// static
+HistoryItem* MakeHistoryItemFor(const KURL& url, const String& key) {
+  HistoryItem* item = MakeGarbageCollected<HistoryItem>();
+  item->SetURL(url);
+  item->SetDocumentSequenceNumber(1234);
+  item->SetNavigationApiKey(key);
+  // The |item| has a unique default item sequence number. Reusing an item
+  // sequence number will suppress the naivgate event, so don't overwrite it.
+  return item;
+}
+
 class NavigationApiTest : public testing::Test {
  public:
   void TearDown() override {
@@ -59,7 +71,7 @@
   EXPECT_TRUE(client.BeginNavigationCalled());
 }
 
-TEST_F(NavigationApiTest, BrowserInitiatedSameDocumentBackForwardUncancelable) {
+TEST_F(NavigationApiTest, BrowserInitiatedSameDocumentBackForward) {
   url_test_helpers::RegisterMockedURLLoad(
       url_test_helpers::ToKURL(
           "https://example.com/navigation-api/onnavigate-preventDefault.html"),
@@ -69,12 +81,75 @@
   web_view_helper.InitializeAndLoad(
       "https://example.com/navigation-api/onnavigate-preventDefault.html");
 
+  LocalFrame* frame = web_view_helper.LocalMainFrame()->GetFrame();
+  DocumentLoader* document_loader = frame->Loader().GetDocumentLoader();
+  const KURL& url = document_loader->Url();
+  const String& key = document_loader->GetHistoryItem()->GetNavigationApiKey();
+
   // Emulate a same-document back-forward navigation initiated by browser UI.
   // It should be uncancelable, even though the onnavigate handler will try.
-  auto& frame_loader = web_view_helper.LocalMainFrame()->GetFrame()->Loader();
-  HistoryItem* item = frame_loader.GetDocumentLoader()->GetHistoryItem();
-  auto result = frame_loader.GetDocumentLoader()->CommitSameDocumentNavigation(
-      item->Url(), WebFrameLoadType::kBackForward, item,
+  auto result1 = document_loader->CommitSameDocumentNavigation(
+      url, WebFrameLoadType::kBackForward, MakeHistoryItemFor(url, key),
+      ClientRedirectPolicy::kNotClientRedirect,
+      false /* has_transient_user_activation */, nullptr /* initiator_origin */,
+      false /* is_synchronously_committed */,
+      mojom::blink::TriggeringEventInfo::kNotFromEvent,
+      true /* is_browser_initiated */, absl::nullopt);
+  EXPECT_EQ(result1, mojom::blink::CommitResult::Ok);
+
+  // Now that there's been a user activation, the onnavigate handler should be
+  // able to cancel the navigation (which will consume the user activation).
+  LocalFrame::NotifyUserActivation(
+      frame, mojom::UserActivationNotificationType::kTest);
+  auto result2 = document_loader->CommitSameDocumentNavigation(
+      url, WebFrameLoadType::kBackForward, MakeHistoryItemFor(url, key),
+      ClientRedirectPolicy::kNotClientRedirect,
+      false /* has_transient_user_activation */, nullptr /* initiator_origin */,
+      false /* is_synchronously_committed */,
+      mojom::blink::TriggeringEventInfo::kNotFromEvent,
+      true /* is_browser_initiated */, absl::nullopt);
+  EXPECT_EQ(result2, mojom::blink::CommitResult::Aborted);
+
+  // Having consumed the user activation, the onnavigate handler should not be
+  // able to cancel the next navigation.
+  auto result3 = document_loader->CommitSameDocumentNavigation(
+      url, WebFrameLoadType::kBackForward, MakeHistoryItemFor(url, key),
+      ClientRedirectPolicy::kNotClientRedirect,
+      false /* has_transient_user_activation */, nullptr /* initiator_origin */,
+      false /* is_synchronously_committed */,
+      mojom::blink::TriggeringEventInfo::kNotFromEvent,
+      true /* is_browser_initiated */, absl::nullopt);
+  EXPECT_EQ(result3, mojom::blink::CommitResult::Ok);
+}
+
+TEST_F(NavigationApiTest, BrowserInitiatedSameDocumentBackForwardUncancelable) {
+  // Disable the feature that allows navigate events to cancel traversals, and
+  // ensure that the cancellation fails.
+  WebRuntimeFeatures::EnableFeatureFromString(
+      "NavigateEventCancelableTraversals", false);
+
+  url_test_helpers::RegisterMockedURLLoad(
+      url_test_helpers::ToKURL(
+          "https://example.com/navigation-api/onnavigate-preventDefault.html"),
+      test::CoreTestDataPath("navigation-api/onnavigate-preventDefault.html"));
+
+  frame_test_helpers::WebViewHelper web_view_helper;
+  web_view_helper.InitializeAndLoad(
+      "https://example.com/navigation-api/onnavigate-preventDefault.html");
+
+  LocalFrame* frame = web_view_helper.LocalMainFrame()->GetFrame();
+  DocumentLoader* document_loader = frame->Loader().GetDocumentLoader();
+  const KURL& url = document_loader->Url();
+  const String& key = document_loader->GetHistoryItem()->GetNavigationApiKey();
+
+  // Emulate a same-document back-forward navigation initiated by browser UI and
+  // with user activation. The navigate event would be cancelable if
+  // kNavigateEventCancelableTraversals were enabled, but since we disabled it,
+  // the cancel should fail and the navigation should proceed.
+  LocalFrame::NotifyUserActivation(
+      frame, mojom::UserActivationNotificationType::kTest);
+  auto result = document_loader->CommitSameDocumentNavigation(
+      url, WebFrameLoadType::kBackForward, MakeHistoryItemFor(url, key),
       ClientRedirectPolicy::kNotClientRedirect,
       /*has_transient_user_activation=*/false, /*initiator_origin=*/nullptr,
       /*is_synchronously_committed=*/false,
diff --git a/third_party/blink/renderer/core/paint/paint_property_tree_builder.cc b/third_party/blink/renderer/core/paint/paint_property_tree_builder.cc
index 6e361af..f3d5c565 100644
--- a/third_party/blink/renderer/core/paint/paint_property_tree_builder.cc
+++ b/third_party/blink/renderer/core/paint/paint_property_tree_builder.cc
@@ -644,7 +644,7 @@
   if (paint_offset_translation) {
     TransformPaintPropertyNode::State state{
         {gfx::Transform::MakeTranslation(*paint_offset_translation)}};
-    state.flattens_inherited_transform =
+    state.flags.flattens_inherited_transform =
         context_.should_flatten_inherited_transform;
     state.rendering_context_id = context_.rendering_context_id;
     state.direct_compositing_reasons =
@@ -662,7 +662,7 @@
 
     if (IsA<LayoutView>(object_)) {
       DCHECK(object_.GetFrame());
-      state.is_frame_paint_offset_translation = true;
+      state.flags.is_frame_paint_offset_translation = true;
       state.visible_frame_element_id =
           object_.GetFrame()->GetVisibleToHitTesting()
               ? CompositorElementIdFromUniqueObjectId(
@@ -707,7 +707,7 @@
           box_model.UniqueId(),
           CompositorElementIdNamespace::kStickyTranslation);
       state.rendering_context_id = context_.rendering_context_id;
-      state.flattens_inherited_transform =
+      state.flags.flattens_inherited_transform =
           context_.should_flatten_inherited_transform;
 
       if (state.direct_compositing_reasons) {
@@ -804,7 +804,7 @@
           box.UniqueId(),
           CompositorElementIdNamespace::kAnchorScrollTranslation);
       state.rendering_context_id = context_.rendering_context_id;
-      state.flattens_inherited_transform =
+      state.flags.flattens_inherited_transform =
           context_.should_flatten_inherited_transform;
 
       state.anchor_scroll_containers_data =
@@ -944,10 +944,10 @@
       // be included here, such as setting animation_is_axis_aligned.
       state.direct_compositing_reasons =
           direct_compositing_reasons & CompositingReasonsForTransformProperty();
-      state.flattens_inherited_transform =
+      state.flags.flattens_inherited_transform =
           context_.should_flatten_inherited_transform;
       state.rendering_context_id = context_.rendering_context_id;
-      state.is_for_svg_child = true;
+      state.flags.is_for_svg_child = true;
       state.compositor_element_id = GetCompositorElementId(
           CompositorElementIdNamespace::kPrimaryTransform);
 
@@ -1164,7 +1164,7 @@
         // TODO(crbug.com/1185254): Make this work correctly for block
         // fragmentation. It's the size of each individual NGPhysicalBoxFragment
         // that's interesting, not the total LayoutBox size.
-        state.animation_is_axis_aligned =
+        state.flags.animation_is_axis_aligned =
             UpdateBoxSizeAndCheckActiveAnimationAxisAlignment(
                 box, full_context_.direct_compositing_reasons);
       }
@@ -1173,7 +1173,7 @@
           full_context_.direct_compositing_reasons &
           compositing_reasons_for_property;
 
-      state.flattens_inherited_transform =
+      state.flags.flattens_inherited_transform =
           context_.should_flatten_inherited_transform;
       if (running_on_compositor_test) {
         state.compositor_element_id =
@@ -2264,7 +2264,7 @@
           {matrix,
            gfx::Point3F(PerspectiveOrigin(To<LayoutBox>(object_)) +
                         gfx::Vector2dF(context_.current.paint_offset))}};
-      state.flattens_inherited_transform =
+      state.flags.flattens_inherited_transform =
           context_.should_flatten_inherited_transform;
       state.rendering_context_id = context_.rendering_context_id;
       OnUpdateTransform(properties_->UpdatePerspective(
@@ -2298,7 +2298,7 @@
     if (!content_to_parent_space.IsIdentity()) {
       TransformPaintPropertyNode::State state;
       state.transform_and_origin = {content_to_parent_space.ToTransform()};
-      state.flattens_inherited_transform =
+      state.flags.flattens_inherited_transform =
           context_.should_flatten_inherited_transform;
       state.rendering_context_id = context_.rendering_context_id;
       OnUpdateTransform(properties_->UpdateReplacedContentTransform(
@@ -2435,7 +2435,7 @@
             box.GetScrollableArea()->PendingScrollAnchorAdjustment();
         box.GetScrollableArea()->ClearPendingScrollAnchorAdjustment();
       }
-      state.flattens_inherited_transform =
+      state.flags.flattens_inherited_transform =
           context_.should_flatten_inherited_transform;
       state.rendering_context_id = context_.rendering_context_id;
       state.direct_compositing_reasons =
@@ -2447,7 +2447,7 @@
       // scrolling contents.
       if (object_.HasTransform() && object_.StyleRef().BackfaceVisibility() ==
                                         EBackfaceVisibility::kHidden) {
-        state.delegates_to_parent_for_backface = true;
+        state.flags.delegates_to_parent_for_backface = true;
       }
 
       auto effective_change_type = properties_->UpdateScrollTranslation(
diff --git a/third_party/blink/renderer/modules/accessibility/ax_image_map_link.cc b/third_party/blink/renderer/modules/accessibility/ax_image_map_link.cc
index 138cb79..0dfb3069 100644
--- a/third_party/blink/renderer/modules/accessibility/ax_image_map_link.cc
+++ b/third_party/blink/renderer/modules/accessibility/ax_image_map_link.cc
@@ -80,11 +80,6 @@
   return ax::mojom::blink::Role::kGenericContainer;
 }
 
-bool AXImageMapLink::ComputeAccessibilityIsIgnored(
-    IgnoredReasons* ignored_reasons) const {
-  return AccessibilityIsIgnoredByDefault(ignored_reasons);
-}
-
 Element* AXImageMapLink::ActionElement() const {
   return AnchorElement();
 }
diff --git a/third_party/blink/renderer/modules/accessibility/ax_image_map_link.h b/third_party/blink/renderer/modules/accessibility/ax_image_map_link.h
index f47e92aa..eb9b5a75 100644
--- a/third_party/blink/renderer/modules/accessibility/ax_image_map_link.h
+++ b/third_party/blink/renderer/modules/accessibility/ax_image_map_link.h
@@ -54,7 +54,6 @@
   HTMLMapElement* MapElement() const;
 
   ax::mojom::blink::Role NativeRoleIgnoringAria() const override;
-  bool ComputeAccessibilityIsIgnored(IgnoredReasons* = nullptr) const override;
   bool CanHaveChildren() const override {
     // If the area has child nodes, those will be rendered, and the combination
     // of Role::kGenericContainer and CanHaveChildren() = true allows for those
diff --git a/third_party/blink/renderer/modules/accessibility/ax_layout_object.cc b/third_party/blink/renderer/modules/accessibility/ax_layout_object.cc
index 0bfd1de..8a14915 100644
--- a/third_party/blink/renderer/modules/accessibility/ax_layout_object.cc
+++ b/third_party/blink/renderer/modules/accessibility/ax_layout_object.cc
@@ -373,28 +373,6 @@
 // Whether objects are ignored, i.e. not included in the tree.
 //
 
-AXObjectInclusion AXLayoutObject::DefaultObjectInclusion(
-    IgnoredReasons* ignored_reasons) const {
-  if (!layout_object_) {
-    if (ignored_reasons)
-      ignored_reasons->push_back(IgnoredReason(kAXNotRendered));
-    return kIgnoreObject;
-  }
-
-  if (layout_object_->Style()->Visibility() != EVisibility::kVisible) {
-    // aria-hidden is meant to override visibility as the determinant in AX
-    // hierarchy inclusion.
-    if (AOMPropertyOrARIAAttributeIsFalse(AOMBooleanProperty::kHidden))
-      return kDefaultBehavior;
-
-    if (ignored_reasons)
-      ignored_reasons->push_back(IgnoredReason(kAXNotVisible));
-    return kIgnoreObject;
-  }
-
-  return AXObject::DefaultObjectInclusion(ignored_reasons);
-}
-
 // Is this the anonymous placeholder for a text control?
 bool AXLayoutObject::IsPlaceholder() const {
   AXObject* parent_object = ParentObject();
@@ -419,34 +397,13 @@
   DCHECK(initialized_);
 #endif
 
-  // All nodes must have an unignored parent within their tree under the root
-  // node of the main web area, so force that node to always be unignored.
-  // The web area for a <select>'s' popup document is ignored, because the
-  // popup object hierarchy is constructed without the document root.
-  if (IsA<Document>(GetNode())) {
-    return CachedParentObject() && CachedParentObject()->IsMenuList();
-  }
-
-  const Node* node = GetNode();
-  if (IsA<HTMLHtmlElement>(node))
-    return true;
-
-  if (!layout_object_) {
-    if (ignored_reasons)
-      ignored_reasons->push_back(IgnoredReason(kAXNotRendered));
-    return true;
-  }
-
   // Ignore continuations, they're duplicate copies of inline nodes with blocks
   // inside. AXObjects are no longer created for these.
   DCHECK(!layout_object_->IsElementContinuation());
 
-  // Check first if any of the common reasons cause this element to be ignored.
-  AXObjectInclusion default_inclusion = DefaultObjectInclusion(ignored_reasons);
-  if (default_inclusion == kIncludeObject)
-    return false;
-  if (default_inclusion == kIgnoreObject)
+  if (AXObject::ShouldIgnoreForHiddenOrInert(ignored_reasons)) {
     return true;
+  }
 
   AXObjectInclusion semantic_inclusion =
       ShouldIncludeBasedOnSemantics(ignored_reasons);
@@ -458,6 +415,7 @@
   // Inner editor element of editable area with empty text provides bounds
   // used to compute the character extent for index 0. This is the same as
   // what the caret's bounds would be if the editable area is focused.
+  Node* node = GetNode();
   if (node) {
     const TextControlElement* text_control = EnclosingTextControl(node);
     if (text_control) {
@@ -488,16 +446,12 @@
     }
   }
 
-  // The SVG-AAM says the foreignObject element is normally presentational.
-  if (layout_object_->IsSVGForeignObjectIncludingNG()) {
-    if (ignored_reasons)
-      ignored_reasons->push_back(IgnoredReason(kAXPresentational));
-    return true;
-  }
-
-  // Make sure renderers with layers stay in the tree.
-  if (GetLayoutObject() && GetLayoutObject()->HasLayer() && node &&
-      node->hasChildren()) {
+  // Layers are used on objects that have styles where Blink is likely to
+  // attempt to optimize them in for the GPU, such as animations, z-indexing and
+  // hidden overflow. Ensure layered objects are unignored, except for <html>.
+  // TODO(accessibility) There is no clear reason to specifically include these,
+  // consider removal of this special case.
+  if (layout_object_->HasLayer() && node && node->hasChildren()) {
     return false;
   }
 
@@ -505,6 +459,7 @@
     if (CanvasHasFallbackContent())
       return false;
 
+    // A 1x1 canvas is too small for the user to see and thus ignored.
     const auto* canvas = DynamicTo<LayoutHTMLCanvas>(GetLayoutObject());
     if (canvas &&
         (canvas->Size().Height() <= 1 || canvas->Size().Width() <= 1)) {
@@ -598,14 +553,6 @@
     }
   }
 
-  // If setting enabled, do not ignore SVG grouping (<g>) elements.
-  if (IsA<SVGGElement>(node)) {
-    Settings* settings = GetDocument()->GetSettings();
-    if (settings->GetAccessibilityIncludeSvgGElement()) {
-      return false;
-    }
-  }
-
   // By default, objects should be ignored so that the AX hierarchy is not
   // filled with unnecessary items.
   if (ignored_reasons)
@@ -748,18 +695,35 @@
                : result->DeepestLastChildIncludingIgnored();
 }
 
-// Note: |NextOnLineInternalNG()| returns null when fragment for |layout_object|
-// is culled as legacy layout version since |LayoutInline::LastLineBox()|
-// returns null when it is culled.
-// See also |PreviousOnLineInternalNG()| which is identical except for using
-// "next" and |back()| instead of "previous" and |front()|.
-static AXObject* NextOnLineInternalNG(const AXObject& ax_object) {
-  DCHECK(!ax_object.IsDetached());
-  const LayoutObject* layout_object = ax_object.GetLayoutObject();
+AXObject* AXLayoutObject::NextOnLine() const {
+  // If this is the last object on the line, nullptr is returned. Otherwise, all
+  // AXLayoutObjects, regardless of role and tree depth, are connected to the
+  // next inline text box on the same line. If there is no inline text box, they
+  // are connected to the next leaf AXObject.
+  DCHECK(!IsDetached());
+
+  const LayoutObject* layout_object = GetLayoutObject();
   DCHECK(layout_object);
-  DCHECK(ShouldUseLayoutNG(*layout_object)) << layout_object;
-  if (layout_object->IsBoxListMarkerIncludingNG() ||
-      !layout_object->IsInLayoutNGInlineFormattingContext()) {
+
+  if (DisplayLockUtilities::LockedAncestorPreventingPaint(*layout_object)) {
+    return nullptr;
+  }
+
+  if (layout_object->IsBoxListMarkerIncludingNG()) {
+    // A list marker should be followed by a list item on the same line.
+    // Note that pseudo content is always included in the tree, so
+    // NextSiblingIncludingIgnored() will succeed.
+    if (AccessibilityIsIncludedInTree()) {
+      return GetDeepestAXChildInLayoutTree(NextSiblingIncludingIgnored(), true);
+    }
+    return nullptr;
+  }
+
+  if (!ShouldUseLayoutNG(*layout_object)) {
+    return nullptr;
+  }
+
+  if (!layout_object->IsInLayoutNGInlineFormattingContext()) {
     return nullptr;
   }
 
@@ -785,8 +749,7 @@
     if (cursor) {
       LayoutObject* runner_layout_object = cursor.CurrentMutableLayoutObject();
       DCHECK(runner_layout_object);
-      AXObject* result =
-          ax_object.AXObjectCache().GetOrCreate(runner_layout_object);
+      AXObject* result = AXObjectCache().GetOrCreate(runner_layout_object);
       result = GetDeepestAXChildInLayoutTree(result, true);
       if (result)
         return result;
@@ -798,19 +761,14 @@
   // line as its first line.
   if (layout_object->NextSibling())
     return nullptr;  // Not at end of parent layout object.
-  if (!ax_object.ParentObject()) {
-    NOTREACHED();
-    return nullptr;
-  }
-
   // Fallback: Use AX parent's next on line.
-  AXObject* ax_parent = ax_object.ParentObject();
+  AXObject* ax_parent = ParentObject();
+  DCHECK(ax_parent);
   AXObject* ax_result = ax_parent->NextOnLine();
   if (!ax_result)
     return nullptr;
 
-  if (!ax_object.AXObjectCache().IsAriaOwned(&ax_object) &&
-      ax_result->ParentObject() == &ax_object) {
+  if (!AXObjectCache().IsAriaOwned(this) && ax_result->ParentObject() == this) {
     // NextOnLine() must not point to a child of the current object.
     // Because inline objects try to return a result from their
     // parents, using a descendant can cause a previous position to be
@@ -822,96 +780,31 @@
   return ax_result;
 }
 
-AXObject* AXLayoutObject::NextOnLine() const {
-  // If this is the last object on the line, nullptr is returned. Otherwise, all
-  // AXLayoutObjects, regardless of role and tree depth, are connected to the
-  // next inline text box on the same line. If there is no inline text box, they
-  // are connected to the next leaf AXObject.
-  if (IsDetached()) {
-    NOTREACHED();
-    return nullptr;
-  }
+AXObject* AXLayoutObject::PreviousOnLine() const {
+  // If this is the first object on the line, nullptr is returned. Otherwise,
+  // all AXLayoutObjects, regardless of role and tree depth, are connected to
+  // the previous inline text box on the same line. If there is no inline text
+  // box, they are connected to the previous leaf AXObject.
+  DCHECK(!IsDetached());
 
-  DCHECK(GetLayoutObject());
-
-  if (DisplayLockUtilities::LockedAncestorPreventingPaint(*GetLayoutObject())) {
-    return nullptr;
-  }
-
-  if (GetLayoutObject()->IsBoxListMarkerIncludingNG()) {
-    // A list marker should be followed by a list item on the same line.
-    // Note that pseudo content is always included in the tree, so
-    // NextSiblingIncludingIgnored() will succeed.
-    return GetDeepestAXChildInLayoutTree(NextSiblingIncludingIgnored(), true);
-  }
-
-  if (ShouldUseLayoutNG(*GetLayoutObject())) {
-    return NextOnLineInternalNG(*this);
-  }
-
-  InlineBox* inline_box = nullptr;
-  if (GetLayoutObject()->IsBox()) {
-    inline_box = To<LayoutBox>(GetLayoutObject())->InlineBoxWrapper();
-  } else if (GetLayoutObject()->IsLayoutInline()) {
-    // For performance and memory consumption, LayoutInline may ignore some
-    // inline-boxes during line layout because they don't actually impact
-    // layout. This is known as "culled inline". We have to recursively look
-    // to the LayoutInline's children via "LastLineBoxIncludingCulling".
-    inline_box =
-        To<LayoutInline>(GetLayoutObject())->LastLineBoxIncludingCulling();
-  } else if (GetLayoutObject()->IsText()) {
-    inline_box = To<LayoutText>(GetLayoutObject())->LastTextBox();
-  }
-
-  if (!inline_box)
-    return nullptr;
-
-  for (InlineBox* next = inline_box->NextOnLine(); next;
-       next = next->NextOnLine()) {
-    LayoutObject* layout_object =
-        LineLayoutAPIShim::LayoutObjectFrom(next->GetLineLayoutItem());
-    AXObject* result = AXObjectCache().GetOrCreate(layout_object);
-    result = GetDeepestAXChildInLayoutTree(result, true);
-    if (result)
-      return result;
-  }
-
-  // Our parent object could have been created based on an ignored inline or
-  // inline block spanning multiple lines. We need to ensure that we are
-  // really at the end of our parent before attempting to connect to the
-  // next AXObject that is on the same line as its last line.
-  //
-  // For example, look at the following layout tree:
-  // LayoutBlockFlow
-  // ++LayoutInline
-  // ++++LayoutText "Beginning of line one "
-  // ++++AnonymousLayoutInline
-  // ++++++LayoutText "end of line one"
-  // ++++++LayoutBR
-  // ++++++LayoutText "Beginning of line two "
-  // ++++LayoutText "End of line two"
-  //
-  // If we are on kStaticText "End of line one", and retrieve the parent
-  // AXObject, it will be the anonymous layout inline which actually ends
-  // somewhere in the second line, not the first line. Its "NextOnLine"
-  // AXObject will be kStaticText "End of line two", which is obviously
-  // wrong.
-  if (!GetLayoutObject()->NextSibling())
-    return ParentObject()->NextOnLine();
-
-  return nullptr;
-}
-
-// Note: |PreviousOnLineInlineNG()| returns null when fragment for
-// |layout_object| is culled as legacy layout version since
-// |LayoutInline::FirstLineBox()| returns null when it is culled. See also
-// |NextOnLineNG()| which is identical except for using "previous" and |front()|
-// instead of "next" and |back()|.
-static AXObject* PreviousOnLineInlineNG(const AXObject& ax_object) {
-  DCHECK(!ax_object.IsDetached());
-  const LayoutObject* layout_object = ax_object.GetLayoutObject();
+  const LayoutObject* layout_object = GetLayoutObject();
   DCHECK(layout_object);
-  DCHECK(ShouldUseLayoutNG(*layout_object)) << layout_object;
+  if (!ShouldUseLayoutNG(*layout_object)) {
+    return nullptr;
+  }
+
+  if (DisplayLockUtilities::LockedAncestorPreventingPaint(*layout_object)) {
+    return nullptr;
+  }
+
+  AXObject* previous_sibling = AccessibilityIsIncludedInTree()
+                                   ? PreviousSiblingIncludingIgnored()
+                                   : nullptr;
+  if (previous_sibling && previous_sibling->GetLayoutObject() &&
+      previous_sibling->GetLayoutObject()->IsLayoutNGOutsideListMarker()) {
+    // A list item should be preceded by a list marker on the same line.
+    return GetDeepestAXChildInLayoutTree(previous_sibling, false);
+  }
 
   if (layout_object->IsBoxListMarkerIncludingNG() ||
       !layout_object->IsInLayoutNGInlineFormattingContext()) {
@@ -940,8 +833,7 @@
     if (cursor) {
       LayoutObject* runner_layout_object = cursor.CurrentMutableLayoutObject();
       DCHECK(runner_layout_object);
-      AXObject* result =
-          ax_object.AXObjectCache().GetOrCreate(runner_layout_object);
+      AXObject* result = AXObjectCache().GetOrCreate(runner_layout_object);
       result = GetDeepestAXChildInLayoutTree(result, false);
       if (result)
         return result;
@@ -954,19 +846,14 @@
   if (layout_object->PreviousSibling())
     return nullptr;  // Not at start of parent layout object.
 
-  if (!ax_object.ParentObject()) {
-    NOTREACHED();
-    return nullptr;
-  }
-
   // Fallback: Use AX parent's previous on line.
-  AXObject* ax_parent = ax_object.ParentObject();
+  AXObject* ax_parent = ParentObject();
+  DCHECK(ax_parent);
   AXObject* ax_result = ax_parent->PreviousOnLine();
   if (!ax_result)
     return nullptr;
 
-  if (!ax_object.AXObjectCache().IsAriaOwned(&ax_object) &&
-      ax_result->ParentObject() == &ax_object) {
+  if (!AXObjectCache().IsAriaOwned(this) && ax_result->ParentObject() == this) {
     // PreviousOnLine() must not point to a child of the current object.
     // Because inline objects without try to return a result from their
     // parents, using a descendant can cause a previous position to be
@@ -978,85 +865,6 @@
   return ax_result;
 }
 
-AXObject* AXLayoutObject::PreviousOnLine() const {
-  // If this is the first object on the line, nullptr is returned. Otherwise,
-  // all AXLayoutObjects, regardless of role and tree depth, are connected to
-  // the previous inline text box on the same line. If there is no inline text
-  // box, they are connected to the previous leaf AXObject.
-  if (IsDetached()) {
-    NOTREACHED();
-    return nullptr;
-  }
-
-  DCHECK(GetLayoutObject());
-
-  if (DisplayLockUtilities::LockedAncestorPreventingPaint(*GetLayoutObject())) {
-    return nullptr;
-  }
-
-  AXObject* previous_sibling = AccessibilityIsIncludedInTree()
-                                   ? PreviousSiblingIncludingIgnored()
-                                   : nullptr;
-  if (previous_sibling && previous_sibling->GetLayoutObject() &&
-      previous_sibling->GetLayoutObject()->IsLayoutNGOutsideListMarker()) {
-    // A list item should be preceded by a list marker on the same line.
-    return GetDeepestAXChildInLayoutTree(previous_sibling, false);
-  }
-
-  if (ShouldUseLayoutNG(*GetLayoutObject()))
-    return PreviousOnLineInlineNG(*this);
-
-  InlineBox* inline_box = nullptr;
-  if (GetLayoutObject()->IsBox()) {
-    inline_box = To<LayoutBox>(GetLayoutObject())->InlineBoxWrapper();
-  } else if (GetLayoutObject()->IsLayoutInline()) {
-    // For performance and memory consumption, LayoutInline may ignore some
-    // inline-boxes during line layout because they don't actually impact
-    // layout. This is known as "culled inline". We have to recursively look
-    // to the LayoutInline's children via "FirstLineBoxIncludingCulling".
-    inline_box =
-        To<LayoutInline>(GetLayoutObject())->FirstLineBoxIncludingCulling();
-  } else if (GetLayoutObject()->IsText()) {
-    inline_box = To<LayoutText>(GetLayoutObject())->FirstTextBox();
-  }
-
-  if (!inline_box)
-    return nullptr;
-
-  for (InlineBox* prev = inline_box->PrevOnLine(); prev;
-       prev = prev->PrevOnLine()) {
-    LayoutObject* layout_object =
-        LineLayoutAPIShim::LayoutObjectFrom(prev->GetLineLayoutItem());
-    AXObject* result = AXObjectCache().GetOrCreate(layout_object);
-    result = GetDeepestAXChildInLayoutTree(result, false);
-    if (result)
-      return result;
-  }
-
-  // Our parent object could have been created based on an ignored inline or
-  // inline block spanning multiple lines. We need to ensure that we are
-  // at the start of our parent layout object before attempting to connect
-  // to the previous AXObject that is on the same line as its first line.
-  //
-  // For example, fook at the following layout tree:
-  // LayoutBlockFlow
-  // ++LayoutInline
-  // ++++LayoutText "Beginning of line one "
-  // ++++AnonymousLayoutInline
-  // ++++++LayoutText "end of line one"
-  // ++++++LayoutBR
-  // ++++++LayoutText "Line two"
-  //
-  // If we are on kStaticText "Line two", and retrieve the parent AXObject,
-  // it will be the anonymous layout inline which actually started somewhere
-  // in the first line, not the second line. Its "PreviousOnLine" AXObject
-  // will be kStaticText "Start of line one", which is obviously wrong.
-  if (!GetLayoutObject()->PreviousSibling())
-    return ParentObject()->PreviousOnLine();
-
-  return nullptr;
-}
-
 //
 // Properties of interactive elements.
 //
diff --git a/third_party/blink/renderer/modules/accessibility/ax_layout_object.h b/third_party/blink/renderer/modules/accessibility/ax_layout_object.h
index c3954e4..4d356ff 100644
--- a/third_party/blink/renderer/modules/accessibility/ax_layout_object.h
+++ b/third_party/blink/renderer/modules/accessibility/ax_layout_object.h
@@ -83,8 +83,6 @@
   bool IsNotUserSelectable() const override;
 
   // Whether objects are ignored, i.e. not included in the tree.
-  AXObjectInclusion DefaultObjectInclusion(
-      IgnoredReasons* = nullptr) const override;
   bool ComputeAccessibilityIsIgnored(IgnoredReasons* = nullptr) const override;
 
   // Properties of static elements.
diff --git a/third_party/blink/renderer/modules/accessibility/ax_list_box_option.cc b/third_party/blink/renderer/modules/accessibility/ax_list_box_option.cc
index 9b40a67..95ccf343 100644
--- a/third_party/blink/renderer/modules/accessibility/ax_list_box_option.cc
+++ b/third_party/blink/renderer/modules/accessibility/ax_list_box_option.cc
@@ -63,17 +63,6 @@
   return list_box_parent_node->ActiveSelectionEnd() == GetNode();
 }
 
-bool AXListBoxOption::ComputeAccessibilityIsIgnored(
-    IgnoredReasons* ignored_reasons) const {
-  if (!GetNode())
-    return true;
-
-  if (AccessibilityIsIgnoredByDefault(ignored_reasons))
-    return true;
-
-  return false;
-}
-
 String AXListBoxOption::TextAlternative(
     bool recursive,
     const AXObject* aria_label_or_description_root,
diff --git a/third_party/blink/renderer/modules/accessibility/ax_list_box_option.h b/third_party/blink/renderer/modules/accessibility/ax_list_box_option.h
index 202b37d6..9de61dd8 100644
--- a/third_party/blink/renderer/modules/accessibility/ax_list_box_option.h
+++ b/third_party/blink/renderer/modules/accessibility/ax_list_box_option.h
@@ -61,7 +61,6 @@
 
  private:
   bool CanHaveChildren() const override { return false; }
-  bool ComputeAccessibilityIsIgnored(IgnoredReasons* = nullptr) const override;
 
   HTMLSelectElement* ListBoxOptionParentNode() const;
 };
diff --git a/third_party/blink/renderer/modules/accessibility/ax_media_element.cc b/third_party/blink/renderer/modules/accessibility/ax_media_element.cc
index c0e02fbd..b1f8800 100644
--- a/third_party/blink/renderer/modules/accessibility/ax_media_element.cc
+++ b/third_party/blink/renderer/modules/accessibility/ax_media_element.cc
@@ -48,11 +48,6 @@
   return true;
 }
 
-bool AccessibilityMediaElement::ComputeAccessibilityIsIgnored(
-    IgnoredReasons* ignored_reasons) const {
-  return false;
-}
-
 AXRestriction AccessibilityMediaElement::Restriction() const {
   if (IsUnplayable())
     return kRestrictionDisabled;
diff --git a/third_party/blink/renderer/modules/accessibility/ax_media_element.h b/third_party/blink/renderer/modules/accessibility/ax_media_element.h
index a243426..2f80450 100644
--- a/third_party/blink/renderer/modules/accessibility/ax_media_element.h
+++ b/third_party/blink/renderer/modules/accessibility/ax_media_element.h
@@ -34,7 +34,6 @@
 
   // AXNodeObject overrides.
   bool CanHaveChildren() const override;
-  bool ComputeAccessibilityIsIgnored(IgnoredReasons* = nullptr) const override;
   AXRestriction Restriction() const override;
 
  protected:
diff --git a/third_party/blink/renderer/modules/accessibility/ax_menu_list_option.cc b/third_party/blink/renderer/modules/accessibility/ax_menu_list_option.cc
index b548ad3..4f2e025 100644
--- a/third_party/blink/renderer/modules/accessibility/ax_menu_list_option.cc
+++ b/third_party/blink/renderer/modules/accessibility/ax_menu_list_option.cc
@@ -137,6 +137,9 @@
   return true;
 }
 
+// TODO(aleventhal) This override could go away, but it will cause a lot of
+// test changes, as invisible options inside of a collapsed <select> will become
+// ignored since they have no layout object.
 bool AXMenuListOption::ComputeAccessibilityIsIgnored(
     IgnoredReasons* ignored_reasons) const {
   if (IsDetached()) {
@@ -148,7 +151,14 @@
           html_names::kHiddenAttr))
     return true;
 
-  return AccessibilityIsIgnoredByDefault(ignored_reasons);
+  if (IsAriaHidden()) {
+    if (ignored_reasons) {
+      ComputeIsAriaHidden(ignored_reasons);
+    }
+    return true;
+  }
+
+  return ParentObject()->ComputeAccessibilityIsIgnored(ignored_reasons);
 }
 
 void AXMenuListOption::GetRelativeBounds(
diff --git a/third_party/blink/renderer/modules/accessibility/ax_mock_object.cc b/third_party/blink/renderer/modules/accessibility/ax_mock_object.cc
index 1b70b6d2..4fa4c2f 100644
--- a/third_party/blink/renderer/modules/accessibility/ax_mock_object.cc
+++ b/third_party/blink/renderer/modules/accessibility/ax_mock_object.cc
@@ -34,11 +34,6 @@
 
 AXMockObject::~AXMockObject() = default;
 
-bool AXMockObject::ComputeAccessibilityIsIgnored(
-    IgnoredReasons* ignored_reasons) const {
-  return AccessibilityIsIgnoredByDefault(ignored_reasons);
-}
-
 Document* AXMockObject::GetDocument() const {
   return ParentObject() ? ParentObject()->GetDocument() : nullptr;
 }
diff --git a/third_party/blink/renderer/modules/accessibility/ax_mock_object.h b/third_party/blink/renderer/modules/accessibility/ax_mock_object.h
index c5c2afe4..6dd49c4e 100644
--- a/third_party/blink/renderer/modules/accessibility/ax_mock_object.h
+++ b/third_party/blink/renderer/modules/accessibility/ax_mock_object.h
@@ -53,9 +53,6 @@
   bool IsMockObject() const final { return true; }
   Document* GetDocument() const override;
   ax::mojom::blink::Role NativeRoleIgnoringAria() const override;
-
- private:
-  bool ComputeAccessibilityIsIgnored(IgnoredReasons* = nullptr) const override;
 };
 
 template <>
diff --git a/third_party/blink/renderer/modules/accessibility/ax_node_object.cc b/third_party/blink/renderer/modules/accessibility/ax_node_object.cc
index e741aa7..bb34258 100644
--- a/third_party/blink/renderer/modules/accessibility/ax_node_object.cc
+++ b/third_party/blink/renderer/modules/accessibility/ax_node_object.cc
@@ -46,6 +46,7 @@
 #include "third_party/blink/renderer/core/css/css_resolution_units.h"
 #include "third_party/blink/renderer/core/css/properties/longhands.h"
 #include "third_party/blink/renderer/core/display_lock/display_lock_utilities.h"
+#include "third_party/blink/renderer/core/dom/css_toggle_inference.h"
 #include "third_party/blink/renderer/core/dom/flat_tree_traversal.h"
 #include "third_party/blink/renderer/core/dom/layout_tree_builder_traversal.h"
 #include "third_party/blink/renderer/core/dom/node_computed_style.h"
@@ -453,6 +454,12 @@
     IgnoredReasons* ignored_reasons) const {
   DCHECK(GetDocument());
 
+  // All nodes must have an unignored parent within their tree under
+  // the root node of the web area, so force that node to always be unignored.
+  if (IsA<Document>(GetNode())) {
+    return kIncludeObject;
+  }
+
   if (IsPresentational()) {
     if (ignored_reasons)
       ignored_reasons->push_back(IgnoredReason(kAXPresentational));
@@ -472,6 +479,7 @@
     // alt text. This can allow auto alt to be applied to them.
     if (IsImage())
       return kIncludeObject;
+
     return kDefaultBehavior;
   }
 
@@ -487,8 +495,9 @@
   }
 
   Element* element = GetElement();
-  if (!element)
+  if (!element) {
     return kDefaultBehavior;
+  }
 
   if (IsA<SVGElement>(node)) {
     // The symbol element is used to define graphical templates which can be
@@ -515,6 +524,14 @@
       return kIncludeObject;
     }
 
+    // If setting enabled, do not ignore SVG grouping (<g>) elements.
+    if (IsA<SVGGElement>(node)) {
+      Settings* settings = GetDocument()->GetSettings();
+      if (settings->GetAccessibilityIncludeSvgGElement()) {
+        return kIncludeObject;
+      }
+    }
+
     // If we return kDefaultBehavior here, the logic related to inclusion of
     // clickable objects, links, controls, etc. will not be reached. We handle
     // SVG elements early to ensure properties in a <symbol> subtree do not
@@ -524,7 +541,14 @@
   if (IsTableLikeRole() || IsTableRowLikeRole() || IsTableCellLikeRole())
     return kIncludeObject;
 
-  // All focusable elements except the <body> are included.
+  if (IsA<HTMLHtmlElement>(node)) {
+    if (ignored_reasons) {
+      ignored_reasons->push_back(IgnoredReason(kAXUninteresting));
+    }
+    return kIgnoreObject;
+  }
+
+  // All focusable elements except the <body> and <html> are included.
   if (!IsA<HTMLBodyElement>(node) && CanSetFocusAttribute())
     return kIncludeObject;
 
@@ -579,6 +603,7 @@
           ax::mojom::blink::Role::kAbbr,
           ax::mojom::blink::Role::kApplication,
           ax::mojom::blink::Role::kArticle,
+          ax::mojom::blink::Role::kAudio,
           ax::mojom::blink::Role::kBanner,
           ax::mojom::blink::Role::kBlockquote,
           ax::mojom::blink::Role::kComplementary,
@@ -654,6 +679,7 @@
           ax::mojom::blink::Role::kSubscript,
           ax::mojom::blink::Role::kSuperscript,
           ax::mojom::blink::Role::kTime,
+          ax::mojom::blink::Role::kVideo,
       };
 
   if (always_included_computed_roles.find(RoleValue()) !=
@@ -705,9 +731,44 @@
     return kIncludeObject;
   }
 
+  // The SVG-AAM says the foreignObject element is normally presentational.
+  if (IsA<SVGForeignObjectElement>(node)) {
+    if (ignored_reasons) {
+      ignored_reasons->push_back(IgnoredReason(kAXPresentational));
+    }
+    return kIgnoreObject;
+  }
+
   return kDefaultBehavior;
 }
 
+bool AXNodeObject::ComputeAccessibilityIsIgnored(
+    IgnoredReasons* ignored_reasons) const {
+  if (AXObject::ComputeAccessibilityIsIgnored(ignored_reasons)) {
+    // Fallback elements inside of a <canvas> are invisible, but are not ignored
+    // if they are semantic and not aria-hidden or hidden via style.
+    if (IsAriaHidden() || IsHiddenViaStyle() || !GetNode()->parentElement() ||
+        !GetNode()->parentElement()->IsInCanvasSubtree()) {
+      return true;
+    }
+  }
+
+  // Handle content that is either visible or in a canvas subtree.
+  AXObjectInclusion include = ShouldIncludeBasedOnSemantics(ignored_reasons);
+  if (include == kIgnoreObject) {
+    return true;
+  }
+
+  if (include == kDefaultBehavior && !IsA<Text>(GetNode())) {
+    if (ignored_reasons) {
+      ignored_reasons->push_back(IgnoredReason(kAXUninteresting));
+    }
+    return true;
+  }
+
+  return false;
+}
+
 // static
 absl::optional<String> AXNodeObject::GetCSSAltText(const Node* node) {
   // CSS alt text rules allow text to be assigned to ::before/::after content.
@@ -743,65 +804,6 @@
   return absl::nullopt;
 }
 
-bool AXNodeObject::ComputeAccessibilityIsIgnored(
-    IgnoredReasons* ignored_reasons) const {
-#if DCHECK_IS_ON()
-  // Double-check that an AXObject is never accessed before
-  // it's been initialized.
-  DCHECK(initialized_);
-#endif
-
-  // If we don't have a node, then ignore the node object.
-  // TODO(vmpstr/aleventhal): Investigate how this can happen.
-  if (!GetNode()) {
-    NOTREACHED();
-    return true;
-  }
-
-  // All nodes must have an unignored parent within their tree under
-  // the root node of the web area, so force that node to always be unignored.
-  if (IsA<Document>(GetNode())) {
-    return false;
-  }
-
-  DCHECK_NE(role_, ax::mojom::blink::Role::kUnknown);
-  // Use AXLayoutObject::ComputeAccessibilityIsIgnored().
-  DCHECK(!GetLayoutObject());
-
-  if (DisplayLockUtilities::IsDisplayLockedPreventingPaint(GetNode())) {
-    if (IsAriaHidden() ||
-        DisplayLockUtilities::ShouldIgnoreNodeDueToDisplayLock(
-            *GetNode(), DisplayLockActivationReason::kAccessibility)) {
-      if (ignored_reasons)
-        ignored_reasons->push_back(IgnoredReason(kAXNotRendered));
-      return true;
-    }
-    return ShouldIncludeBasedOnSemantics(ignored_reasons) == kIgnoreObject;
-  }
-
-  auto* element = DynamicTo<Element>(GetNode());
-  if (!element)
-    element = GetNode()->parentElement();
-
-  if (!element)
-    return true;
-
-  if (element->IsInCanvasSubtree())
-    return ShouldIncludeBasedOnSemantics(ignored_reasons) == kIgnoreObject;
-
-  if (AOMPropertyOrARIAAttributeIsFalse(AOMBooleanProperty::kHidden))
-    return false;
-
-  if (element->HasDisplayContentsStyle()) {
-    if (ShouldIncludeBasedOnSemantics(ignored_reasons) == kIncludeObject)
-      return false;
-  }
-
-  if (ignored_reasons)
-    ignored_reasons->push_back(IgnoredReason(kAXNotRendered));
-  return true;
-}
-
 // The following lists are for deciding whether the tags aside,
 // header and footer can be interpreted as roles complementary, banner and
 // contentInfo or if they should be interpreted as generic. This list
@@ -1342,6 +1344,75 @@
   return RoleFromLayoutObjectOrNode();
 }
 
+namespace {
+
+ax::mojom::blink::Role InferredCSSToggleRole(Node* node) {
+  Element* element = DynamicTo<Element>(node);
+  if (!element) {
+    return ax::mojom::blink::Role::kUnknown;
+  }
+
+  // toggle_inference is null when CSS toggles are not used in the document.
+  CSSToggleInference* toggle_inference =
+      element->GetDocument().GetCSSToggleInference();
+  if (!toggle_inference) {
+    return ax::mojom::blink::Role::kUnknown;
+  }
+
+  DCHECK(RuntimeEnabledFeatures::CSSTogglesEnabled());
+
+  switch (toggle_inference->RoleForElement(element)) {
+    case CSSToggleRole::kNone:
+      break;
+    case CSSToggleRole::kButtonWithPopup:
+      return ax::mojom::blink::Role::kPopUpButton;
+    case CSSToggleRole::kDisclosure:
+      break;
+    case CSSToggleRole::kDisclosureButton:
+      return ax::mojom::blink::Role::kButton;
+    case CSSToggleRole::kTree:
+      return ax::mojom::blink::Role::kTree;
+    case CSSToggleRole::kTreeGroup:
+      return ax::mojom::blink::Role::kGroup;
+    case CSSToggleRole::kTreeItem:
+      return ax::mojom::blink::Role::kTreeItem;
+    case CSSToggleRole::kAccordion:
+      break;
+    case CSSToggleRole::kAccordionItem:
+      return ax::mojom::blink::Role::kRegion;
+    case CSSToggleRole::kAccordionItemButton:
+      return ax::mojom::blink::Role::kButton;
+    case CSSToggleRole::kTabContainer:
+      // TODO(dbaron): We should verify that using kTabList really
+      // works here, since this is a container that has both the tab
+      // list *and* the tab panels.  We should also make sure that
+      // posinset/setsize work correctly for the tabs.
+      return ax::mojom::blink::Role::kTabList;
+    case CSSToggleRole::kTab:
+      return ax::mojom::blink::Role::kTab;
+    case CSSToggleRole::kTabPanel:
+      return ax::mojom::blink::Role::kTabPanel;
+    case CSSToggleRole::kRadioGroup:
+      return ax::mojom::blink::Role::kRadioGroup;
+    case CSSToggleRole::kRadioItem:
+      return ax::mojom::blink::Role::kRadioButton;
+    case CSSToggleRole::kCheckboxGroup:
+      break;
+    case CSSToggleRole::kCheckbox:
+      return ax::mojom::blink::Role::kCheckBox;
+    case CSSToggleRole::kListbox:
+      return ax::mojom::blink::Role::kListBox;
+    case CSSToggleRole::kListboxItem:
+      return ax::mojom::blink::Role::kListBoxOption;
+    case CSSToggleRole::kButton:
+      return ax::mojom::blink::Role::kButton;
+  }
+
+  return ax::mojom::blink::Role::kUnknown;
+}
+
+}  // namespace
+
 ax::mojom::blink::Role AXNodeObject::DetermineAccessibilityRole() {
 #if DCHECK_IS_ON()
   base::AutoReset<bool> reentrancy_protector(&is_computing_role_, true);
@@ -1356,8 +1427,25 @@
 
   aria_role_ = DetermineAriaRoleAttribute();
 
-  return aria_role_ == ax::mojom::blink::Role::kUnknown ? native_role_
-                                                        : aria_role_;
+  // Order of precedence is currently:
+  //   1. ARIA role
+  //   2. Inferred role from CSS Toggle inference engine
+  //   3. Native markup role
+  // but we may decide to change how the CSS Toggle inference fits in.
+  //
+  // TODO(dbaron): Perhaps revisit whether there are types of elements
+  // where toggles should not work.
+
+  if (aria_role_ != ax::mojom::blink::Role::kUnknown) {
+    return aria_role_;
+  }
+
+  ax::mojom::blink::Role css_toggle_role = InferredCSSToggleRole(GetNode());
+  if (css_toggle_role != ax::mojom::blink::Role::kUnknown) {
+    return css_toggle_role;
+  }
+
+  return native_role_;
 }
 
 void AXNodeObject::AccessibilityChildrenFromAOMProperty(
@@ -1416,7 +1504,8 @@
 #endif
   AXObject::Init(parent);
 
-  DCHECK(role_ == native_role_ || role_ == aria_role_)
+  DCHECK(role_ == native_role_ || role_ == aria_role_ ||
+         GetNode()->GetDocument().GetCSSToggleInference())
       << "Role must be either the cached native role or cached aria role: "
       << "\n* Final role: " << role_ << "\n* Native role: " << native_role_
       << "\n* Aria role: " << aria_role_ << "\n* Node: " << GetNode();
diff --git a/third_party/blink/renderer/modules/accessibility/ax_object.cc b/third_party/blink/renderer/modules/accessibility/ax_object.cc
index da11b71..4e5236b9 100644
--- a/third_party/blink/renderer/modules/accessibility/ax_object.cc
+++ b/third_party/blink/renderer/modules/accessibility/ax_object.cc
@@ -2837,6 +2837,10 @@
         << "\nThe Detach() method sets cached_is_ignored_ to true, but "
            "something has recomputed it.";
   }
+  if (!cached_is_ignored_ && IsA<Document>(GetNode()) && CachedParentObject() &&
+      CachedParentObject()->IsMenuList()) {
+    NOTREACHED() << "The menulist popup's document must be ignored.";
+  }
 #endif
   return cached_is_ignored_;
 }
@@ -2888,7 +2892,7 @@
 
   const ComputedStyle* style = GetComputedStyle();
 
-  cached_is_hidden_via_style = ComputeIsHiddenViaStyle(style);
+  cached_is_hidden_via_style_ = ComputeIsHiddenViaStyle(style);
 
   // Decisions in what subtree descendants are included (each descendant's
   // cached children_) depends on the ARIA hidden state. When it changes,
@@ -2973,30 +2977,64 @@
   }
 }
 
-bool AXObject::AccessibilityIsIgnoredByDefault(
+bool AXObject::ComputeAccessibilityIsIgnored(
     IgnoredReasons* ignored_reasons) const {
-  return DefaultObjectInclusion(ignored_reasons) == kIgnoreObject;
+  return ShouldIgnoreForHiddenOrInert(ignored_reasons);
 }
 
-AXObjectInclusion AXObject::DefaultObjectInclusion(
+bool AXObject::ShouldIgnoreForHiddenOrInert(
     IgnoredReasons* ignored_reasons) const {
-  if (IsAriaHidden()) {
+  DCHECK(AXObjectCache().ModificationCount() == last_modification_count_)
+      << "Hidden values must be computed before ignored.";
+
+  // All nodes must have an unignored parent within their tree under
+  // the root node of the web area, so force that node to always be unignored.
+  if (IsA<Document>(GetNode())) {
+    return false;
+  }
+
+  if (cached_is_aria_hidden_) {
     // Keep keyboard focusable elements that are aria-hidden in tree, so that
     // they can still fire events such as focus and value changes.
     if (!IsKeyboardFocusable()) {
       if (ignored_reasons)
         ComputeIsAriaHidden(ignored_reasons);
-      return kIgnoreObject;
+      return true;
     }
   }
 
-  if (IsInert()) {
-    if (ignored_reasons)
+  if (cached_is_inert_) {
+    if (ignored_reasons) {
       ComputeIsInert(ignored_reasons);
-    return kIgnoreObject;
+    }
+    return true;
   }
 
-  return kDefaultBehavior;
+  // aria-hidden=false is meant to override visibility as the determinant in
+  // AX hierarchy inclusion, but only for the element it is specified, and not
+  // the entire subtree. See https://w3c.github.io/aria/#aria-hidden.
+  if (AOMPropertyOrARIAAttributeIsFalse(AOMBooleanProperty::kHidden)) {
+    return false;
+  }
+
+  if (cached_is_hidden_via_style_) {
+    if (ignored_reasons) {
+      ignored_reasons->push_back(
+          IgnoredReason(GetLayoutObject() ? kAXNotVisible : kAXNotRendered));
+    }
+    return true;
+  }
+
+  // Hide nodes that are whitespace or are occluded by CSS alt text.
+  if (!GetLayoutObject() && GetNode() && !IsA<HTMLAreaElement>(GetNode()) &&
+      !DisplayLockUtilities::IsDisplayLockedPreventingPaint(GetNode())) {
+    if (ignored_reasons) {
+      ignored_reasons->push_back(IgnoredReason(kAXNotRendered));
+    }
+    return true;
+  }
+
+  return false;
 }
 
 // Note: do not rely on the value of this inside of display:none.
@@ -4000,7 +4038,7 @@
 
 bool AXObject::IsHiddenViaStyle() const {
   UpdateCachedAttributeValuesIfNeeded();
-  return cached_is_hidden_via_style;
+  return cached_is_hidden_via_style_;
 }
 
 // Return true if this should be removed from accessible name computations.
@@ -6962,8 +7000,9 @@
     } else if (AriaHiddenRoot()) {
       string_builder = string_builder + " ariaHiddenRootExtra";
     }
-    if (cached_values_only ? cached_is_hidden_via_style : IsHiddenViaStyle())
+    if (cached_values_only ? cached_is_hidden_via_style_ : IsHiddenViaStyle()) {
       string_builder = string_builder + " isHiddenViaCSS";
+    }
     if (cached_values_only ? cached_is_inert_ : IsInert())
       string_builder = string_builder + " isInert";
     if (IsMissingParent())
diff --git a/third_party/blink/renderer/modules/accessibility/ax_object.h b/third_party/blink/renderer/modules/accessibility/ax_object.h
index b14b18ba..511f940d 100644
--- a/third_party/blink/renderer/modules/accessibility/ax_object.h
+++ b/third_party/blink/renderer/modules/accessibility/ax_object.h
@@ -486,12 +486,8 @@
   // elements are, etc.
   bool AccessibilityIsIncludedInTree() const;
   typedef HeapVector<IgnoredReason> IgnoredReasons;
-  virtual bool ComputeAccessibilityIsIgnored(IgnoredReasons* = nullptr) const {
-    return true;
-  }
-  bool AccessibilityIsIgnoredByDefault(IgnoredReasons* = nullptr) const;
-  virtual AXObjectInclusion DefaultObjectInclusion(
-      IgnoredReasons* = nullptr) const;
+  virtual bool ComputeAccessibilityIsIgnored(IgnoredReasons* = nullptr) const;
+  bool ShouldIgnoreForHiddenOrInert(IgnoredReasons* = nullptr) const;
   bool IsInert() const;
   bool IsAriaHidden() const;
   bool CachedIsAriaHidden() { return cached_is_aria_hidden_; }
@@ -1468,7 +1464,7 @@
   mutable bool cached_is_ignored_but_included_in_tree_ : 1;
   mutable bool cached_is_inert_ : 1;
   mutable bool cached_is_aria_hidden_ : 1;
-  mutable bool cached_is_hidden_via_style : 1;
+  mutable bool cached_is_hidden_via_style_ : 1;
   mutable bool cached_is_descendant_of_disabled_node_ : 1;
   mutable bool cached_can_set_focus_attribute_ : 1;
 
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 e1c0107..c29af7d5 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
@@ -887,10 +887,12 @@
   if (result->IsMissingParent()) {
     AXObject* computed_parent =
         AXObject::ComputeNonARIAParent(*this, layout_object->GetNode());
-    NOTREACHED() << "Had AXObject but was missing parent: " << layout_object
-                 << " " << result->ToString(true, true) << "\nComputed parent: "
-                 << (computed_parent ? computed_parent->ToString(true, true)
-                                     : "null");
+    // TODO(https://crbug.com/1413932) resolve and restore to NOTREACHED().
+    DCHECK(false) << "Had AXObject but was missing parent: " << layout_object
+                  << " " << result->ToString(true, true)
+                  << "\nComputed parent: "
+                  << (computed_parent ? computed_parent->ToString(true, true)
+                                      : "null");
   }
 #endif
 
diff --git a/third_party/blink/renderer/modules/accessibility/ax_progress_indicator.cc b/third_party/blink/renderer/modules/accessibility/ax_progress_indicator.cc
index f85dd5c..4107770e 100644
--- a/third_party/blink/renderer/modules/accessibility/ax_progress_indicator.cc
+++ b/third_party/blink/renderer/modules/accessibility/ax_progress_indicator.cc
@@ -44,11 +44,6 @@
   return ax::mojom::blink::Role::kProgressIndicator;
 }
 
-bool AXProgressIndicator::ComputeAccessibilityIsIgnored(
-    IgnoredReasons* ignored_reasons) const {
-  return AccessibilityIsIgnoredByDefault(ignored_reasons);
-}
-
 bool AXProgressIndicator::ValueForRange(float* out_value) const {
   float value_now;
   if (HasAOMPropertyOrARIAAttribute(AOMFloatProperty::kValueNow, value_now)) {
diff --git a/third_party/blink/renderer/modules/accessibility/ax_progress_indicator.h b/third_party/blink/renderer/modules/accessibility/ax_progress_indicator.h
index a651512..ef56f0e2d 100644
--- a/third_party/blink/renderer/modules/accessibility/ax_progress_indicator.h
+++ b/third_party/blink/renderer/modules/accessibility/ax_progress_indicator.h
@@ -45,7 +45,6 @@
   bool MinValueForRange(float* out_value) const override;
 
   HTMLProgressElement* GetProgressElement() const;
-  bool ComputeAccessibilityIsIgnored(IgnoredReasons* = nullptr) const override;
 };
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/modules/accessibility/ax_virtual_object.cc b/third_party/blink/renderer/modules/accessibility/ax_virtual_object.cc
index 84c43e6..d80edcd 100644
--- a/third_party/blink/renderer/modules/accessibility/ax_virtual_object.cc
+++ b/third_party/blink/renderer/modules/accessibility/ax_virtual_object.cc
@@ -35,11 +35,6 @@
   return GetAccessibleNode() ? GetAccessibleNode()->GetDocument() : nullptr;
 }
 
-bool AXVirtualObject::ComputeAccessibilityIsIgnored(
-    IgnoredReasons* ignoredReasons) const {
-  return AccessibilityIsIgnoredByDefault(ignoredReasons);
-}
-
 void AXVirtualObject::AddChildren() {
 #if defined(AX_FAIL_FAST_BUILD)
   DCHECK(!IsDetached());
diff --git a/third_party/blink/renderer/modules/accessibility/ax_virtual_object.h b/third_party/blink/renderer/modules/accessibility/ax_virtual_object.h
index e50c159..db2236c3 100644
--- a/third_party/blink/renderer/modules/accessibility/ax_virtual_object.h
+++ b/third_party/blink/renderer/modules/accessibility/ax_virtual_object.h
@@ -41,8 +41,6 @@
   ax::mojom::blink::Role AriaRoleAttribute() const override;
 
  private:
-  bool ComputeAccessibilityIsIgnored(IgnoredReasons* = nullptr) const override;
-
   Member<AccessibleNode> accessible_node_;
 
   ax::mojom::blink::Role aria_role_;
diff --git a/third_party/blink/renderer/modules/accessibility/blink_ax_tree_source.cc b/third_party/blink/renderer/modules/accessibility/blink_ax_tree_source.cc
index 3aad1328..300ad40 100644
--- a/third_party/blink/renderer/modules/accessibility/blink_ax_tree_source.cc
+++ b/third_party/blink/renderer/modules/accessibility/blink_ax_tree_source.cc
@@ -306,9 +306,10 @@
     }
 
     if (!child->AccessibilityIsIncludedInTree()) {
-      NOTREACHED() << "Should not receive unincluded child."
-                   << "\nChild: " << child->ToString(true).Utf8()
-                   << "\nParent: " << parent->ToString(true).Utf8();
+      // TODO(https://crbug.com/1407396) resolve and restore to NOTREACHED().
+      DCHECK(false) << "Should not receive unincluded child."
+                    << "\nChild: " << child->ToString(true).Utf8()
+                    << "\nParent: " << parent->ToString(true).Utf8();
       continue;
     }
 
diff --git a/third_party/blink/renderer/modules/ad_auction/auction_ad_config.idl b/third_party/blink/renderer/modules/ad_auction/auction_ad_config.idl
index e0348e2..3ab8969 100644
--- a/third_party/blink/renderer/modules/ad_auction/auction_ad_config.idl
+++ b/third_party/blink/renderer/modules/ad_auction/auction_ad_config.idl
@@ -25,21 +25,27 @@
 
   unsigned long long sellerTimeout;
   unsigned short sellerExperimentGroupId;
+
   // Really (record<USVString, any> or Promise<record<USVString, any>>)
   any perBuyerSignals;
+
   // Really (record<USVString, unsigned long long> or
   //         Promise<record<USVString, unsigned long long>>)
   any perBuyerTimeouts;
+  any perBuyerCumulativeTimeouts;
+
   record<USVString, unsigned short> perBuyerGroupLimits;
   record<USVString, unsigned short> perBuyerExperimentGroupIds;
   record<USVString, record<USVString, double>> perBuyerPrioritySignals;
+
   // `auctionReportBuyerKeys` and `auctionReportBuyers` are described in:
   // https://github.com/WICG/turtledove/blob/main/FLEDGE_extended_PA_reporting.md
-
+  //
   // TODO(crbug.com/1402467): Use `bigint` instead of `any` when supported by
   // WebIDL parser.
   sequence<any> auctionReportBuyerKeys;
   record<USVString, AuctionReportBuyersConfig> auctionReportBuyers;
+
   sequence<AuctionAdConfig> componentAuctions;
   AbortSignal? signal;
   boolean resolveToConfig;
diff --git a/third_party/blink/renderer/modules/ad_auction/navigator_auction.cc b/third_party/blink/renderer/modules/ad_auction/navigator_auction.cc
index de5ef5c43..bd3cc9f 100644
--- a/third_party/blink/renderer/modules/ad_auction/navigator_auction.cc
+++ b/third_party/blink/renderer/modules/ad_auction/navigator_auction.cc
@@ -61,6 +61,7 @@
 #include "third_party/blink/renderer/platform/bindings/exception_state.h"
 #include "third_party/blink/renderer/platform/loader/fetch/resource_fetcher.h"
 #include "third_party/blink/renderer/platform/weborigin/security_origin.h"
+#include "third_party/blink/renderer/platform/wtf/text/string_operators.h"
 #include "third_party/blink/renderer/platform/wtf/text/wtf_string.h"
 #include "third_party/blink/renderer/platform/wtf/vector.h"
 #include "v8/include/v8-primitive.h"
@@ -109,10 +110,13 @@
     const String seller_name_;
   };
 
+  // This is used for perBuyerTimeouts and perBuyerCumulativeTimeouts, with
+  // `field` indicating which of the two fields an object is being used for.
   class BuyerTimeoutsResolved : public ScriptFunction::Callable {
    public:
     BuyerTimeoutsResolved(AuctionHandle* auction_handle,
                           mojom::blink::AuctionAdConfigAuctionIdPtr auction_id,
+                          mojom::blink::AuctionAdConfigBuyerTimeoutField field,
                           const String& seller_name);
 
     ScriptValue Call(ScriptState* script_state, ScriptValue value) override;
@@ -121,6 +125,7 @@
    private:
     Member<AuctionHandle> auction_handle_;
     const mojom::blink::AuctionAdConfigAuctionIdPtr auction_id_;
+    const mojom::blink::AuctionAdConfigBuyerTimeoutField field_;
     const String seller_name_;
   };
 
@@ -185,9 +190,10 @@
 
   void ResolvedBuyerTimeoutsPromise(
       mojom::blink::AuctionAdConfigAuctionIdPtr auction,
+      mojom::blink::AuctionAdConfigBuyerTimeoutField field,
       mojom::blink::AuctionAdConfigBuyerTimeoutsPtr buyer_timeouts) {
     abortable_ad_auction_->ResolvedBuyerTimeoutsPromise(
-        std::move(auction), std::move(buyer_timeouts));
+        std::move(auction), field, std::move(buyer_timeouts));
   }
 
   void ResolvedDirectFromSellerSignalsPromise(
@@ -1260,11 +1266,17 @@
 }
 
 // Returns nullptr + sets exception on failure, or returns a concrete value.
+//
+// This is shared logic for `perBuyerTimeouts` and `perBuyerCumulativeTimeouts`,
+// with `field` indicating which name to use in error messages. The logic is
+// identical in both cases.
 mojom::blink::AuctionAdConfigBuyerTimeoutsPtr
-ConvertNonPromisePerBuyerTimeoutsFromV8ToMojo(const ScriptState& script_state,
-                                              ExceptionState& exception_state,
-                                              const String& seller_name,
-                                              v8::Local<v8::Value> value) {
+ConvertNonPromisePerBuyerTimeoutsFromV8ToMojo(
+    const ScriptState& script_state,
+    ExceptionState& exception_state,
+    const String& seller_name,
+    v8::Local<v8::Value> value,
+    mojom::blink::AuctionAdConfigBuyerTimeoutField field) {
   Vector<std::pair<String, uint64_t>> decoded =
       NativeValueTraits<IDLRecord<IDLUSVString, IDLUnsignedLongLong>>::
           NativeValue(script_state.GetIsolate(), value, exception_state);
@@ -1284,8 +1296,18 @@
     scoped_refptr<const SecurityOrigin> buyer =
         ParseOrigin(per_buyer_timeout.first);
     if (!buyer) {
+      String field_name;
+      switch (field) {
+        case mojom::blink::AuctionAdConfigBuyerTimeoutField::kPerBuyerTimeouts:
+          field_name = "perBuyerTimeouts buyer";
+          break;
+        case mojom::blink::AuctionAdConfigBuyerTimeoutField::
+            kPerBuyerCumulativeTimeouts:
+          field_name = "perBuyerCumulativeTimeouts buyer";
+          break;
+      }
       exception_state.ThrowTypeError(ErrorInvalidAuctionConfigSeller(
-          seller_name, "perBuyerTimeouts buyer", per_buyer_timeout.first,
+          seller_name, field_name, per_buyer_timeout.first,
           "must be \"*\" (wildcard) or a valid https origin."));
       return nullptr;
     }
@@ -1318,7 +1340,10 @@
             &script_state,
             MakeGarbageCollected<
                 NavigatorAuction::AuctionHandle::BuyerTimeoutsResolved>(
-                auction_handle, auction_id->Clone(), input.seller())),
+                auction_handle, auction_id->Clone(),
+                mojom::blink::AuctionAdConfigBuyerTimeoutField::
+                    kPerBuyerTimeouts,
+                input.seller())),
         MakeGarbageCollected<ScriptFunction>(
             &script_state,
             MakeGarbageCollected<NavigatorAuction::AuctionHandle::Rejected>(
@@ -1330,7 +1355,8 @@
 
   mojom::blink::AuctionAdConfigBuyerTimeoutsPtr buyer_timeouts =
       ConvertNonPromisePerBuyerTimeoutsFromV8ToMojo(
-          script_state, exception_state, input.seller(), value);
+          script_state, exception_state, input.seller(), value,
+          mojom::blink::AuctionAdConfigBuyerTimeoutField::kPerBuyerTimeouts);
   if (buyer_timeouts) {
     output.auction_ad_config_non_shared_params->buyer_timeouts =
         mojom::blink::AuctionAdConfigMaybePromiseBuyerTimeouts::NewValue(
@@ -1340,6 +1366,55 @@
   return false;
 }
 
+bool CopyPerBuyerCumulativeTimeoutsFromIdlToMojo(
+    NavigatorAuction::AuctionHandle* auction_handle,
+    const mojom::blink::AuctionAdConfigAuctionId* auction_id,
+    ScriptState& script_state,
+    ExceptionState& exception_state,
+    const AuctionAdConfig& input,
+    mojom::blink::AuctionAdConfig& output) {
+  if (!input.hasPerBuyerCumulativeTimeouts()) {
+    output.auction_ad_config_non_shared_params->buyer_cumulative_timeouts =
+        mojom::blink::AuctionAdConfigMaybePromiseBuyerTimeouts::NewValue(
+            mojom::blink::AuctionAdConfigBuyerTimeouts::New());
+    return true;
+  }
+
+  v8::Local<v8::Value> value = input.perBuyerCumulativeTimeouts().V8Value();
+  if (auction_handle && value->IsPromise()) {
+    ScriptPromise promise(&script_state, value);
+    promise.Then(
+        MakeGarbageCollected<ScriptFunction>(
+            &script_state,
+            MakeGarbageCollected<
+                NavigatorAuction::AuctionHandle::BuyerTimeoutsResolved>(
+                auction_handle, auction_id->Clone(),
+                mojom::blink::AuctionAdConfigBuyerTimeoutField::
+                    kPerBuyerCumulativeTimeouts,
+                input.seller())),
+        MakeGarbageCollected<ScriptFunction>(
+            &script_state,
+            MakeGarbageCollected<NavigatorAuction::AuctionHandle::Rejected>(
+                auction_handle)));
+    output.auction_ad_config_non_shared_params->buyer_cumulative_timeouts =
+        mojom::blink::AuctionAdConfigMaybePromiseBuyerTimeouts::NewPromise(0);
+    return true;
+  }
+
+  mojom::blink::AuctionAdConfigBuyerTimeoutsPtr buyer_cumulative_timeouts =
+      ConvertNonPromisePerBuyerTimeoutsFromV8ToMojo(
+          script_state, exception_state, input.seller(), value,
+          mojom::blink::AuctionAdConfigBuyerTimeoutField::
+              kPerBuyerCumulativeTimeouts);
+  if (buyer_cumulative_timeouts) {
+    output.auction_ad_config_non_shared_params->buyer_cumulative_timeouts =
+        mojom::blink::AuctionAdConfigMaybePromiseBuyerTimeouts::NewValue(
+            std::move(buyer_cumulative_timeouts));
+    return true;
+  }
+  return false;
+}
+
 bool CopyPerBuyerExperimentIdsFromIdlToMojo(
     const ScriptState& script_state,
     ExceptionState& exception_state,
@@ -1575,6 +1650,9 @@
       !CopyPerBuyerTimeoutsFromIdlToMojo(auction_handle, auction_id.get(),
                                          script_state, exception_state, config,
                                          *mojo_config) ||
+      !CopyPerBuyerCumulativeTimeoutsFromIdlToMojo(
+          auction_handle, auction_id.get(), script_state, exception_state,
+          config, *mojo_config) ||
       !CopyPerBuyerExperimentIdsFromIdlToMojo(script_state, exception_state,
                                               config, *mojo_config) ||
       !CopyPerBuyerGroupLimitsFromIdlToMojo(script_state, exception_state,
@@ -1800,9 +1878,11 @@
 NavigatorAuction::AuctionHandle::BuyerTimeoutsResolved::BuyerTimeoutsResolved(
     AuctionHandle* auction_handle,
     mojom::blink::AuctionAdConfigAuctionIdPtr auction_id,
+    mojom::blink::AuctionAdConfigBuyerTimeoutField field,
     const String& seller_name)
     : auction_handle_(auction_handle),
       auction_id_(std::move(auction_id)),
+      field_(field),
       seller_name_(seller_name) {}
 
 ScriptValue NavigatorAuction::AuctionHandle::BuyerTimeoutsResolved::Call(
@@ -1816,7 +1896,7 @@
     v8::Local<v8::Value> v8_value = value.V8Value();
     if (!v8_value->IsUndefined() && !v8_value->IsNull()) {
       buyer_timeouts = ConvertNonPromisePerBuyerTimeoutsFromV8ToMojo(
-          *script_state, exception_state, seller_name_, v8_value);
+          *script_state, exception_state, seller_name_, v8_value, field_);
     }
   }
 
@@ -1825,7 +1905,7 @@
   }
 
   if (!exception_state.HadException()) {
-    auction_handle_->ResolvedBuyerTimeoutsPromise(auction_id_->Clone(),
+    auction_handle_->ResolvedBuyerTimeoutsPromise(auction_id_->Clone(), field_,
                                                   std::move(buyer_timeouts));
   } else {
     auction_handle_->Abort();
diff --git a/third_party/blink/renderer/modules/webgpu/gpu_device.cc b/third_party/blink/renderer/modules/webgpu/gpu_device.cc
index d040dbc4..da118564 100644
--- a/third_party/blink/renderer/modules/webgpu/gpu_device.cc
+++ b/third_party/blink/renderer/modules/webgpu/gpu_device.cc
@@ -341,7 +341,6 @@
       break;
     }
 
-    case WGPUCreatePipelineAsyncStatus_Error:
     case WGPUCreatePipelineAsyncStatus_InternalError:
     case WGPUCreatePipelineAsyncStatus_DeviceLost:
     case WGPUCreatePipelineAsyncStatus_DeviceDestroyed:
diff --git a/third_party/blink/renderer/platform/graphics/DEPS b/third_party/blink/renderer/platform/graphics/DEPS
index a987189..d24aa11 100644
--- a/third_party/blink/renderer/platform/graphics/DEPS
+++ b/third_party/blink/renderer/platform/graphics/DEPS
@@ -10,6 +10,7 @@
     "+base/strings/string_number_conversions.h",
     "+base/strings/string_split.h",
     "+base/strings/string_util.h",
+    "+base/task/task_traits.h",
     "+base/threading/sequenced_task_runner_handle.h",
     "+base/threading/thread_restrictions.h",
     "+base/barrier_closure.h",
diff --git a/third_party/blink/renderer/platform/graphics/canvas_2d_layer_bridge.cc b/third_party/blink/renderer/platform/graphics/canvas_2d_layer_bridge.cc
index 7d6f4379..020c25e 100644
--- a/third_party/blink/renderer/platform/graphics/canvas_2d_layer_bridge.cc
+++ b/third_party/blink/renderer/platform/graphics/canvas_2d_layer_bridge.cc
@@ -32,6 +32,8 @@
 #include "base/location.h"
 #include "base/rand_util.h"
 #include "base/task/single_thread_task_runner.h"
+#include "base/task/task_traits.h"
+#include "base/time/time.h"
 #include "base/timer/elapsed_timer.h"
 #include "cc/layers/texture_layer.h"
 #include "components/viz/common/resources/transferable_resource.h"
@@ -52,31 +54,171 @@
 #include "third_party/blink/renderer/platform/instrumentation/histogram.h"
 #include "third_party/blink/renderer/platform/instrumentation/tracing/trace_event.h"
 #include "third_party/blink/renderer/platform/runtime_enabled_features.h"
+#include "third_party/blink/renderer/platform/scheduler/public/main_thread.h"
 #include "third_party/blink/renderer/platform/scheduler/public/thread_scheduler.h"
+#include "third_party/blink/renderer/platform/scheduler/public/worker_pool.h"
+#include "third_party/blink/renderer/platform/wtf/cross_thread_copier_std.h"
+#include "third_party/blink/renderer/platform/wtf/cross_thread_functional.h"
 #include "third_party/blink/renderer/platform/wtf/functional.h"
+#include "third_party/skia/include/core/SkAlphaType.h"
 #include "third_party/skia/include/core/SkData.h"
+#include "third_party/skia/include/core/SkEncodedImageFormat.h"
+#include "third_party/skia/include/core/SkImage.h"
 #include "third_party/skia/include/core/SkSurface.h"
 
 namespace blink {
 
-namespace {
-
-BASE_FEATURE(
-    kCanvas2DHibernation,
-    "Canvas2DHibernation",
-#if BUILDFLAG(IS_MAC)
-    // Canvas hibernation is not always enabled on MacOS X due to a bug that
-    // causes content loss. TODO: Find a better fix for crbug.com/588434
-    base::FeatureState::FEATURE_DISABLED_BY_DEFAULT
-#else
-    base::FeatureState::FEATURE_ENABLED_BY_DEFAULT
-#endif
-);
-}
-
 // static
 bool Canvas2DLayerBridge::IsHibernationEnabled() {
-  return base::FeatureList::IsEnabled(kCanvas2DHibernation);
+  return base::FeatureList::IsEnabled(features::kCanvas2DHibernation);
+}
+
+void HibernationHandler::TakeHibernationImage(sk_sp<SkImage>&& image) {
+  DCheckInvariant();
+  epoch_++;
+  image_ = image;
+
+  if (!base::FeatureList::IsEnabled(features::kCanvasCompressHibernatedImage)) {
+    return;
+  }
+
+  width_ = image_->width();
+  height_ = image_->height();
+  bytes_per_pixel_ = image_->imageInfo().bytesPerPixel();
+
+  // If we had an encoded version, discard it.
+  encoded_.reset();
+
+  // Don't bother compressing very small canvases.
+  size_t memory_size = image_->height() * static_cast<size_t>(image_->width()) *
+                       static_cast<size_t>(image_->imageInfo().bytesPerPixel());
+  if (memory_size < 16 * 1024) {
+    return;
+  }
+
+  // Don't post the compression task to the thread pool with a delay right away.
+  // The task increases the reference count on the SkImage. In the case of rapid
+  // foreground / background transitions, each transition allocates a new
+  // SkImage. If we post a compression task right away with a sk_sp<SkImage> as
+  // a parameter, this takes a reference on the underlying SkImage, keeping it
+  // alive until the task runs. This means that posting the compression task
+  // right away would increase memory usage by a lot in these cases.
+  //
+  // Rather, post a main thread task later that will check whether we are still
+  // in hibernation mode, and in the same hibernation "epoch" as last time. If
+  // this is the case, then compress.
+  //
+  // This simplifies tracking of background / foreground cycles, at the cost of
+  // running one extra trivial task for each cycle.
+  //
+  // Note: not using a delayed idle tasks, because idle tasks do not run when
+  // the renderer is idle. In other words, a delayed idle task would not execute
+  // as long as the renderer is in background, which completely defeats the
+  // purpose.
+  GetMainThreadTaskRunner()->PostDelayedTask(
+      FROM_HERE,
+      base::BindOnce(&HibernationHandler::OnAfterHibernation,
+                     weak_ptr_factory_.GetWeakPtr(), epoch_),
+      kBeforeCompressionDelay);
+}
+
+void HibernationHandler::OnAfterHibernation(uint64_t epoch) {
+  DCheckInvariant();
+  DCHECK(
+      base::FeatureList::IsEnabled(features::kCanvasCompressHibernatedImage));
+  // Either we no longer have the image (because we are not hibernating), or we
+  // went through another visible / not visible cycle (in which case it is too
+  // early to compress).
+  if (epoch_ != epoch || !image_) {
+    return;
+  }
+  auto task_runner = GetMainThreadTaskRunner();
+  auto params = std::make_unique<BackgroundTaskParams>(
+      image_, epoch_, weak_ptr_factory_.GetWeakPtr(), task_runner);
+
+  if (background_thread_task_runner_for_testing_) {
+    background_thread_task_runner_for_testing_->PostTask(
+        FROM_HERE,
+        base::BindOnce(&HibernationHandler::Encode, std::move(params)));
+  } else {
+    worker_pool::PostTask(
+        FROM_HERE, {base::TaskPriority::BEST_EFFORT},
+        CrossThreadBindOnce(&HibernationHandler::Encode, std::move(params)));
+  }
+}
+
+void HibernationHandler::OnEncoded(
+    std::unique_ptr<HibernationHandler::BackgroundTaskParams> params,
+    sk_sp<SkData> encoded) {
+  DCheckInvariant();
+  DCHECK(
+      base::FeatureList::IsEnabled(features::kCanvasCompressHibernatedImage));
+  // Discard the compressed image, it is no longer current.
+  if (params->epoch != epoch_ || !IsHibernating()) {
+    return;
+  }
+
+  DCHECK_EQ(image_.get(), params->image.get());
+  encoded_ = encoded;
+  image_ = nullptr;
+}
+
+scoped_refptr<base::SingleThreadTaskRunner>
+HibernationHandler::GetMainThreadTaskRunner() const {
+  return main_thread_task_runner_for_testing_
+             ? main_thread_task_runner_for_testing_
+             : Thread::MainThread()->GetTaskRunner(
+                   MainThreadTaskRunnerRestricted());
+}
+
+void HibernationHandler::Encode(
+    std::unique_ptr<HibernationHandler::BackgroundTaskParams> params) {
+  TRACE_EVENT0("blink", __PRETTY_FUNCTION__);
+  DCHECK(
+      base::FeatureList::IsEnabled(features::kCanvasCompressHibernatedImage));
+  sk_sp<SkData> encoded =
+      params->image->encodeToData(SkEncodedImageFormat::kPNG, 100);
+
+  auto* reply_task_runner = params->reply_task_runner.get();
+  reply_task_runner->PostTask(
+      FROM_HERE,
+      base::BindOnce(&HibernationHandler::OnEncoded, params->weak_instance,
+                     std::move(params), encoded));
+}
+
+sk_sp<SkImage> HibernationHandler::GetImage() {
+  TRACE_EVENT0("blink", __PRETTY_FUNCTION__);
+  DCheckInvariant();
+  if (image_) {
+    return image_;
+  }
+
+  DCHECK(encoded_);
+  DCHECK(
+      base::FeatureList::IsEnabled(features::kCanvasCompressHibernatedImage));
+
+  // Note: not discarding the encoded image.
+  return SkImage::MakeFromEncoded(encoded_)->makeRasterImage();
+}
+
+void HibernationHandler::Clear() {
+  DCheckInvariant();
+  encoded_ = nullptr;
+  image_ = nullptr;
+}
+
+size_t HibernationHandler::memory_size() const {
+  DCheckInvariant();
+  DCHECK(IsHibernating());
+  if (is_encoded()) {
+    return encoded_->size();
+  } else {
+    return original_memory_size();
+  }
+}
+
+size_t HibernationHandler::original_memory_size() const {
+  return static_cast<size_t>(width_) * height_ * bytes_per_pixel_;
 }
 
 Canvas2DLayerBridge::Canvas2DLayerBridge(const gfx::Size& size,
@@ -166,11 +308,13 @@
 static void LoseContextInBackgroundWrapper(
     base::WeakPtr<Canvas2DLayerBridge> bridge,
     base::TimeTicks /*idleDeadline*/) {
-  if (bridge)
+  if (bridge) {
     bridge->LoseContext();
+  }
 }
 
 void Canvas2DLayerBridge::Hibernate() {
+  TRACE_EVENT0("blink", __PRETTY_FUNCTION__);
   DCHECK(!IsHibernating());
   DCHECK(hibernation_scheduled_);
 
@@ -215,14 +359,18 @@
     logger_->ReportHibernationEvent(kHibernationAbortedDueSnapshotFailure);
     return;
   }
-  hibernation_image_ = snapshot->PaintImageForCurrentFrame().GetSwSkImage();
+  hibernation_handler_.TakeHibernationImage(
+      snapshot->PaintImageForCurrentFrame().GetSwSkImage());
+
   ResetResourceProvider();
-  if (layer_)
+  if (layer_) {
     layer_->ClearTexture();
+  }
 
   // shouldBeDirectComposited() may have changed.
-  if (resource_host_)
+  if (resource_host_) {
     resource_host_->SetNeedsCompositingUpdate();
+  }
   logger_->DidStartHibernating();
 }
 
@@ -340,10 +488,13 @@
   }
 
   PaintImageBuilder builder = PaintImageBuilder::WithDefault();
-  builder.set_image(hibernation_image_, PaintImage::GetNextContentId());
+  builder.set_image(hibernation_handler_.GetImage(),
+                    PaintImage::GetNextContentId());
   builder.set_id(PaintImage::GetNextId());
   resource_provider->RestoreBackBuffer(builder.TakePaintImage());
-  hibernation_image_.reset();
+  // The hibernation image is no longer valid, clear it.
+  hibernation_handler_.Clear();
+  DCHECK(!IsHibernating());
 
   if (resource_host_) {
     // shouldBeDirectComposited() may have changed.
@@ -607,19 +758,23 @@
 }
 
 bool Canvas2DLayerBridge::CheckResourceProviderValid() {
-  if (IsHibernating())
+  if (IsHibernating()) {
     return true;
-  if (!layer_ || raster_mode_ == RasterMode::kCPU)
+  }
+  if (!layer_ || raster_mode_ == RasterMode::kCPU) {
     return true;
-  if (context_lost_)
+  }
+  if (context_lost_) {
     return false;
+  }
   if (ResourceProvider() && IsAccelerated() &&
       ResourceProvider()->IsGpuContextLost()) {
     context_lost_ = true;
     ClearPendingRasterTimers();
     ResetResourceProvider();
-    if (resource_host_)
+    if (resource_host_) {
       resource_host_->NotifyGpuContextLost();
+    }
     return false;
   }
   return !!GetOrCreateResourceProvider();
@@ -772,8 +927,10 @@
 scoped_refptr<StaticBitmapImage> Canvas2DLayerBridge::NewImageSnapshot() {
   if (snapshot_state_ == kInitialSnapshotState)
     snapshot_state_ = kDidAcquireSnapshot;
-  if (IsHibernating())
-    return UnacceleratedStaticBitmapImage::Create(hibernation_image_);
+  if (IsHibernating()) {
+    return UnacceleratedStaticBitmapImage::Create(
+        hibernation_handler_.GetImage());
+  }
   if (!IsValid())
     return nullptr;
   // GetOrCreateResourceProvider needs to be called before FlushRecording, to
diff --git a/third_party/blink/renderer/platform/graphics/canvas_2d_layer_bridge.h b/third_party/blink/renderer/platform/graphics/canvas_2d_layer_bridge.h
index ed344ceb..f134d141 100644
--- a/third_party/blink/renderer/platform/graphics/canvas_2d_layer_bridge.h
+++ b/third_party/blink/renderer/platform/graphics/canvas_2d_layer_bridge.h
@@ -33,6 +33,7 @@
 #include "base/memory/weak_ptr.h"
 #include "base/numerics/checked_math.h"
 #include "base/rand_util.h"
+#include "base/task/single_thread_task_runner.h"
 #include "base/time/time.h"
 #include "base/types/optional_util.h"
 #include "build/build_config.h"
@@ -46,6 +47,7 @@
 #include "third_party/blink/renderer/platform/wtf/allocator/allocator.h"
 #include "third_party/blink/renderer/platform/wtf/deque.h"
 #include "third_party/blink/renderer/platform/wtf/ref_counted.h"
+#include "third_party/blink/renderer/platform/wtf/wtf.h"
 #include "third_party/khronos/GLES2/gl2.h"
 #include "third_party/skia/include/core/SkRefCnt.h"
 #include "ui/gfx/color_space.h"
@@ -65,6 +67,94 @@
 class SharedContextRateLimiter;
 class StaticBitmapImage;
 
+// All the fields are main-thread only. See DCheckInvariant() for invariants.
+class PLATFORM_EXPORT HibernationHandler {
+ public:
+  // Semi-arbitrary threshold. Some past experiments (e.g. tile discard) have
+  // shown that taking action after 5 minutes has a positive impact on memory,
+  // and a minimal impact on tab switching latency (and on needless
+  // compression).
+  static constexpr base::TimeDelta kBeforeCompressionDelay = base::Minutes(5);
+
+  void TakeHibernationImage(sk_sp<SkImage>&& image);
+  // Returns the uncompressed image for this hibernation image. Does not
+  // invalidate the hibernated image. Must call `Clear()` if invalidation is
+  // required.
+  sk_sp<SkImage> GetImage();
+  // Invalidate the hibernated image.
+  void Clear();
+
+  bool IsHibernating() const {
+    DCheckInvariant();
+    return image_ != nullptr || encoded_ != nullptr;
+  }
+  bool is_encoded() const {
+    DCheckInvariant();
+    return encoded_ != nullptr;
+  }
+  size_t memory_size() const;
+  size_t original_memory_size() const;
+  int width() const { return width_; }
+  int height() const { return height_; }
+
+  void SetTaskRunnersForTesting(
+      scoped_refptr<base::SingleThreadTaskRunner> main_thread_task_runner,
+      scoped_refptr<base::SingleThreadTaskRunner>
+          background_thread_task_runner) {
+    main_thread_task_runner_for_testing_ = main_thread_task_runner;
+    background_thread_task_runner_for_testing_ = background_thread_task_runner;
+  }
+
+ private:
+  struct BackgroundTaskParams final {
+    BackgroundTaskParams(
+        sk_sp<SkImage> image,
+        uint64_t epoch,
+        base::WeakPtr<HibernationHandler> weak_instance,
+        scoped_refptr<base::SingleThreadTaskRunner> reply_task_runner)
+        : image(image),
+          epoch(epoch),
+          weak_instance(weak_instance),
+          reply_task_runner(reply_task_runner) {}
+
+    BackgroundTaskParams(const BackgroundTaskParams&) = delete;
+    BackgroundTaskParams& operator=(const BackgroundTaskParams&) = delete;
+    ~BackgroundTaskParams() { DCHECK(IsMainThread()); }
+
+    const sk_sp<SkImage> image;
+    const uint64_t epoch;
+    const base::WeakPtr<HibernationHandler> weak_instance;
+    const scoped_refptr<base::SingleThreadTaskRunner> reply_task_runner;
+  };
+
+  void DCheckInvariant() const {
+    DCHECK(IsMainThread());
+    DCHECK(!((image_ != nullptr) && (encoded_ != nullptr)));
+  }
+  void OnAfterHibernation(uint64_t initial_epoch);
+  static void Encode(std::unique_ptr<BackgroundTaskParams> params);
+  void OnEncoded(
+      std::unique_ptr<HibernationHandler::BackgroundTaskParams> params,
+      sk_sp<SkData> encoded);
+  scoped_refptr<base::SingleThreadTaskRunner> GetMainThreadTaskRunner() const;
+
+  // Incremented each time the canvas is hibernated.
+  uint64_t epoch_ = 0;
+  // Uncompressed hibernation image.
+  sk_sp<SkImage> image_ = nullptr;
+  // Compressed hibernation image.
+  sk_sp<SkData> encoded_ = nullptr;
+  scoped_refptr<base::SingleThreadTaskRunner>
+      main_thread_task_runner_for_testing_;
+  scoped_refptr<base::SingleThreadTaskRunner>
+      background_thread_task_runner_for_testing_;
+  int width_;
+  int height_;
+  int bytes_per_pixel_;
+
+  base::WeakPtrFactory<HibernationHandler> weak_ptr_factory_{this};
+};
+
 class PLATFORM_EXPORT Canvas2DLayerBridge : public cc::TextureLayerClient {
  public:
   Canvas2DLayerBridge(const gfx::Size&, RasterMode, OpacityMode opacity_mode);
@@ -111,7 +201,7 @@
   // This is used for a memory usage experiment: frees canvas resource when
   // canvas is in an invisible tab.
   void LoseContext();
-  bool IsHibernating() const { return hibernation_image_ != nullptr; }
+  bool IsHibernating() const { return hibernation_handler_.IsHibernating(); }
 
   bool HasRecordedDrawCommands() { return have_recorded_draw_commands_; }
 
@@ -162,6 +252,10 @@
 
   static bool IsHibernationEnabled();
 
+  HibernationHandler& GetHibernationHandlerForTesting() {
+    return hibernation_handler_;
+  }
+
  private:
   friend class Canvas2DLayerBridgeTest;
   friend class CanvasRenderingContext2DTest;
@@ -176,7 +270,8 @@
   // Check if the Raster Mode is GPU and if the GPU context is not lost
   bool ShouldAccelerate() const;
 
-  sk_sp<SkImage> hibernation_image_;
+  HibernationHandler hibernation_handler_;
+
   scoped_refptr<cc::TextureLayer> layer_;
   std::unique_ptr<SharedContextRateLimiter> rate_limiter_;
   std::unique_ptr<Logger> logger_;
diff --git a/third_party/blink/renderer/platform/graphics/canvas_2d_layer_bridge_test.cc b/third_party/blink/renderer/platform/graphics/canvas_2d_layer_bridge_test.cc
index 0442563f..58fc337 100644
--- a/third_party/blink/renderer/platform/graphics/canvas_2d_layer_bridge_test.cc
+++ b/third_party/blink/renderer/platform/graphics/canvas_2d_layer_bridge_test.cc
@@ -25,12 +25,18 @@
 
 #include "third_party/blink/renderer/platform/graphics/canvas_2d_layer_bridge.h"
 
+#include <list>
+#include <memory>
 #include <utility>
 
+#include "base/functional/callback_forward.h"
 #include "base/location.h"
 #include "base/memory/scoped_refptr.h"
 #include "base/run_loop.h"
 #include "base/task/single_thread_task_runner.h"
+#include "base/test/bind.h"
+#include "base/test/scoped_feature_list.h"
+#include "base/test/task_environment.h"
 #include "build/build_config.h"
 #include "cc/layers/texture_layer.h"
 #include "cc/paint/paint_flags.h"
@@ -48,6 +54,7 @@
 #include "gpu/command_buffer/common/gpu_memory_buffer_support.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/blink/public/common/features.h"
 #include "third_party/blink/public/platform/platform.h"
 #include "third_party/blink/public/platform/scheduler/test/renderer_scheduler_test_support.h"
 #include "third_party/blink/public/platform/scheduler/web_thread_scheduler.h"
@@ -67,8 +74,6 @@
 #include "third_party/skia/include/gpu/GrDirectContext.h"
 #include "third_party/skia/include/gpu/gl/GrGLTypes.h"
 
-#include <memory>
-
 namespace blink {
 
 namespace {
@@ -82,6 +87,53 @@
 using testing::SetArgPointee;
 using testing::Test;
 
+class TestSingleThreadTaskRunner : public base::SingleThreadTaskRunner {
+ public:
+  bool PostDelayedTask(const base::Location& from_here,
+                       base::OnceClosure task,
+                       base::TimeDelta delay) override {
+    if (delay.is_zero()) {
+      immediate_.push_back(std::move(task));
+    } else {
+      delayed_.push_back(std::move(task));
+    }
+
+    return true;
+  }
+  bool PostNonNestableDelayedTask(const base::Location& from_here,
+                                  base::OnceClosure task,
+                                  base::TimeDelta delay) override {
+    return false;
+  }
+  bool RunsTasksInCurrentSequence() const override { return false; }
+
+  static size_t RunAll(std::list<base::OnceClosure>& tasks) {
+    size_t count = 0;
+    while (!tasks.empty()) {
+      std::move(tasks.front()).Run();
+      tasks.pop_front();
+      count++;
+    }
+    return count;
+  }
+
+  static bool RunOne(std::list<base::OnceClosure>& tasks) {
+    if (tasks.empty()) {
+      return false;
+    }
+    std::move(tasks.front()).Run();
+    tasks.pop_front();
+    return true;
+  }
+
+  std::list<base::OnceClosure>& delayed() { return delayed_; }
+  std::list<base::OnceClosure>& immediate() { return immediate_; }
+
+ private:
+  std::list<base::OnceClosure> delayed_;
+  std::list<base::OnceClosure> immediate_;
+};
+
 class ImageTrackingDecodeCache : public cc::StubDecodeCache {
  public:
   ImageTrackingDecodeCache() = default;
@@ -1017,4 +1069,347 @@
   EXPECT_FALSE(bridge->HasRateLimiterForTesting());
 }
 
+namespace {
+void SetIsInHiddenPage(
+    Canvas2DLayerBridge* bridge,
+    ScopedTestingPlatformSupport<GpuMemoryBufferTestPlatform>& platform,
+    bool hidden) {
+  bridge->SetIsInHiddenPage(hidden);
+  // Make sure that idle tasks run when hidden.
+  if (hidden) {
+    scheduler::RunIdleTasksForTesting(
+        scheduler::WebThreadScheduler::MainThreadScheduler(),
+        base::DoNothing());
+    platform->RunUntilIdle();
+    EXPECT_TRUE(bridge->IsHibernating());
+  }
+}
+}  // namespace
+
+TEST_F(Canvas2DLayerBridgeTest, HibernationHandlerSimpleTest) {
+  base::test::ScopedFeatureList scoped_feature_list;
+  scoped_feature_list.InitWithFeatures(
+      {features::kCanvas2DHibernation,
+       features::kCanvasCompressHibernatedImage},
+      {});
+
+  auto task_runner = base::MakeRefCounted<TestSingleThreadTaskRunner>();
+  ScopedTestingPlatformSupport<GpuMemoryBufferTestPlatform> platform;
+  std::unique_ptr<Canvas2DLayerBridge> bridge =
+      MakeBridge(gfx::Size(300, 200), RasterMode::kGPU, kNonOpaque);
+  DrawSomething(bridge.get());
+
+  auto& handler = bridge->GetHibernationHandlerForTesting();
+  handler.SetTaskRunnersForTesting(task_runner, task_runner);
+
+  SetIsInHiddenPage(bridge.get(), platform, true);
+
+  EXPECT_TRUE(bridge->IsHibernating());
+  // Triggers a delayed task for encoding.
+  EXPECT_FALSE(task_runner->delayed().empty());
+  EXPECT_TRUE(task_runner->immediate().empty());
+
+  TestSingleThreadTaskRunner::RunAll(task_runner->delayed());
+  // Posted the background compression task.
+  EXPECT_FALSE(task_runner->immediate().empty());
+
+  size_t uncompressed_size = 300u * 200 * 4;
+  EXPECT_EQ(handler.width(), 300);
+  EXPECT_EQ(handler.height(), 200);
+  EXPECT_EQ(uncompressed_size, handler.memory_size());
+
+  // Runs the encoding task, but also the callback one.
+  EXPECT_EQ(2u, TestSingleThreadTaskRunner::RunAll(task_runner->immediate()));
+  EXPECT_TRUE(handler.is_encoded());
+  EXPECT_LT(handler.memory_size(), uncompressed_size);
+  EXPECT_EQ(handler.original_memory_size(), uncompressed_size);
+
+  SetIsInHiddenPage(bridge.get(), platform, false);
+  EXPECT_FALSE(handler.is_encoded());
+
+  EXPECT_TRUE(bridge->IsAccelerated());
+  EXPECT_FALSE(bridge->IsHibernating());
+  EXPECT_TRUE(bridge->IsValid());
+}
+
+TEST_F(Canvas2DLayerBridgeTest, HibernationHandlerDisabledTest) {
+  base::test::ScopedFeatureList scoped_feature_list;
+  scoped_feature_list.InitWithFeatures(
+      {features::kCanvas2DHibernation},
+      {features::kCanvasCompressHibernatedImage});
+
+  auto task_runner = base::MakeRefCounted<TestSingleThreadTaskRunner>();
+  ScopedTestingPlatformSupport<GpuMemoryBufferTestPlatform> platform;
+  std::unique_ptr<Canvas2DLayerBridge> bridge =
+      MakeBridge(gfx::Size(300, 200), RasterMode::kGPU, kNonOpaque);
+  DrawSomething(bridge.get());
+
+  auto& handler = bridge->GetHibernationHandlerForTesting();
+  handler.SetTaskRunnersForTesting(task_runner, task_runner);
+
+  SetIsInHiddenPage(bridge.get(), platform, true);
+
+  EXPECT_TRUE(bridge->IsHibernating());
+
+  // No posted tasks.
+  EXPECT_TRUE(task_runner->delayed().empty());
+  EXPECT_TRUE(task_runner->immediate().empty());
+
+  // But still hibernating.
+  EXPECT_TRUE(bridge->IsHibernating());
+}
+
+TEST_F(Canvas2DLayerBridgeTest, HibernationHandlerForegroundTooEarly) {
+  base::test::ScopedFeatureList scoped_feature_list;
+  scoped_feature_list.InitWithFeatures(
+      {features::kCanvas2DHibernation,
+       features::kCanvasCompressHibernatedImage},
+      {});
+
+  auto task_runner = base::MakeRefCounted<TestSingleThreadTaskRunner>();
+  ScopedTestingPlatformSupport<GpuMemoryBufferTestPlatform> platform;
+  std::unique_ptr<Canvas2DLayerBridge> bridge =
+      MakeBridge(gfx::Size(300, 300), RasterMode::kGPU, kNonOpaque);
+  DrawSomething(bridge.get());
+
+  auto& handler = bridge->GetHibernationHandlerForTesting();
+  handler.SetTaskRunnersForTesting(task_runner, task_runner);
+  SetIsInHiddenPage(bridge.get(), platform, true);
+
+  // Triggers a delayed task for encoding.
+  EXPECT_FALSE(task_runner->delayed().empty());
+
+  EXPECT_TRUE(bridge->IsHibernating());
+  SetIsInHiddenPage(bridge.get(), platform, false);
+
+  // Nothing happens, because the page came to foreground in-between.
+  TestSingleThreadTaskRunner::RunAll(task_runner->delayed());
+  EXPECT_TRUE(task_runner->immediate().empty());
+  EXPECT_FALSE(handler.is_encoded());
+}
+
+TEST_F(Canvas2DLayerBridgeTest, HibernationHandlerBackgroundForeground) {
+  base::test::ScopedFeatureList scoped_feature_list;
+  scoped_feature_list.InitWithFeatures(
+      {features::kCanvas2DHibernation,
+       features::kCanvasCompressHibernatedImage},
+      {});
+
+  auto task_runner = base::MakeRefCounted<TestSingleThreadTaskRunner>();
+  ScopedTestingPlatformSupport<GpuMemoryBufferTestPlatform> platform;
+  std::unique_ptr<Canvas2DLayerBridge> bridge =
+      MakeBridge(gfx::Size(300, 300), RasterMode::kGPU, kNonOpaque);
+  DrawSomething(bridge.get());
+
+  auto& handler = bridge->GetHibernationHandlerForTesting();
+  handler.SetTaskRunnersForTesting(task_runner, task_runner);
+
+  // Background -> Foreground -> Background
+  SetIsInHiddenPage(bridge.get(), platform, true);
+  SetIsInHiddenPage(bridge.get(), platform, false);
+  SetIsInHiddenPage(bridge.get(), platform, true);
+
+  // 2 delayed task that will potentially trigger encoding.
+  EXPECT_EQ(2u, TestSingleThreadTaskRunner::RunAll(task_runner->delayed()));
+  // But a single encoding task (plus the main thread callback).
+  EXPECT_EQ(2u, TestSingleThreadTaskRunner::RunAll(task_runner->immediate()));
+  EXPECT_TRUE(handler.is_encoded());
+}
+
+TEST_F(Canvas2DLayerBridgeTest, HibernationHandlerForegroundAfterEncoding) {
+  base::test::ScopedFeatureList scoped_feature_list;
+  scoped_feature_list.InitWithFeatures(
+      {features::kCanvas2DHibernation,
+       features::kCanvasCompressHibernatedImage},
+      {});
+
+  auto task_runner = base::MakeRefCounted<TestSingleThreadTaskRunner>();
+  ScopedTestingPlatformSupport<GpuMemoryBufferTestPlatform> platform;
+  std::unique_ptr<Canvas2DLayerBridge> bridge =
+      MakeBridge(gfx::Size(300, 300), RasterMode::kGPU, kNonOpaque);
+  DrawSomething(bridge.get());
+
+  auto& handler = bridge->GetHibernationHandlerForTesting();
+  handler.SetTaskRunnersForTesting(task_runner, task_runner);
+
+  SetIsInHiddenPage(bridge.get(), platform, true);
+  // Wait for the encoding task to be posted.
+  EXPECT_EQ(1u, TestSingleThreadTaskRunner::RunAll(task_runner->delayed()));
+  EXPECT_TRUE(TestSingleThreadTaskRunner::RunOne(task_runner->immediate()));
+  // Come back to foreground after (or during) compression, but before the
+  // callback.
+  SetIsInHiddenPage(bridge.get(), platform, false);
+
+  // The callback is still pending.
+  EXPECT_EQ(1u, TestSingleThreadTaskRunner::RunAll(task_runner->immediate()));
+  // But the encoded version is dropped.
+  EXPECT_FALSE(handler.is_encoded());
+  EXPECT_FALSE(bridge->IsHibernating());
+}
+
+TEST_F(Canvas2DLayerBridgeTest,
+       HibernationHandlerForegroundFlipForAfterEncoding) {
+  base::test::ScopedFeatureList scoped_feature_list;
+  scoped_feature_list.InitWithFeatures(
+      {features::kCanvas2DHibernation,
+       features::kCanvasCompressHibernatedImage},
+      {});
+
+  auto task_runner = base::MakeRefCounted<TestSingleThreadTaskRunner>();
+  ScopedTestingPlatformSupport<GpuMemoryBufferTestPlatform> platform;
+  std::unique_ptr<Canvas2DLayerBridge> bridge =
+      MakeBridge(gfx::Size(300, 300), RasterMode::kGPU, kNonOpaque);
+  DrawSomething(bridge.get());
+
+  auto& handler = bridge->GetHibernationHandlerForTesting();
+  handler.SetTaskRunnersForTesting(task_runner, task_runner);
+
+  SetIsInHiddenPage(bridge.get(), platform, true);
+  // Wait for the encoding task to be posted.
+  EXPECT_EQ(1u, TestSingleThreadTaskRunner::RunAll(task_runner->delayed()));
+  EXPECT_TRUE(TestSingleThreadTaskRunner::RunOne(task_runner->immediate()));
+  // Come back to foreground after (or during) compression, but before the
+  // callback.
+  SetIsInHiddenPage(bridge.get(), platform, false);
+  // And back to background.
+  SetIsInHiddenPage(bridge.get(), platform, true);
+  EXPECT_TRUE(bridge->IsHibernating());
+
+  // The callback is still pending.
+  EXPECT_EQ(1u, TestSingleThreadTaskRunner::RunAll(task_runner->immediate()));
+  // But the encoded version is dropped (epoch mismatch).
+  EXPECT_FALSE(handler.is_encoded());
+  // Yet we are hibernating (since the bridge is in background).
+  EXPECT_TRUE(bridge->IsHibernating());
+
+  EXPECT_EQ(1u, TestSingleThreadTaskRunner::RunAll(task_runner->delayed()));
+  EXPECT_EQ(2u, TestSingleThreadTaskRunner::RunAll(task_runner->immediate()));
+  EXPECT_TRUE(handler.is_encoded());
+  // Yet we are hibernating (since the bridge is in background).
+  EXPECT_TRUE(bridge->IsHibernating());
+}
+
+TEST_F(Canvas2DLayerBridgeTest,
+       HibernationHandlerForegroundFlipForBeforeEncoding) {
+  base::test::ScopedFeatureList scoped_feature_list;
+  scoped_feature_list.InitWithFeatures(
+      {features::kCanvas2DHibernation,
+       features::kCanvasCompressHibernatedImage},
+      {});
+
+  auto task_runner = base::MakeRefCounted<TestSingleThreadTaskRunner>();
+  ScopedTestingPlatformSupport<GpuMemoryBufferTestPlatform> platform;
+  std::unique_ptr<Canvas2DLayerBridge> bridge =
+      MakeBridge(gfx::Size(300, 300), RasterMode::kGPU, kNonOpaque);
+  DrawSomething(bridge.get());
+
+  auto& handler = bridge->GetHibernationHandlerForTesting();
+  handler.SetTaskRunnersForTesting(task_runner, task_runner);
+
+  SetIsInHiddenPage(bridge.get(), platform, true);
+  // Wait for the encoding task to be posted.
+  EXPECT_EQ(1u, TestSingleThreadTaskRunner::RunAll(task_runner->delayed()));
+  // Come back to foreground before compression.
+  SetIsInHiddenPage(bridge.get(), platform, false);
+  // And back to background.
+  SetIsInHiddenPage(bridge.get(), platform, true);
+  EXPECT_TRUE(bridge->IsHibernating());
+  // Compression still happens, since it's a static task, doesn't look at the
+  // epoch before compressing.
+  EXPECT_EQ(2u, TestSingleThreadTaskRunner::RunAll(task_runner->immediate()));
+
+  // But the encoded version is dropped (epoch mismatch).
+  EXPECT_FALSE(handler.is_encoded());
+  // Yet we are hibernating (since the bridge is in background).
+  EXPECT_TRUE(bridge->IsHibernating());
+}
+
+TEST_F(Canvas2DLayerBridgeTest,
+       HibernationHandlerCanvasSnapshottedInBackground) {
+  base::test::ScopedFeatureList scoped_feature_list;
+  scoped_feature_list.InitWithFeatures(
+      {features::kCanvas2DHibernation,
+       features::kCanvasCompressHibernatedImage},
+      {});
+
+  auto task_runner = base::MakeRefCounted<TestSingleThreadTaskRunner>();
+  ScopedTestingPlatformSupport<GpuMemoryBufferTestPlatform> platform;
+  std::unique_ptr<Canvas2DLayerBridge> bridge =
+      MakeBridge(gfx::Size(300, 300), RasterMode::kGPU, kNonOpaque);
+  DrawSomething(bridge.get());
+
+  auto& handler = bridge->GetHibernationHandlerForTesting();
+  handler.SetTaskRunnersForTesting(task_runner, task_runner);
+
+  SetIsInHiddenPage(bridge.get(), platform, true);
+  // Wait for the canvas to be encoded.
+  EXPECT_EQ(1u, TestSingleThreadTaskRunner::RunAll(task_runner->delayed()));
+  EXPECT_EQ(2u, TestSingleThreadTaskRunner::RunAll(task_runner->immediate()));
+  EXPECT_TRUE(handler.is_encoded());
+
+  EXPECT_TRUE(bridge->IsHibernating());
+  auto image = bridge->NewImageSnapshot();
+  EXPECT_TRUE(bridge->IsHibernating());
+  // Do not discard the encoded representation.
+  EXPECT_TRUE(handler.is_encoded());
+}
+
+TEST_F(Canvas2DLayerBridgeTest, HibernationHandlerCanvasWriteInBackground) {
+  base::test::ScopedFeatureList scoped_feature_list;
+  scoped_feature_list.InitWithFeatures(
+      {features::kCanvas2DHibernation,
+       features::kCanvasCompressHibernatedImage},
+      {});
+
+  auto task_runner = base::MakeRefCounted<TestSingleThreadTaskRunner>();
+  ScopedTestingPlatformSupport<GpuMemoryBufferTestPlatform> platform;
+  std::unique_ptr<Canvas2DLayerBridge> bridge =
+      MakeBridge(gfx::Size(300, 300), RasterMode::kGPU, kNonOpaque);
+  DrawSomething(bridge.get());
+
+  auto& handler = bridge->GetHibernationHandlerForTesting();
+  handler.SetTaskRunnersForTesting(task_runner, task_runner);
+
+  SetIsInHiddenPage(bridge.get(), platform, true);
+  // Wait for the canvas to be encoded.
+  EXPECT_EQ(1u, TestSingleThreadTaskRunner::RunAll(task_runner->delayed()));
+  EXPECT_EQ(2u, TestSingleThreadTaskRunner::RunAll(task_runner->immediate()));
+  EXPECT_TRUE(handler.is_encoded());
+
+  bridge->WritePixels(SkImageInfo::MakeN32Premul(10, 10), nullptr, 10, 0, 0);
+
+  EXPECT_FALSE(bridge->IsHibernating());
+  EXPECT_FALSE(handler.is_encoded());
+}
+
+TEST_F(Canvas2DLayerBridgeTest, HibernationHandlerCanvasWriteWhileCompressing) {
+  base::test::ScopedFeatureList scoped_feature_list;
+  scoped_feature_list.InitWithFeatures(
+      {features::kCanvas2DHibernation,
+       features::kCanvasCompressHibernatedImage},
+      {});
+
+  auto task_runner = base::MakeRefCounted<TestSingleThreadTaskRunner>();
+  ScopedTestingPlatformSupport<GpuMemoryBufferTestPlatform> platform;
+  std::unique_ptr<Canvas2DLayerBridge> bridge =
+      MakeBridge(gfx::Size(300, 300), RasterMode::kGPU, kNonOpaque);
+  DrawSomething(bridge.get());
+
+  auto& handler = bridge->GetHibernationHandlerForTesting();
+  handler.SetTaskRunnersForTesting(task_runner, task_runner);
+
+  SetIsInHiddenPage(bridge.get(), platform, true);
+  // Wait for the canvas to be encoded.
+  EXPECT_EQ(1u, TestSingleThreadTaskRunner::RunAll(task_runner->delayed()));
+  // Run the compression task, not the callback.
+  EXPECT_TRUE(TestSingleThreadTaskRunner::RunOne(task_runner->immediate()));
+
+  bridge->WritePixels(SkImageInfo::MakeN32Premul(10, 10), nullptr, 10, 0, 0);
+  EXPECT_EQ(1u, TestSingleThreadTaskRunner::RunAll(task_runner->immediate()));
+
+  // No hibernation, read happened in-between.
+  EXPECT_FALSE(bridge->IsHibernating());
+  EXPECT_FALSE(handler.is_encoded());
+}
+
 }  // namespace blink
diff --git a/third_party/blink/renderer/platform/graphics/compositing/paint_artifact_compositor_test.cc b/third_party/blink/renderer/platform/graphics/compositing/paint_artifact_compositor_test.cc
index 928a0e1..b974c74 100644
--- a/third_party/blink/renderer/platform/graphics/compositing/paint_artifact_compositor_test.cc
+++ b/third_party/blink/renderer/platform/graphics/compositing/paint_artifact_compositor_test.cc
@@ -451,7 +451,8 @@
         CreateTransform(*transform1, MakeRotationMatrix(0, 45, 0));
     TransformPaintPropertyNode::State transform3_state{
         {MakeRotationMatrix(0, 45, 0)}};
-    transform3_state.flattens_inherited_transform = transform_is_flattened;
+    transform3_state.flags.flattens_inherited_transform =
+        transform_is_flattened;
     auto transform3 = TransformPaintPropertyNode::Create(
         *transform2, std::move(transform3_state));
 
@@ -504,7 +505,8 @@
     auto transform2 = TransformPaintPropertyNodeAlias::Create(*real_transform2);
     TransformPaintPropertyNode::State transform3_state{
         {MakeRotationMatrix(0, 45, 0)}};
-    transform3_state.flattens_inherited_transform = transform_is_flattened;
+    transform3_state.flags.flattens_inherited_transform =
+        transform_is_flattened;
     auto real_transform3 = TransformPaintPropertyNode::Create(
         *transform2, std::move(transform3_state));
     auto transform3 = TransformPaintPropertyNodeAlias::Create(*real_transform3);
@@ -3757,7 +3759,7 @@
 TEST_P(PaintArtifactCompositorTest, CreatesViewportNodes) {
   auto matrix = MakeScaleMatrix(2);
   TransformPaintPropertyNode::State transform_state{{matrix}};
-  transform_state.in_subtree_of_page_scale = false;
+  transform_state.flags.in_subtree_of_page_scale = false;
   const CompositorElementId compositor_element_id =
       CompositorElementIdFromUniqueObjectId(1);
   transform_state.compositor_element_id = compositor_element_id;
@@ -3782,12 +3784,12 @@
 // the page scale transform node or ancestors, and is set on descendants.
 TEST_P(PaintArtifactCompositorTest, InSubtreeOfPageScale) {
   TransformPaintPropertyNode::State ancestor_transform_state;
-  ancestor_transform_state.in_subtree_of_page_scale = false;
+  ancestor_transform_state.flags.in_subtree_of_page_scale = false;
   auto ancestor_transform = TransformPaintPropertyNode::Create(
       TransformPaintPropertyNode::Root(), std::move(ancestor_transform_state));
 
   TransformPaintPropertyNode::State page_scale_transform_state;
-  page_scale_transform_state.in_subtree_of_page_scale = false;
+  page_scale_transform_state.flags.in_subtree_of_page_scale = false;
   const CompositorElementId page_scale_compositor_element_id =
       CompositorElementIdFromUniqueObjectId(1);
   page_scale_transform_state.compositor_element_id =
@@ -3800,7 +3802,7 @@
       CompositorElementIdFromUniqueObjectId(2);
   descendant_transform_state.compositor_element_id =
       descendant_compositor_element_id;
-  descendant_transform_state.in_subtree_of_page_scale = true;
+  descendant_transform_state.flags.in_subtree_of_page_scale = true;
   descendant_transform_state.direct_compositing_reasons =
       CompositingReason::kWillChangeTransform;
   auto descendant_transform = TransformPaintPropertyNode::Create(
@@ -3837,7 +3839,7 @@
 TEST_P(PaintArtifactCompositorTest, ViewportPageScale) {
   // Create a page scale transform node with a page scale factor of 2.0.
   TransformPaintPropertyNode::State transform_state{{MakeScaleMatrix(2)}};
-  transform_state.in_subtree_of_page_scale = false;
+  transform_state.flags.in_subtree_of_page_scale = false;
   transform_state.compositor_element_id =
       CompositorElementIdFromUniqueObjectId(1);
   auto scale_transform_node = TransformPaintPropertyNode::Create(
diff --git a/third_party/blink/renderer/platform/graphics/compositing/property_tree_manager.cc b/third_party/blink/renderer/platform/graphics/compositing/property_tree_manager.cc
index f07c1ba..cb71308 100644
--- a/third_party/blink/renderer/platform/graphics/compositing/property_tree_manager.cc
+++ b/third_party/blink/renderer/platform/graphics/compositing/property_tree_manager.cc
@@ -985,7 +985,7 @@
         clip_id = EnsureCompositorClipNode(*clip);
       }
       // For non-trivial clip, isolation_effect.stable_id will be assigned later
-      // when the effect is closed. For now the default value kInvalidStableId
+      // when the effect is closed. For now the default value INVALID_STABLE_ID
       // is used. See PropertyTreeManager::EmitClipMaskLayer().
       if (SupportsShaderBasedRoundedCorner(*pending_clip.clip,
                                            pending_clip.type, next_effect)) {
@@ -1116,7 +1116,7 @@
   }
 
   if (has_multiple_groups) {
-    if (effect_node.stable_id != cc::EffectNode::kInvalidStableId) {
+    if (effect_node.stable_id != cc::EffectNode::INVALID_STABLE_ID) {
       // We are creating more than one cc effect nodes for one blink effect.
       // Give the extra cc effect node a unique stable id.
       effect_node.stable_id =
diff --git a/third_party/blink/renderer/platform/graphics/paint/geometry_mapper_test.cc b/third_party/blink/renderer/platform/graphics/paint/geometry_mapper_test.cc
index 28b64a7..f7f30ff 100644
--- a/third_party/blink/renderer/platform/graphics/paint/geometry_mapper_test.cc
+++ b/third_party/blink/renderer/platform/graphics/paint/geometry_mapper_test.cc
@@ -356,7 +356,7 @@
 
   auto inverse_rotate_transform = MakeRotationMatrix(-45, 0, 0);
   TransformPaintPropertyNode::State inverse_state{{inverse_rotate_transform}};
-  inverse_state.flattens_inherited_transform = true;
+  inverse_state.flags.flattens_inherited_transform = true;
   auto transform2 =
       TransformPaintPropertyNode::Create(*transform1, std::move(inverse_state));
   local_state.SetTransform(*transform2);
diff --git a/third_party/blink/renderer/platform/graphics/paint/transform_paint_property_node.cc b/third_party/blink/renderer/platform/graphics/paint/transform_paint_property_node.cc
index 6661a9e..ff7fe6e 100644
--- a/third_party/blink/renderer/platform/graphics/paint/transform_paint_property_node.cc
+++ b/third_party/blink/renderer/platform/graphics/paint/transform_paint_property_node.cc
@@ -68,18 +68,20 @@
     const State& other,
     const AnimationState& animation_state) const {
   // Whether or not a node is considered a frame root should be invariant.
-  DCHECK_EQ(is_frame_paint_offset_translation,
-            other.is_frame_paint_offset_translation);
+  DCHECK_EQ(flags.is_frame_paint_offset_translation,
+            other.flags.is_frame_paint_offset_translation);
 
   // Changes other than compositing reason and the transform are not simple.
-  if (flattens_inherited_transform != other.flattens_inherited_transform ||
-      in_subtree_of_page_scale != other.in_subtree_of_page_scale ||
-      animation_is_axis_aligned != other.animation_is_axis_aligned ||
-      delegates_to_parent_for_backface !=
-          other.delegates_to_parent_for_backface ||
-      is_frame_paint_offset_translation !=
-          other.is_frame_paint_offset_translation ||
-      is_for_svg_child != other.is_for_svg_child ||
+  if (flags.flattens_inherited_transform !=
+          other.flags.flattens_inherited_transform ||
+      flags.in_subtree_of_page_scale != other.flags.in_subtree_of_page_scale ||
+      flags.animation_is_axis_aligned !=
+          other.flags.animation_is_axis_aligned ||
+      flags.delegates_to_parent_for_backface !=
+          other.flags.delegates_to_parent_for_backface ||
+      flags.is_frame_paint_offset_translation !=
+          other.flags.is_frame_paint_offset_translation ||
+      flags.is_for_svg_child != other.flags.is_for_svg_child ||
       backface_visibility != other.backface_visibility ||
       rendering_context_id != other.rendering_context_id ||
       compositor_element_id != other.compositor_element_id ||
@@ -129,13 +131,14 @@
 // The root of the transform tree. The root transform node references the root
 // scroll node.
 const TransformPaintPropertyNode& TransformPaintPropertyNode::Root() {
-  DEFINE_STATIC_REF(TransformPaintPropertyNode, root,
-                    base::AdoptRef(new TransformPaintPropertyNode(
-                        nullptr, State{{},
-                                       &ScrollPaintPropertyNode::Root(),
-                                       nullptr,
-                                       false /* flattens_inherited_transform */,
-                                       false /* in_subtree_of_page_scale */})));
+  DEFINE_STATIC_REF(
+      TransformPaintPropertyNode, root,
+      base::AdoptRef(new TransformPaintPropertyNode(
+          nullptr, State{{},
+                         &ScrollPaintPropertyNode::Root(),
+                         nullptr,
+                         State::Flags{false /* flattens_inherited_transform */,
+                                      false /* in_subtree_of_page_scale */}})));
   return *root;
 }
 
@@ -166,12 +169,10 @@
     json->SetString("matrix", matrix.Replace("\n", ", "));
     json->SetString("origin", String(Origin().ToString()));
   }
-  if (!state_.flattens_inherited_transform) {
+  if (!state_.flags.flattens_inherited_transform)
     json->SetBoolean("flattensInheritedTransform", false);
-  }
-  if (!state_.in_subtree_of_page_scale) {
+  if (!state_.flags.in_subtree_of_page_scale)
     json->SetBoolean("in_subtree_of_page_scale", false);
-  }
   if (state_.backface_visibility != BackfaceVisibility::kInherited) {
     json->SetString("backface",
                     state_.backface_visibility == BackfaceVisibility::kVisible
diff --git a/third_party/blink/renderer/platform/graphics/paint/transform_paint_property_node.h b/third_party/blink/renderer/platform/graphics/paint/transform_paint_property_node.h
index bb7ab651..a2a609c 100644
--- a/third_party/blink/renderer/platform/graphics/paint/transform_paint_property_node.h
+++ b/third_party/blink/renderer/platform/graphics/paint/transform_paint_property_node.h
@@ -103,13 +103,21 @@
     scoped_refptr<const ScrollPaintPropertyNode> scroll;
     scoped_refptr<const TransformPaintPropertyNode>
         scroll_translation_for_fixed;
-    bool flattens_inherited_transform : 1 = false;
-    bool in_subtree_of_page_scale : 1 = true;
-    bool animation_is_axis_aligned : 1 = false;
-    bool delegates_to_parent_for_backface : 1 = false;
-    // Set if a frame is rooted at this node.
-    bool is_frame_paint_offset_translation : 1 = false;
-    bool is_for_svg_child : 1 = false;
+
+    // Use bitfield packing instead of separate bools to save space.
+    struct Flags {
+      DISALLOW_NEW();
+
+     public:
+      bool flattens_inherited_transform : 1;
+      bool in_subtree_of_page_scale : 1;
+      bool animation_is_axis_aligned : 1;
+      bool delegates_to_parent_for_backface : 1;
+      // Set if a frame is rooted at this node.
+      bool is_frame_paint_offset_translation : 1;
+      bool is_for_svg_child : 1;
+    } flags = {false, true, false, false, false, false};
+
     BackfaceVisibility backface_visibility = BackfaceVisibility::kInherited;
     unsigned rendering_context_id = 0;
     CompositingReasons direct_compositing_reasons = CompositingReason::kNone;
@@ -214,7 +222,7 @@
   // If true, this node is a descendant of the page scale transform. This is
   // important for avoiding raster during pinch-zoom (see: crbug.com/951861).
   bool IsInSubtreeOfPageScale() const {
-    return state_.in_subtree_of_page_scale;
+    return state_.flags.in_subtree_of_page_scale;
   }
 
   const CompositorStickyConstraint* GetStickyConstraint() const {
@@ -242,7 +250,7 @@
   // the plane of its parent. This is implemented by flattening the total
   // accumulated transform from its ancestors.
   bool FlattensInheritedTransform() const {
-    return state_.flattens_inherited_transform;
+    return state_.flags.flattens_inherited_transform;
   }
 
   // Returns the local BackfaceVisibility value set on this node. To be used
@@ -274,8 +282,8 @@
   bool FlattensInheritedTransformSameAsParent() const {
     if (IsRoot())
       return true;
-    return state_.flattens_inherited_transform ==
-           Parent()->Unalias().state_.flattens_inherited_transform;
+    return state_.flags.flattens_inherited_transform ==
+           Parent()->Unalias().state_.flags.flattens_inherited_transform;
   }
 
   bool HasDirectCompositingReasons() const {
@@ -314,7 +322,7 @@
   }
 
   bool TransformAnimationIsAxisAligned() const {
-    return state_.animation_is_axis_aligned;
+    return state_.flags.animation_is_axis_aligned;
   }
 
   bool RequiresCompositingForRootScroller() const {
@@ -344,11 +352,11 @@
   }
 
   bool IsFramePaintOffsetTranslation() const {
-    return state_.is_frame_paint_offset_translation;
+    return state_.flags.is_frame_paint_offset_translation;
   }
 
   bool DelegatesToParentForBackface() const {
-    return state_.delegates_to_parent_for_backface;
+    return state_.flags.delegates_to_parent_for_backface;
   }
 
   // Content whose transform nodes have a common rendering context ID are 3D
@@ -356,7 +364,7 @@
   unsigned RenderingContextId() const { return state_.rendering_context_id; }
   bool HasRenderingContext() const { return state_.rendering_context_id; }
 
-  bool IsForSVGChild() const { return state_.is_for_svg_child; }
+  bool IsForSVGChild() const { return state_.flags.is_for_svg_child; }
 
   std::unique_ptr<JSONObject> ToJSON() const;
 
diff --git a/third_party/blink/renderer/platform/loader/fetch/url_loader/request_conversion.cc b/third_party/blink/renderer/platform/loader/fetch/url_loader/request_conversion.cc
index 338c01d..03d26352 100644
--- a/third_party/blink/renderer/platform/loader/fetch/url_loader/request_conversion.cc
+++ b/third_party/blink/renderer/platform/loader/fetch/url_loader/request_conversion.cc
@@ -400,6 +400,8 @@
     dest->do_not_prompt_for_login = true;
     dest->load_flags |= net::LOAD_DO_NOT_USE_EMBEDDED_IDENTITY;
   }
+
+  dest->has_storage_access = src.GetHasStorageAccess();
 }
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/platform/runtime_enabled_features.json5 b/third_party/blink/renderer/platform/runtime_enabled_features.json5
index fdf6f1a..a0c915e 100644
--- a/third_party/blink/renderer/platform/runtime_enabled_features.json5
+++ b/third_party/blink/renderer/platform/runtime_enabled_features.json5
@@ -2109,6 +2109,12 @@
       public: true,
     },
     {
+      name: "NavigateEventCancelableTraversals",
+      status: "experimental",
+      base_feature: "NavigateEventCancelableTraversals",
+      base_feature_status: "enabled",
+    },
+    {
       name: "NavigationId",
       status: "experimental",
       origin_trial_feature_name: "SoftNavigationHeuristics",
diff --git a/third_party/blink/renderer/platform/scheduler/public/main_thread.h b/third_party/blink/renderer/platform/scheduler/public/main_thread.h
index f6a6ac4..ad2ae3f0 100644
--- a/third_party/blink/renderer/platform/scheduler/public/main_thread.h
+++ b/third_party/blink/renderer/platform/scheduler/public/main_thread.h
@@ -33,6 +33,7 @@
   friend class WebGLWebCodecsVideoFrame;
   friend class WebRtcVideoFrameAdapter;
   friend class WorkerGlobalScope;
+  friend class HibernationHandler;
   friend MainThreadTaskRunnerRestricted AccessMainThreadForGpuFactories();
   friend MainThreadTaskRunnerRestricted
   AccessMainThreadForWebGraphicsContext3DProvider();
diff --git a/third_party/blink/renderer/platform/widget/input/input_handler_proxy_unittest.cc b/third_party/blink/renderer/platform/widget/input/input_handler_proxy_unittest.cc
index 04b03d4..ed5b2d6 100644
--- a/third_party/blink/renderer/platform/widget/input/input_handler_proxy_unittest.cc
+++ b/third_party/blink/renderer/platform/widget/input/input_handler_proxy_unittest.cc
@@ -3179,6 +3179,11 @@
 }
 
 TEST_F(InputHandlerProxyEventQueueTest, CoalescedEventSwitchToMainThread) {
+  // After scroll unification, we do not handle scrolls on the main thread.
+  if (base::FeatureList::IsEnabled(features::kScrollUnification)) {
+    return;
+  }
+
   cc::InputHandlerScrollResult scroll_result_did_scroll_;
   cc::InputHandlerScrollResult scroll_result_did_not_scroll_;
   scroll_result_did_scroll_.did_scroll = true;
diff --git a/third_party/blink/web_tests/TestExpectations b/third_party/blink/web_tests/TestExpectations
index de2e7da9..522270c 100644
--- a/third_party/blink/web_tests/TestExpectations
+++ b/third_party/blink/web_tests/TestExpectations
@@ -915,6 +915,9 @@
 
 crbug.com/922249 virtual/android/fullscreen/compositor-touch-hit-rects-fullscreen-video-controls.html [ Failure Pass ]
 
+# Fractional paint offsets lead to 1px bleed with position sticky.
+crbug.com/1136079 external/wpt/css/css-position/sticky/position-sticky-fractional-offset.html [ Failure ]
+
 # ====== Layout team owned tests from here ======
 
 crbug.com/711704 external/wpt/css/CSS2/floats/floats-rule3-outside-left-002.xht [ Failure ]
diff --git a/third_party/blink/web_tests/accessibility/aria-hidden-hides-all-elements.html b/third_party/blink/web_tests/accessibility/aria-hidden-hides-all-elements.html
index 07ba441..b31d4ce 100644
--- a/third_party/blink/web_tests/accessibility/aria-hidden-hides-all-elements.html
+++ b/third_party/blink/web_tests/accessibility/aria-hidden-hides-all-elements.html
@@ -43,19 +43,19 @@
     var content = accessibilityController.accessibleElementById("main");
     assert_equals(content.childrenCount, 8);
     assert_true(content.isIgnored);
-    expectUnignoredChildren(content, [0, 1, 2, 3, 6]);
+    expectUnignoredChildren(content, [1, 2, 3, 6]);
 
     document.getElementById("ul").tabIndex = 0;
     assert_equals(content.childrenCount, 8,
                       "Making list focusable should make it unignored");
     assert_true(content.isIgnored);
-    expectUnignoredChildren(content, [0, 1, 2, 3, 5, 6]);
+    expectUnignoredChildren(content, [1, 2, 3, 5, 6]);
 
     document.getElementById("ul").removeAttribute("tabindex");
     assert_equals(content.childrenCount, 8,
                           "Making list unfocusable should make it ignored again");
     assert_true(content.isIgnored);
-    expectUnignoredChildren(content, [0, 1, 2, 3, 6]);
+    expectUnignoredChildren(content, [1, 2, 3, 6]);
 });
 </script>
 
diff --git a/third_party/blink/web_tests/external/wpt/css/css-position/sticky/position-sticky-fractional-offset-ref.html b/third_party/blink/web_tests/external/wpt/css/css-position/sticky/position-sticky-fractional-offset-ref.html
new file mode 100644
index 0000000..8b7a1f8
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-position/sticky/position-sticky-fractional-offset-ref.html
@@ -0,0 +1,40 @@
+<!DOCTYPE html>
+<style>
+  .container {
+    width: 100px;
+    height: 100px;
+    overflow-y: scroll;
+    background: lightgreen;
+    display: inline-block;
+  }
+</style>
+
+<div class="container">
+  <div style="height: calc(300px + 50px + 10.10px);"></div>
+</div>
+
+<div class="container">
+  <div style="height: calc(300px + 50px + 10.25px);"></div>
+</div>
+
+<div class="container">
+  <div style="height: calc(300px + 50px + 10.50px);"></div>
+</div>
+
+<div class="container">
+  <div style="height: calc(300px + 50px + 10.75px);"></div>
+</div>
+
+<div class="container">
+  <div style="height: calc(300px + 50px + 10.90px);"></div>
+</div>
+
+<script>
+  window.onload = function() {
+    var containers = document.getElementsByClassName('container');
+    for (let i = 0; i < containers.length; i++) {
+      containers[i].scrollTo(0, 20);
+    }
+  };
+</script>
+</html>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-position/sticky/position-sticky-fractional-offset.html b/third_party/blink/web_tests/external/wpt/css/css-position/sticky/position-sticky-fractional-offset.html
new file mode 100644
index 0000000..79c29f4
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-position/sticky/position-sticky-fractional-offset.html
@@ -0,0 +1,80 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+<link rel="help" href="https://www.w3.org/TR/css-position-3/#sticky-pos" />
+<meta name="assert" content="Position sticky with a fractional offset should not show a gap" />
+<link rel="match" href="position-sticky-fractional-offset-ref.html" />
+
+<style>
+  .sticky-container {
+    width: 100px;
+    height: 100px;
+    overflow-y: scroll;
+    background: red;
+    display: inline-block;
+  }
+
+  .sticky {
+    position: sticky;
+    top: 0;
+    height: 50px;
+    background: lightgreen;
+  }
+
+  .force-scroll {
+    height: 300px;
+    background: lightgreen;
+  }
+</style>
+
+<div class="sticky-container">
+  <div style="height: 10.10px;"></div>
+  <div class="sticky"></div>
+  <div class="force-scroll"></div>
+</div>
+
+<div class="sticky-container">
+  <div style="height: 10.25px;"></div>
+  <div class="sticky"></div>
+  <div class="force-scroll"></div>
+</div>
+
+<div class="sticky-container">
+  <div style="height: 10.50px;"></div>
+  <div class="sticky"></div>
+  <div class="force-scroll"></div>
+</div>
+
+<div class="sticky-container">
+  <div style="height: 10.75px;"></div>
+  <div class="sticky"></div>
+  <div class="force-scroll"></div>
+</div>
+
+<div class="sticky-container">
+  <div style="height: 10.90px;"></div>
+  <div class="sticky"></div>
+  <div class="force-scroll"></div>
+</div>
+
+<script>
+  window.onload = function() {
+    // Start with all containers scrolled to the top.
+    var containers = document.getElementsByClassName('sticky-container');
+    for (let i = 0; i < containers.length; i++) {
+      containers[i].scrollTo(0, 0);
+    }
+
+    // Wait for a full frame, then scroll all containers down so the sticky
+    // elements are stuck to the container. There should be no visible gap
+    // where the container's red background color is visible.
+    requestAnimationFrame(() => {
+      requestAnimationFrame(() => {
+        for (let i = 0; i < containers.length; i++) {
+          containers[i].scrollTo(0, 20);
+        }
+        document.documentElement.classList.remove('reftest-wait');
+      });
+    });
+  };
+</script>
+</html>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-toggle/toggle-aria-roles.tentative.html b/third_party/blink/web_tests/external/wpt/css/css-toggle/toggle-aria-roles.tentative.html
new file mode 100644
index 0000000..7c500fd
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-toggle/toggle-aria-roles.tentative.html
@@ -0,0 +1,354 @@
+<!DOCTYPE HTML>
+<meta charset="UTF-8">
+<title>CSS Toggles: ARIA roles</title>
+<link rel="author" title="L. David Baron" href="https://dbaron.org/">
+<link rel="author" title="Google" href="http://www.google.com/">
+<link rel="help" href="https://tabatkins.github.io/css-toggle/">
+<link rel="help" href="https://github.com/tabatkins/css-toggle/issues/41">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="support/toggle-helpers.js"></script>
+<style id="style"></style>
+
+<body>
+
+<div id="container"></div>
+<script>
+
+let aria_role_tests = [
+  // Markup to create the test assertions:
+  //   data-expected-role:  The expected aria role for this element.
+  //
+  // Helper markup to create more markup:
+  //   class=group: group the group with the toggle-group property
+  //   class=group-self: same, but with the self keyword (narrow scope)
+  //   class=root: create a test-role toggle with the toggle-root property
+  //   class=root-group: same, but with the 'group' keyword
+  //   class=root-self: same, but with the 'self' keyword
+  //   class=trigger: toggle-trigger to activate test-role toggle
+  //   class=visibility: toggle-visibility connected to test-role toggle
+  `
+    <div></div>
+  `,
+  `
+    <div class="root">
+      <div></div>
+    </div>
+  `,
+  `
+    <div class="root trigger" data-expected-role="checkbox"></div>
+  `,
+  // Test that ARIA attributes override the toggle inference:
+  `
+    <div class="root trigger" role="link" data-expected-role="link"></div>
+  `,
+  `
+    <div class="root">
+      <div class="trigger" data-expected-role="button"></div>
+    </div>
+  `,
+
+  // Radios and radio groups:
+  `
+    <div class="group" data-expected-role="radiogroup">
+      <div class="root-group trigger" data-expected-role="radio"></div>
+    </div>
+  `,
+  `
+    <div class="group" data-expected-role="radiogroup">
+      <div class="root-group trigger" data-expected-role="radio"></div>
+      <div class="root-group trigger" data-expected-role="radio"></div>
+    </div>
+  `,
+  `
+    <div>
+      <div class="root-group trigger" data-expected-role="radio"></div>
+    </div>
+  `,
+  `
+    <div style="toggle-group: another-group">
+      <div class="root-group trigger" data-expected-role="radio"></div>
+    </div>
+  `,
+  `
+    <div style="toggle-group: another-group, test-role, third-group" data-expected-role="radiogroup">
+      <div class="root-group trigger" data-expected-role="radio"></div>
+    </div>
+  `,
+
+
+  // Checkboxes and checkbox groups:
+  `
+    <div>
+      <div class="root trigger" data-expected-role="checkbox"></div>
+    </div>
+  `,
+  // TODO(dbaron): This is a checkbox group... but we can't distinguish
+  // that with current ARIA roles.
+  `
+    <div>
+      <div class="root trigger" data-expected-role="checkbox"></div>
+      <div class="root trigger" data-expected-role="checkbox"></div>
+    </div>
+  `,
+
+  // Disclosure:
+  // TODO(dbaron): This is a disclosure... but how is it possible to
+  // distinguish with ARIA roles (compare to next test!)?
+  `
+    <div class="root">
+      <div class="trigger" data-expected-role="button"></div>
+      <div class="visibility"></div>
+    </div>
+  `,
+  // This is not a disclosure because it has a toggle-group.
+  `
+    <div class="root-group">
+      <div class="trigger" data-expected-role="button"></div>
+      <div class="visibility"></div>
+    </div>
+  `,
+  // This is button with popup (absolute positioning)
+  // TODO(dbaron): This test doesn't actually distinguish this from
+  // disclosure because the internal kPopUpButton role maps to "button"
+  // in kReverseRoles in ax_object.cc.
+  `
+    <div class="root">
+      <div class="trigger" data-expected-role="button"></div>
+      <div class="visibility" style="position: absolute"></div>
+    </div>
+  `,
+  // This is button with popup (fixed positioning)
+  // TODO(dbaron): This test doesn't actually distinguish this from
+  // disclosure because the internal kPopUpButton role maps to "button"
+  // in kReverseRoles in ax_object.cc.
+  `
+    <div class="root">
+      <div class="trigger" data-expected-role="button"></div>
+      <div class="visibility" style="position: fixed"></div>
+    </div>
+  `,
+  // This is button with popup (popover)
+  // TODO(dbaron): This test doesn't actually distinguish this from
+  // disclosure because the internal kPopUpButton role maps to "button"
+  // in kReverseRoles in ax_object.cc.
+  `
+    <div class="root">
+      <div class="trigger" data-expected-role="button"></div>
+      <div class="visibility" popover="auto"></div>
+    </div>
+  `,
+  // This is disclosure (NOT button with popup) (sticky positioning)
+  `
+    <div class="root">
+      <div class="trigger" data-expected-role="button"></div>
+      <div class="visibility" style="position: sticky"></div>
+    </div>
+  `,
+
+  // Accordion:
+  `
+    <div class="group">
+      <div class="root-group" data-expected-role="region">
+        <div class="trigger" data-expected-role="button"></div>
+        <div class="visibility"></div>
+      </div>
+      <div class="root-group" data-expected-role="region">
+        <div class="trigger" data-expected-role="button"></div>
+        <div class="visibility"></div>
+      </div>
+    </div>
+  `,
+  // Not accordion because of other siblings:
+  `
+    <div class="group">
+      <div class="root-group">
+        <div class="trigger" data-expected-role="button"></div>
+        <div class="visibility"></div>
+      </div>
+      <div class="root-group">
+        <div class="trigger" data-expected-role="button"></div>
+        <div class="visibility"></div>
+      </div>
+      <div></div>
+      <div></div>
+      <div></div>
+    </div>
+  `,
+
+  // Tree:
+  // TODO(dbaron): This should probably also work with the toggles on
+  // the <button>!
+  // TODO(dbaron): This should probably mark the non-interactive items
+  // as treeitem as well!
+  // TODO(dbaron): Do the elements getting the roles here make sense?
+  // TODO(dbaron): The requirement for having multiple disclosure-ish
+  // children to qualify as accordion-ish probably doesn't make sense
+  // here.  The test below is basically the minimal example that gets
+  // detected as a tree, but simpler things definitely should be!
+  // TODO(dbaron): The inner parts of the tree should also be getting
+  // tree roles!
+  `
+    <ul data-expected-role="tree">
+      <li class="root-self" data-expected-role="group">
+        <button class="trigger" data-expected-role="treeitem"></button>
+        <ul class="visibility" data-expected-role="list">
+          <li>item</li>
+          <li class="root-self">
+            <button class="trigger" data-expected-role="button"></button>
+            <ul class="visibility" data-expected-role="list">
+              <li>item</li>
+              <li>item</li>
+            </ul>
+          </li>
+          <li class="root-self">
+            <button class="trigger" data-expected-role="button"></button>
+            <ul class="visibility" data-expected-role="list">
+              <li>item</li>
+              <li>item</li>
+            </ul>
+          </li>
+        </ul>
+      </li>
+      <li class="root-self" data-expected-role="group">
+        <button class="trigger" data-expected-role="treeitem"></button>
+        <ul class="visibility" data-expected-role="list">
+          <li class="root-self">
+            <button class="trigger" data-expected-role="button"></button>
+            <ul class="visibility" data-expected-role="list">
+              <li>item</li>
+              <li>item</li>
+            </ul>
+          </li>
+          <li class="root-self">
+            <button class="trigger" data-expected-role="button"></button>
+            <ul class="visibility" data-expected-role="list">
+              <li>item</li>
+              <li>item</li>
+            </ul>
+          </li>
+        </ul>
+      </li>
+    </ul>
+  `,
+
+  // Tabs:
+  `
+    <section class="group" data-expected-role="tablist">
+      <h1 class="root-group trigger" data-expected-role="tab"></h1>
+      <div class="visibility" data-expected-role="tabpanel"></div>
+      <h1 class="root-group trigger" data-expected-role="tab"></h1>
+      <div class="visibility" data-expected-role="tabpanel"></div>
+      <h1 class="root-group trigger" data-expected-role="tab"></h1>
+      <div class="visibility" data-expected-role="tabpanel"></div>
+    </section>
+  `,
+  `
+    <section class="group" data-expected-role="tablist">
+      <h1 class="root-group trigger" data-expected-role="tab"></h1>
+      <div class="visibility" data-expected-role="tabpanel"></div>
+      <h1 class="root-group trigger" data-expected-role="tab"></h1>
+      <div class="visibility" data-expected-role="tabpanel"></div>
+      <div></div>
+    </section>
+  `,
+  `
+    <section class="group" data-expected-role="tablist">
+      <h1 class="root-group trigger" data-expected-role="tab"></h1>
+      <div class="visibility" data-expected-role="tabpanel"></div>
+      <h1 class="root-group trigger" data-expected-role="tab"></h1>
+      <div class="visibility" data-expected-role="tabpanel"></div>
+      <h1 style="toggle-root: other-toggle; toggle-trigger: other-toggle" data-expected-role="checkbox"></h1>
+    </section>
+  `,
+  `
+    <section class="group" data-expected-role="tablist">
+      <h1 class="root-group trigger" data-expected-role="tab"></h1>
+      <div class="visibility" data-expected-role="tabpanel"></div>
+      <h1 class="root-group trigger" data-expected-role="tab"></h1>
+      <div class="visibility" data-expected-role="tabpanel"></div>
+      <h1 style="toggle-root: other-toggle; toggle-trigger: other-toggle" data-expected-role="button"></h1>
+      <div style="toggle-visibility: toggle other-toggle"></div>
+    </section>
+  `,
+  // TODO(https://crbug.com/758089): The expected role for the <section>
+  // should be generic rather than null!
+  `
+    <section class="group" data-expected-role="null">
+      <h1 class="root-group trigger" data-expected-role="button"></h1>
+      <div class="visibility"></div>
+      <h1 class="root-group trigger" data-expected-role="button"></h1>
+      <div class="visibility"></div>
+      <div></div>
+      <div></div>
+      <div></div>
+      <div></div>
+    </section>
+  `,
+  `
+    <section class="group" data-expected-role="radiogroup">
+      <h1 class="root-group trigger" data-expected-role="radio"></h1>
+      <h1 class="root-group trigger" data-expected-role="radio"></h1>
+      <div></div>
+      <div></div>
+      <div></div>
+      <div></div>
+    </section>
+  `,
+];
+
+for (let t of aria_role_tests) {
+  promise_test(async function() {
+    container.innerHTML = t;
+
+    for (let e of container.querySelectorAll('.group')) {
+      e.style.toggleGroup = "test-role";
+    }
+    for (let e of container.querySelectorAll('.group-self')) {
+      e.style.toggleGroup = "test-role self";
+    }
+    for (let e of container.querySelectorAll('.root')) {
+      e.style.toggleRoot = "test-role";
+    }
+    for (let e of container.querySelectorAll('.root-group')) {
+      e.style.toggleRoot = "test-role group";
+    }
+    for (let e of container.querySelectorAll('.root-self')) {
+      e.style.toggleRoot = "test-role self";
+    }
+    for (let e of container.querySelectorAll('.trigger')) {
+      e.style.toggleTrigger = "test-role";
+    }
+    for (let e of container.querySelectorAll('.visibility')) {
+      e.style.toggleVisibility = "toggle test-role";
+    }
+
+    for (let e of container.querySelectorAll('.root, .root-nogroup')) {
+      await wait_for_toggle_creation(e);
+    }
+
+    let count = 0;
+    for (let e of container.querySelectorAll("*")) {
+      if (e == container)
+        continue;
+
+      let expected_role = "generic";
+      if (e.hasAttribute("data-expected-role")) {
+        expected_role = e.getAttribute("data-expected-role");
+        // TODO(https://crbug.com/758089): See above regarding <section>;
+        // this null handling should eventually be removed.
+        if (expected_role === "null") {
+          expected_role = null;
+        }
+      }
+      ++count;
+      // NOTE:  This relies on Element.computedRole, which is an
+      // experimental feature behind the ComputedAccessibilityInfo flag
+      // in blink.
+      assert_equals(e.computedRole, expected_role, `role on ${e.tagName} element (#${count})`);
+    }
+
+  }, `aria role test: ${t}`);
+}
+
+</script>
diff --git a/third_party/blink/web_tests/external/wpt/fullscreen/rendering/fullscreen-pseudo-class-expected.txt b/third_party/blink/web_tests/external/wpt/fullscreen/rendering/fullscreen-pseudo-class-expected.txt
deleted file mode 100644
index ff07788..0000000
--- a/third_party/blink/web_tests/external/wpt/fullscreen/rendering/fullscreen-pseudo-class-expected.txt
+++ /dev/null
@@ -1,4 +0,0 @@
-This is a testharness.js-based test.
-FAIL :fullscreen pseudo-class assert_true: outer:fullscreen in nested fullscreen expected true got false
-Harness: the test ran to completion.
-
diff --git a/third_party/blink/web_tests/external/wpt/html/semantics/popovers/popover-events.html b/third_party/blink/web_tests/external/wpt/html/semantics/popovers/popover-events.html
index b2994245..b0df2f2 100644
--- a/third_party/blink/web_tests/external/wpt/html/semantics/popovers/popover-events.html
+++ b/third_party/blink/web_tests/external/wpt/html/semantics/popovers/popover-events.html
@@ -20,7 +20,7 @@
 window.onload = () => {
   for(const method of ["listener","attribute"]) {
     promise_test(async t => {
-      const popover = document.querySelector('[popover]');
+      const {popover,signal} = getPopoverAndSignal(t);
       assert_false(popover.matches(':open'));
       let showCount = 0;
       let afterShowCount = 0;
@@ -59,10 +59,9 @@
       };
       switch (method) {
         case "listener":
-          const {signal} = getPopoverAndSignal(t);
-          // These events bubble.
-          document.addEventListener('beforetoggle', listener, {signal});
-          document.addEventListener('toggle', listener, {signal});
+          // These events do *not* bubble.
+          popover.addEventListener('beforetoggle', listener, {signal});
+          popover.addEventListener('toggle', listener, {signal});
           break;
         case "attribute":
           assert_false(popover.hasAttribute('onbeforetoggle'));
diff --git a/third_party/blink/web_tests/external/wpt/html/semantics/popovers/popover-top-layer-interactions.html b/third_party/blink/web_tests/external/wpt/html/semantics/popovers/popover-top-layer-interactions.html
index 50a21be..5cd4d4c 100644
--- a/third_party/blink/web_tests/external/wpt/html/semantics/popovers/popover-top-layer-interactions.html
+++ b/third_party/blink/web_tests/external/wpt/html/semantics/popovers/popover-top-layer-interactions.html
@@ -35,10 +35,10 @@
   },
   {
     type: types.fullscreen,
-    closes: [types.popover, types.fullscreen],
+    closes: [types.popover],
     createElement: () => document.createElement('div'),
     trigger: async function(visibleElement) {assert_false(this.isTopLayer());await blessTopLayer(visibleElement);await this.element.requestFullscreen();},
-    close: function() {assert_equals(this.element,document.fullscreenElement); document.exitFullscreen();},
+    close: function() {document.exitFullscreen();},
     isTopLayer: function() {return this.element.matches(':fullscreen');},
   },
 ];
diff --git a/third_party/blink/web_tests/external/wpt/navigation-api/navigate-event/navigate-history-back-after-fragment.html b/third_party/blink/web_tests/external/wpt/navigation-api/navigate-event/navigate-history-back-after-fragment.html
index 976754f..57a30c8 100644
--- a/third_party/blink/web_tests/external/wpt/navigation-api/navigate-event/navigate-history-back-after-fragment.html
+++ b/third_party/blink/web_tests/external/wpt/navigation-api/navigate-event/navigate-history-back-after-fragment.html
@@ -12,7 +12,7 @@
 
     navigation.onnavigate = t.step_func_done(e => {
       assert_equals(e.navigationType, "traverse");
-      assert_false(e.cancelable);
+      assert_true(e.cancelable);
       assert_true(e.canIntercept);
       assert_false(e.userInitiated);
       assert_true(e.hashChange);
diff --git a/third_party/blink/web_tests/external/wpt/navigation-api/navigate-event/navigate-history-back-after-pushState.html b/third_party/blink/web_tests/external/wpt/navigation-api/navigate-event/navigate-history-back-after-pushState.html
index 4d870fb2..bf2e6e4 100644
--- a/third_party/blink/web_tests/external/wpt/navigation-api/navigate-event/navigate-history-back-after-pushState.html
+++ b/third_party/blink/web_tests/external/wpt/navigation-api/navigate-event/navigate-history-back-after-pushState.html
@@ -12,7 +12,7 @@
 
     navigation.onnavigate = t.step_func_done(e => {
       assert_equals(e.navigationType, "traverse");
-      assert_false(e.cancelable);
+      assert_true(e.cancelable);
       assert_true(e.canIntercept);
       assert_false(e.userInitiated);
       assert_false(e.hashChange);
diff --git a/third_party/blink/web_tests/external/wpt/navigation-api/navigate-event/navigate-navigation-back-cross-document.html b/third_party/blink/web_tests/external/wpt/navigation-api/navigate-event/navigate-navigation-back-cross-document.html
index 214644066..2e1adbe 100644
--- a/third_party/blink/web_tests/external/wpt/navigation-api/navigate-event/navigate-navigation-back-cross-document.html
+++ b/third_party/blink/web_tests/external/wpt/navigation-api/navigate-event/navigate-navigation-back-cross-document.html
@@ -9,7 +9,9 @@
     let target_id = i.contentWindow.navigation.currentEntry.id;
     i.contentWindow.navigation.navigate("?foo");
     i.onload = t.step_func(() => {
+      let beforeunload_called = false;
       i.contentWindow.navigation.onnavigate = t.step_func_done(e => {
+        assert_true(beforeunload_called);
         assert_equals(e.navigationType, "traverse");
         assert_false(e.cancelable);
         assert_false(e.canIntercept);
@@ -24,6 +26,7 @@
         assert_equals(e.formData, null);
         assert_equals(e.info, "hi");
       });
+      i.contentWindow.onbeforeunload = () => beforeunload_called = true;
       assert_true(i.contentWindow.navigation.canGoBack);
       i.contentWindow.navigation.back({ info: "hi" });
     })
diff --git a/third_party/blink/web_tests/external/wpt/navigation-api/navigate-event/navigate-navigation-back-same-document-in-iframe.html b/third_party/blink/web_tests/external/wpt/navigation-api/navigate-event/navigate-navigation-back-same-document-in-iframe.html
new file mode 100644
index 0000000..cebd2f36
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/navigation-api/navigate-event/navigate-navigation-back-same-document-in-iframe.html
@@ -0,0 +1,33 @@
+<!doctype html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<iframe id="i" src="/common/blank.html"></iframe>
+<script>
+promise_test(async t => {
+  // Wait for after the load event so that the navigation doesn't get converted
+  // into a replace navigation.
+  await new Promise(resolve => window.onload = () => t.step_timeout(resolve, 0));
+
+  let target_key = i.contentWindow.navigation.currentEntry.key;
+  let target_id = i.contentWindow.navigation.currentEntry.id;
+  await i.contentWindow.navigation.navigate("#").finished;
+  assert_true(i.contentWindow.navigation.canGoBack);
+
+  i.contentWindow.navigation.onnavigate = e => {
+    assert_equals(e.navigationType, "traverse");
+    assert_false(e.cancelable, "traversals in iframes should never be cancelable");
+    assert_true(e.canIntercept);
+    assert_false(e.userInitiated);
+    assert_true(e.hashChange);
+    assert_equals(e.downloadRequest, null);
+    assert_equals(new URL(e.destination.url).hash, "");
+    assert_true(e.destination.sameDocument);
+    assert_equals(e.destination.key, target_key);
+    assert_equals(e.destination.id, target_id);
+    assert_equals(e.destination.index, 0);
+    assert_equals(e.formData, null);
+    assert_equals(e.info, "hi");
+  }
+  await i.contentWindow.navigation.back({ info: "hi" }).finished;
+}, "navigate event for navigation.back() - same-document in an iframe");
+</script>
diff --git a/third_party/blink/web_tests/external/wpt/navigation-api/navigate-event/navigate-navigation-back-same-document.html b/third_party/blink/web_tests/external/wpt/navigation-api/navigate-event/navigate-navigation-back-same-document.html
index 8753e6b1..431d384 100644
--- a/third_party/blink/web_tests/external/wpt/navigation-api/navigate-event/navigate-navigation-back-same-document.html
+++ b/third_party/blink/web_tests/external/wpt/navigation-api/navigate-event/navigate-navigation-back-same-document.html
@@ -11,7 +11,7 @@
     navigation.navigate("#foo").committed.then(t.step_func(() => {
       navigation.onnavigate = t.step_func_done(e => {
         assert_equals(e.navigationType, "traverse");
-        assert_false(e.cancelable);
+        assert_true(e.cancelable);
         assert_true(e.canIntercept);
         assert_false(e.userInitiated);
         assert_true(e.hashChange);
diff --git a/third_party/blink/web_tests/external/wpt/navigation-api/navigate-event/navigation-back-cross-document-preventDefault.html b/third_party/blink/web_tests/external/wpt/navigation-api/navigate-event/navigation-back-cross-document-preventDefault.html
new file mode 100644
index 0000000..0b5b750
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/navigation-api/navigate-event/navigation-back-cross-document-preventDefault.html
@@ -0,0 +1,33 @@
+
+<!doctype html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../navigation-methods/return-value/resources/helpers.js"></script>
+<script src="/common/get-host-info.sub.js"></script>
+<script>
+promise_test(async t => {
+  // Wait for after the load event so that the navigation doesn't get converted
+  // into a replace navigation.
+  let w = window.open("resources/opener-postMessage-onload.html");
+  await new Promise(resolve => window.onmessage = resolve);
+  // Navigate to a url that will notify us when the navigation is complete.
+  w.navigation.navigate("opener-postMessage-onload.html?1");
+
+  await new Promise(resolve => window.onmessage = resolve);
+  assert_equals(w.navigation.entries().length, 2);
+  assert_equals(w.navigation.currentEntry.index, 1);
+  let navigate_called = false;
+  w.navigation.onnavigate = t.step_func(e => {
+    navigate_called = true;
+    assert_false(e.destination.sameDocument);
+    assert_false(e.cancelable);
+    // Should do nothing.
+    e.preventDefault();
+  });
+  w.navigation.back();
+  await new Promise(resolve => window.onmessage = resolve);
+  assert_equals(w.navigation.currentEntry.index, 0);
+  assert_true(navigate_called);
+}, "navigation.back() cross-document cannot be cancelled with the navigate event");
+</script>
+
diff --git a/third_party/blink/web_tests/external/wpt/navigation-api/navigate-event/navigation-back-same-document-preventDefault.html b/third_party/blink/web_tests/external/wpt/navigation-api/navigate-event/navigation-back-same-document-preventDefault.html
new file mode 100644
index 0000000..7edb18882
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/navigation-api/navigate-event/navigation-back-same-document-preventDefault.html
@@ -0,0 +1,26 @@
+<!doctype html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../navigation-methods/return-value/resources/helpers.js"></script>
+<script>
+promise_test(async t => {
+  // Wait for after the load event so that the navigation doesn't get converted
+  // into a replace navigation.
+  await new Promise(resolve => window.onload = () => t.step_timeout(resolve, 0));
+
+  await navigation.navigate("#").finished;
+  assert_equals(navigation.entries().length, 2);
+  assert_equals(navigation.currentEntry.index, 1);
+
+  navigation.onnavigate = e => e.preventDefault();
+
+  navigation.onnavigateerror = t.step_func(e => {
+    assert_equals(e.constructor, ErrorEvent);
+    assert_equals(e.filename, location.href);
+    navigateerror_called = true;
+  });
+  await assertBothRejectDOM(t, navigation.back(), "AbortError");
+  assert_equals(navigation.currentEntry.index, 1);
+  assert_true(navigateerror_called);
+}, "navigation.back() same-document preventDefault");
+</script>
diff --git a/third_party/blink/web_tests/external/wpt/navigation-api/navigate-event/navigation-traverseTo-in-iframe-same-document-preventDefault.html b/third_party/blink/web_tests/external/wpt/navigation-api/navigate-event/navigation-traverseTo-in-iframe-same-document-preventDefault.html
new file mode 100644
index 0000000..d68b11f
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/navigation-api/navigate-event/navigation-traverseTo-in-iframe-same-document-preventDefault.html
@@ -0,0 +1,47 @@
+<!doctype html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../navigation-methods/return-value/resources/helpers.js"></script>
+<iframe id="i" src="/common/blank.html"></iframe>
+<script>
+promise_test(async t => {
+  // Wait for after the load event so that the navigation doesn't get converted
+  // into a replace navigation.
+  await new Promise(resolve => window.onload = () => t.step_timeout(resolve, 0));
+
+  // Navigate the iframe, then the top window, so that when the iframe goes back
+  // to its initial entry, the top window navigates as well.
+  await i.contentWindow.navigation.navigate("#").finished;
+  await navigation.navigate("#").finished;
+  assert_equals(navigation.entries().length, 2);
+  assert_equals(i.contentWindow.navigation.entries().length, 2);
+  assert_equals(navigation.currentEntry.index, 1);
+  assert_equals(i.contentWindow.navigation.currentEntry.index, 1);
+
+  // Ensure the top window, which is allowed to cancel the traversal, does so.
+  navigation.onnavigate = e => e.preventDefault();
+
+  let top_navigateerror_fired = false;
+  navigation.onnavigateerror = t.step_func(e => {
+    assert_equals(e.constructor, ErrorEvent);
+    assert_equals(e.filename, location.href);
+    top_navigateerror_fired = true;
+  });
+  let iframe_navigateerror_fired = false;
+  i.contentWindow.navigation.onnavigateerror = t.step_func(e => {
+    assert_equals(e.constructor, i.contentWindow.ErrorEvent);
+    assert_equals(e.filename, i.contentWindow.location.href);
+    iframe_navigateerror_fired = true;
+  });
+
+  // When the top window blocks the traversal, it should be blocked in the
+  // iframe as well, and the traversal promises in the iframe should be rejected.
+  const iWindow = i.contentWindow;
+  const iDOMException = iWindow.DOMException;
+  await assertBothRejectDOM(t, i.contentWindow.navigation.traverseTo(i.contentWindow.navigation.entries()[0].key), "AbortError", iWindow, iDOMException);
+  assert_true(top_navigateerror_fired);
+  assert_true(iframe_navigateerror_fired);
+  assert_equals(navigation.currentEntry.index, 1);
+  assert_equals(i.contentWindow.navigation.currentEntry.index, 1);
+}, "navigation.traverseTo() in an iframe with same-document preventDefault in its parent");
+</script>
diff --git a/third_party/blink/web_tests/external/wpt/navigation-api/navigate-event/navigation-traverseTo-navigates-top-and-same-doc-child-and-cross-doc-child.html b/third_party/blink/web_tests/external/wpt/navigation-api/navigate-event/navigation-traverseTo-navigates-top-and-same-doc-child-and-cross-doc-child.html
new file mode 100644
index 0000000..31cb54f
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/navigation-api/navigate-event/navigation-traverseTo-navigates-top-and-same-doc-child-and-cross-doc-child.html
@@ -0,0 +1,49 @@
+<!doctype html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<iframe id="i1" src="/common/blank.html"></iframe>
+<iframe id="i2" src="/common/blank.html"></iframe>
+<script>
+promise_test(async t => {
+  // Wait for after the load event so that the navigation doesn't get converted
+  // into a replace navigation.
+  await new Promise(resolve => window.onload = () => t.step_timeout(resolve, 0));
+  await navigation.navigate("#").finished;
+  await i1.contentWindow.navigation.navigate("#").finished;
+  i2.contentWindow.navigation.navigate("?");
+  await new Promise(resolve => i2.onload = () => t.step_timeout(resolve, 0));
+
+  assert_equals(navigation.entries().length, 2);
+  assert_equals(i1.contentWindow.navigation.entries().length, 2);
+  assert_equals(i2.contentWindow.navigation.entries().length, 2);
+  assert_equals(navigation.currentEntry.index, 1);
+  assert_equals(i1.contentWindow.navigation.currentEntry.index, 1);
+  assert_equals(i2.contentWindow.navigation.currentEntry.index, 1);
+
+  let navigate_event_count = 0;
+  navigation.onnavigate = t.step_func(e => {
+    assert_equals(navigate_event_count, 0);
+    navigate_event_count++;
+    assert_true(e.cancelable);
+  });
+  i1.contentWindow.navigation.onnavigate = t.step_func(e => {
+    assert_true(navigate_event_count > 0);
+    navigate_event_count++;
+    assert_false(e.cancelable);
+  });
+  i2.contentWindow.navigation.onnavigate = t.step_func(e => {
+    assert_true(navigate_event_count > 0);
+    navigate_event_count++;
+    assert_false(e.cancelable);
+  });
+
+  await navigation.traverseTo(navigation.entries()[0].key).finished;
+  // The top window will finish quickly, becuase it is same-document traversal.
+  // i2 will be slower because it is cross-document, so wait for its onload.
+  await new Promise(resolve => i2.onload = () => t.step_timeout(resolve, 0));
+  assert_equals(navigate_event_count, 3);
+  assert_equals(navigation.currentEntry.index, 0);
+  assert_equals(i1.contentWindow.navigation.currentEntry.index, 0);
+  assert_equals(i2.contentWindow.navigation.currentEntry.index, 0);
+}, "navigation.traverseTo() can navigate 3 frames of different types with correct navigate event cancelable values");
+</script>
diff --git a/third_party/blink/web_tests/external/wpt/navigation-api/navigate-event/navigation-traverseTo-same-document-preventDefault-multiple-windows.html b/third_party/blink/web_tests/external/wpt/navigation-api/navigate-event/navigation-traverseTo-same-document-preventDefault-multiple-windows.html
new file mode 100644
index 0000000..9bb64fb
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/navigation-api/navigate-event/navigation-traverseTo-same-document-preventDefault-multiple-windows.html
@@ -0,0 +1,23 @@
+<!doctype html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<iframe id="i" src="/common/blank.html"></iframe>
+<script>
+promise_test(async t => {
+  // Wait for after the load event so that the navigation doesn't get converted
+  // into a replace navigation.
+  await new Promise(resolve => window.onload = () => t.step_timeout(resolve, 0));
+  await navigation.navigate("#").finished;
+  await i.contentWindow.navigation.navigate("#").finished;
+  assert_equals(navigation.entries().length, 2);
+  assert_equals(i.contentWindow.navigation.entries().length, 2);
+  assert_equals(navigation.currentEntry.index, 1);
+  assert_equals(i.contentWindow.navigation.currentEntry.index, 1);
+
+  navigation.onnavigate = e => e.preventDefault();
+  i.contentWindow.navigation.onnavigate = t.unreached_func("navigate event should not fire in the iframe, because the traversal was cancelled in the top window");
+  await promise_rejects_dom(t, "AbortError", navigation.traverseTo(navigation.entries()[0].key).finished);
+  assert_equals(navigation.currentEntry.index, 1);
+  assert_equals(i.contentWindow.navigation.currentEntry.index, 1);
+}, "navigation.traverseTo() - if a top window cancels the traversal, any iframes should not fire navigate");
+</script>
diff --git a/third_party/blink/web_tests/external/wpt/navigation-api/navigate-event/navigation-traverseTo-top-cancels-cross-document-child.html b/third_party/blink/web_tests/external/wpt/navigation-api/navigate-event/navigation-traverseTo-top-cancels-cross-document-child.html
new file mode 100644
index 0000000..11f07af
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/navigation-api/navigate-event/navigation-traverseTo-top-cancels-cross-document-child.html
@@ -0,0 +1,26 @@
+<!doctype html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../navigation-methods/return-value/resources/helpers.js"></script>
+<iframe id="i" src="/common/blank.html"></iframe>
+<script>
+promise_test(async t => {
+  // Wait for after the load event so that the navigation doesn't get converted
+  // into a replace navigation.
+  await new Promise(resolve => window.onload = () => t.step_timeout(resolve, 0));
+  await navigation.navigate("#").finished;
+  i.contentWindow.navigation.navigate("?");
+  await new Promise(resolve => i.onload = () => t.step_timeout(resolve, 0));
+
+  assert_equals(navigation.entries().length, 2);
+  assert_equals(i.contentWindow.navigation.entries().length, 2);
+  assert_equals(navigation.currentEntry.index, 1);
+  assert_equals(i.contentWindow.navigation.currentEntry.index, 1);
+
+  navigation.onnavigate = t.step_func(e => e.preventDefault());
+  i.contentWindow.navigation.onnavigate = t.unreached_func("navigation should be cancelled before iframe fires navigate event");
+  await assertBothRejectDOM(t, navigation.traverseTo(navigation.entries()[0].key), "AbortError");
+  // Give the iframe time to navigate in case it was incorrectly permitted.
+  await new Promise(resolve => t.step_timeout(resolve, 50));
+}, "navigate.traverseTo() cancelled by top frame cancels cross-document iframe");
+</script>
diff --git a/third_party/blink/web_tests/external/wpt/navigation-api/navigation-methods/traverseTo-detach-between-navigate-and-navigatesuccess.html b/third_party/blink/web_tests/external/wpt/navigation-api/navigation-methods/traverseTo-detach-between-navigate-and-navigatesuccess.html
index a0a29188..43bde9a 100644
--- a/third_party/blink/web_tests/external/wpt/navigation-api/navigation-methods/traverseTo-detach-between-navigate-and-navigatesuccess.html
+++ b/third_party/blink/web_tests/external/wpt/navigation-api/navigation-methods/traverseTo-detach-between-navigate-and-navigatesuccess.html
@@ -20,8 +20,16 @@
       let result;
 
       i.contentWindow.navigation.onnavigate = t.step_func(e => {
-        e.intercept({ handler: () => new Promise(resolve => t.step_timeout(resolve, 2)) });
-        t.step_timeout(() => i.remove(), 1);
+        // 1. The intercept handler runs.
+        // 2. "t.step_timeout(handlerRunResolve, 0)" executes handlerRunResolve in a macro task.
+        // 3. In the next microtask, the iframe is removed.
+        // 4. "t.step_timeout(resolve, 5)" executes and the intercept handler promise resolves.
+        let handlerRunResolve;
+        new Promise(r => handlerRunResolve = r).then(() => i.remove());
+        e.intercept({ handler() {
+          t.step_timeout(handlerRunResolve, 0);
+          return new Promise(resolve => t.step_timeout(resolve, 5));
+        }});
       });
 
       i.contentWindow.navigation.onnavigatesuccess = t.unreached_func("navigatesuccess must not fire");
diff --git a/third_party/blink/web_tests/http/tests/inspector-protocol/accessibility/accessibility-getAXNodeAndAncestors-expected.txt b/third_party/blink/web_tests/http/tests/inspector-protocol/accessibility/accessibility-getAXNodeAndAncestors-expected.txt
index a1ad8e6..5563ebd8 100644
--- a/third_party/blink/web_tests/http/tests/inspector-protocol/accessibility/accessibility-getAXNodeAndAncestors-expected.txt
+++ b/third_party/blink/web_tests/http/tests/inspector-protocol/accessibility/accessibility-getAXNodeAndAncestors-expected.txt
@@ -137,6 +137,13 @@
     }
     ignored : true
     ignoredReasons : [
+        [0] : {
+            name : uninteresting
+            value : {
+                type : boolean
+                value : true
+            }
+        }
     ]
     nodeId : <string>
     parentId : <string>
diff --git a/third_party/blink/web_tests/http/tests/inspector-protocol/accessibility/accessibility-ignoredNodes-expected.txt b/third_party/blink/web_tests/http/tests/inspector-protocol/accessibility/accessibility-ignoredNodes-expected.txt
index 1cc9a89..cf947a2 100644
--- a/third_party/blink/web_tests/http/tests/inspector-protocol/accessibility/accessibility-ignoredNodes-expected.txt
+++ b/third_party/blink/web_tests/http/tests/inspector-protocol/accessibility/accessibility-ignoredNodes-expected.txt
@@ -12,6 +12,13 @@
     domNode : html
     ignored : true
     ignoredReasons : [
+        [0] : {
+            name : uninteresting
+            value : {
+                type : boolean
+                value : true
+            }
+        }
     ]
     nodeId : <string>
     parentId : <string>
diff --git a/third_party/glide/gif_encoder/BUILD.gn b/third_party/glide/gif_encoder/BUILD.gn
deleted file mode 100644
index 1e2abbc8..0000000
--- a/third_party/glide/gif_encoder/BUILD.gn
+++ /dev/null
@@ -1,18 +0,0 @@
-# Copyright 2021 The Chromium Authors
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-import("//build/config/android/rules.gni")
-
-assert(is_android)
-
-android_library("gif_encoder_java") {
-  sources = [
-    "java/com/bumptech/glide/gifencoder/AnimatedGifEncoder.java",
-    "java/com/bumptech/glide/gifencoder/LZWEncoder.java",
-    "java/com/bumptech/glide/gifencoder/NeuQuant.java",
-  ]
-  deps = [
-    "//third_party/androidx:androidx_annotation_annotation_java",
-  ]
-}
diff --git a/third_party/glide/gif_encoder/LICENSE b/third_party/glide/gif_encoder/LICENSE
deleted file mode 100644
index 0bdc525..0000000
--- a/third_party/glide/gif_encoder/LICENSE
+++ /dev/null
@@ -1,25 +0,0 @@
-License for AnimatedGifEncoder.java and LZWEncoder.java
-
-No copyright asserted on the source code of this class. May be used for any
-purpose, however, refer to the Unisys LZW patent for restrictions on use of
-the associated LZWEncoder class. Please forward any corrections to
-kweiner@fmsware.com.
-
------------------------------------------------------------------------------
-License for NeuQuant.java
-
-Copyright (c) 1994 Anthony Dekker
-
-NEUQUANT Neural-Net quantization algorithm by Anthony Dekker, 1994. See
-"Kohonen neural networks for optimal colour quantization" in "Network:
-Computation in Neural Systems" Vol. 5 (1994) pp 351-367. for a discussion of
-the algorithm.
-
-Any party obtaining a copy of these files from the author, directly or
-indirectly, is granted, free of charge, a full and unrestricted irrevocable,
-world-wide, paid up, royalty-free, nonexclusive right and license to deal in
-this software and documentation files (the "Software"), including without
-limitation the rights to use, copy, modify, merge, publish, distribute,
-sublicense, and/or sell copies of the Software, and to permit persons who
-receive copies from any such party to do so, with the only requirement being
-that this copyright notice remain intact.
\ No newline at end of file
diff --git a/third_party/glide/gif_encoder/OWNERS b/third_party/glide/gif_encoder/OWNERS
deleted file mode 100644
index a60ab48..0000000
--- a/third_party/glide/gif_encoder/OWNERS
+++ /dev/null
@@ -1,2 +0,0 @@
-gujen@google.com
-sebsg@chromium.org
\ No newline at end of file
diff --git a/third_party/glide/gif_encoder/README.chromium b/third_party/glide/gif_encoder/README.chromium
deleted file mode 100644
index 43ce333..0000000
--- a/third_party/glide/gif_encoder/README.chromium
+++ /dev/null
@@ -1,17 +0,0 @@
-Name: Android GIF Encoder
-URL: https://github.com/bumptech/glide/tree/master/third_party/gif_encoder
-Version: a7b254c42888db691b7339b2165abcd462473fcf
-License: Notice
-License File: LICENSE
-License Android Compatible: yes
-Security Critical: yes
-CPEPrefix: unknown
-
-Description:
-  This contains the GIF encoder classes from the Glide library, which were themselves based on the
-  GIF encoding logic found here: http://java2s.com/Code/Java/2D-Graphics-GUI/AnimatedGifEncoder.htm.
-  The README.third_party file in the Glide repo has more info.
-
-Local Modifications:
-- Changed the name of the top-level package from "com.bumptech.glide.gifencoder" to
-  "org.chromium.third_party.glide.gif_encoder" to avoid potential conflicts with the Glide library.
\ No newline at end of file
diff --git a/third_party/glide/gif_encoder/java/com/bumptech/glide/gifencoder/AnimatedGifEncoder.java b/third_party/glide/gif_encoder/java/com/bumptech/glide/gifencoder/AnimatedGifEncoder.java
deleted file mode 100644
index fd8c313..0000000
--- a/third_party/glide/gif_encoder/java/com/bumptech/glide/gifencoder/AnimatedGifEncoder.java
+++ /dev/null
@@ -1,580 +0,0 @@
-package org.chromium.third_party.glide.gif_encoder;
-
-import android.graphics.Bitmap;
-import android.graphics.Canvas;
-import android.graphics.Color;
-import android.util.Log;
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import java.io.BufferedOutputStream;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.OutputStream;
-
-/**
- * Class AnimatedGifEncoder - Encodes a GIF file consisting of one or more
- * frames.
- *
- * <pre>
- *  Example:
- *     AnimatedGifEncoder e = new AnimatedGifEncoder();
- *     e.start(outputFileName);
- *     e.setDelay(1000);   // 1 frame per sec
- *     e.addFrame(image1);
- *     e.addFrame(image2);
- *     e.addFrame(image3, 100, 100);    // set position of the frame
- *     e.finish();
- * </pre>
- *
- * No copyright asserted on the source code of this class. May be used for any
- * purpose, however, refer to the Unisys LZW patent for restrictions on use of
- * the associated LZWEncoder class. Please forward any corrections to
- * kweiner@fmsware.com.
- *
- * @author Kevin Weiner, FM Software
- * @version 1.03 November 2003
- *
- */
-
-public class AnimatedGifEncoder {
-    private static final String TAG = "AnimatedGifEncoder";
-
-    // The minimum % of an images pixels that must be transparent for us to set a transparent index
-    // automatically.
-    private static final double MIN_TRANSPARENT_PERCENTAGE = 4d;
-
-    private int width; // image size
-
-    private int height;
-
-    private int fixedWidth;   // set by setSize()
-
-    private int fixedHeight;
-
-    private Integer transparent = null; // transparent color if given
-
-    private int transIndex; // transparent index in color table
-
-    private int repeat = -1; // no repeat
-
-    private int delay = 0; // frame delay (hundredths)
-
-    private boolean started = false; // ready to output frames
-
-    private OutputStream out;
-
-    private Bitmap image; // current frame
-
-    private byte[] pixels; // BGR byte array from frame
-
-    private byte[] indexedPixels; // converted frame indexed to palette
-
-    private int colorDepth; // number of bit planes
-
-    private byte[] colorTab; // RGB palette
-
-    private boolean[] usedEntry = new boolean[256]; // active palette entries
-
-    private int palSize = 7; // color table size (bits-1)
-
-    private int dispose = -1; // disposal code (-1 = use default)
-
-    private boolean closeStream = false; // close stream when finished
-
-    private boolean firstFrame = true;
-
-    private boolean sizeSet = false; // if false, get size from first frame
-
-    private int sample = 10; // default sample interval for quantizer
-
-    private boolean hasTransparentPixels;
-
-    /**
-     * Sets the delay time between each frame, or changes it for subsequent frames
-     * (applies to last frame added).
-     *
-     * @param ms
-     *          int delay time in milliseconds
-     */
-    public void setDelay(int ms) {
-        delay = Math.round(ms / 10.0f);
-    }
-
-    /**
-     * Sets the GIF frame disposal code for the last added frame and any
-     * subsequent frames. Default is 0 if no transparent color has been set,
-     * otherwise 2.
-     *
-     * @param code
-     *          int disposal code.
-     */
-    public void setDispose(int code) {
-        if (code >= 0) {
-            dispose = code;
-        }
-    }
-
-    /**
-     * Sets the number of times the set of GIF frames should be played. Default is
-     * 1; 0 means play indefinitely. Must be invoked before the first image is
-     * added.
-     *
-     * @param iter
-     *          int number of iterations.
-     */
-    public void setRepeat(int iter) {
-        if (iter >= 0) {
-            repeat = iter;
-        }
-    }
-
-    /**
-     * Sets the transparent color for the last added frame and any subsequent
-     * frames. Since all colors are subject to modification in the quantization
-     * process, the color in the final palette for each frame closest to the given
-     * color becomes the transparent color for that frame. May be set to null to
-     * indicate no transparent color.
-     *
-     * @param color
-     *          Color to be treated as transparent on display.
-     */
-    public void setTransparent(int color) {
-        transparent = color;
-    }
-
-    /**
-     * Adds next GIF frame. The frame is not written immediately, but is actually
-     * deferred until the next frame is received so that timing data can be
-     * inserted. Invoking <code>finish()</code> flushes all frames. If
-     * <code>setSize</code> was invoked, the size is used for all subsequent frames.
-     * Otherwise, the actual size of the image is used for each frames.
-     *
-     * @param im
-     *          BufferedImage containing frame to write.
-     * @return true if successful.
-     */
-    public boolean addFrame(@Nullable Bitmap im) {
-        return addFrame(im, 0, 0);
-    }
-
-    /**
-     * Adds next GIF frame to the specified position. The frame is not written immediately, but is
-     * actually deferred until the next frame is received so that timing data can be inserted.
-     * Invoking <code>finish()</code> flushes all frames. If <code>setSize</code> was invoked, the
-     * size is used for all subsequent frames. Otherwise, the actual size of the image is used for
-     * each frame.
-     *
-     * See page 11 of http://giflib.sourceforge.net/gif89.txt for the position of the frame
-     *
-     * @param im
-     *          BufferedImage containing frame to write.
-     * @param x
-     *          Column number, in pixels, of the left edge of the image, with respect to the left
-     *          edge of the Logical Screen.
-     * @param y
-     *          Row number, in pixels, of the top edge of the image with respect to the top edge of
-     *          the Logical Screen.
-     * @return true if successful.
-     */
-    public boolean addFrame(@Nullable Bitmap im, int x, int y) {
-        if ((im == null) || !started) {
-            return false;
-        }
-        boolean ok = true;
-        try {
-            if (sizeSet) {
-                setFrameSize(fixedWidth, fixedHeight);
-            } else {
-                setFrameSize(im.getWidth(), im.getHeight());
-            }
-            image = im;
-            getImagePixels(); // convert to correct format if necessary
-            analyzePixels(); // build color table & map pixels
-            if (firstFrame) {
-                writeLSD(); // logical screen descriptor
-                writePalette(); // global color table
-                if (repeat >= 0) {
-                    // use NS app extension to indicate reps
-                    writeNetscapeExt();
-                }
-            }
-            writeGraphicCtrlExt(); // write graphic control extension
-            writeImageDesc(x, y); // image descriptor
-            if (!firstFrame) {
-                writePalette(); // local color table
-            }
-            writePixels(); // encode and write pixel data
-            firstFrame = false;
-        } catch (IOException e) {
-            ok = false;
-        }
-
-        return ok;
-    }
-
-    /**
-     * Flushes any pending data and closes output file. If writing to an
-     * OutputStream, the stream is not closed.
-     */
-    public boolean finish() {
-        if (!started)
-            return false;
-        boolean ok = true;
-        started = false;
-        try {
-            out.write(0x3b); // GIF trailer
-            out.flush();
-            if (closeStream) {
-                out.close();
-            }
-        } catch (IOException e) {
-            ok = false;
-        }
-
-        // reset for subsequent use
-        transIndex = 0;
-        out = null;
-        image = null;
-        pixels = null;
-        indexedPixels = null;
-        colorTab = null;
-        closeStream = false;
-        firstFrame = true;
-
-        return ok;
-    }
-
-    /**
-     * Sets frame rate in frames per second. Equivalent to
-     * <code>setDelay(1000/fps)</code>.
-     *
-     * @param fps
-     *          float frame rate (frames per second)
-     */
-    public void setFrameRate(float fps) {
-        if (fps != 0f) {
-            delay = Math.round(100f / fps);
-        }
-    }
-
-    /**
-     * Sets quality of color quantization (conversion of images to the maximum 256
-     * colors allowed by the GIF specification). Lower values (minimum = 1)
-     * produce better colors, but slow processing significantly. 10 is the
-     * default, and produces good color mapping at reasonable speeds. Values
-     * greater than 20 do not yield significant improvements in speed.
-     *
-     * @param quality int greater than 0.
-     */
-    public void setQuality(int quality) {
-        if (quality < 1)
-            quality = 1;
-        sample = quality;
-    }
-
-    /**
-     * Sets the fixed GIF frame size for all the frames.
-     * This should be called before start.
-     *
-     * @param w
-     *          int frame width.
-     * @param h
-     *          int frame width.
-     */
-    public void setSize(int w, int h) {
-        if (started) {
-            return;
-        }
-
-        fixedWidth = w;
-        fixedHeight = h;
-        if (fixedWidth < 1) {
-            fixedWidth = 320;
-        }
-        if (fixedHeight < 1) {
-            fixedHeight = 240;
-        }
-
-        sizeSet = true;
-    }
-
-    /**
-     * Sets current GIF frame size.
-     *
-     * @param w
-     *          int frame width.
-     * @param h
-     *          int frame width.
-     */
-    private void setFrameSize(int w, int h) {
-        width = w;
-        height = h;
-    }
-
-    /**
-     * Initiates GIF file creation on the given stream. The stream is not closed
-     * automatically.
-     *
-     * @param os
-     *          OutputStream on which GIF images are written.
-     * @return false if initial write failed.
-     */
-    public boolean start(@Nullable OutputStream os) {
-        if (os == null)
-            return false;
-        boolean ok = true;
-        closeStream = false;
-        out = os;
-        try {
-            writeString("GIF89a"); // header
-        } catch (IOException e) {
-            ok = false;
-        }
-        return started = ok;
-    }
-
-    /**
-     * Initiates writing of a GIF file with the specified name.
-     *
-     * @param file
-     *          String containing output file name.
-     * @return false if open or initial write failed.
-     */
-    public boolean start(@NonNull String file) {
-        boolean ok;
-        try {
-            out = new BufferedOutputStream(new FileOutputStream(file));
-            ok = start(out);
-            closeStream = true;
-        } catch (IOException e) {
-            ok = false;
-        }
-        return started = ok;
-    }
-
-    /**
-     * Analyzes image colors and creates color map.
-     */
-    private void analyzePixels() {
-        int len = pixels.length;
-        int nPix = len / 3;
-        indexedPixels = new byte[nPix];
-        NeuQuant nq = new NeuQuant(pixels, len, sample);
-        // initialize quantizer
-        colorTab = nq.process(); // create reduced palette
-        // convert map from BGR to RGB
-        for (int i = 0; i < colorTab.length; i += 3) {
-            byte temp = colorTab[i];
-            colorTab[i] = colorTab[i + 2];
-            colorTab[i + 2] = temp;
-            usedEntry[i / 3] = false;
-        }
-        // map image pixels to new palette
-        int k = 0;
-        for (int i = 0; i < nPix; i++) {
-            int index = nq.map(pixels[k++] & 0xff, pixels[k++] & 0xff, pixels[k++] & 0xff);
-            usedEntry[index] = true;
-            indexedPixels[i] = (byte) index;
-        }
-        pixels = null;
-        colorDepth = 8;
-        palSize = 7;
-        // get closest match to transparent color if specified
-        if (transparent != null) {
-            transIndex = findClosest(transparent);
-        } else if (hasTransparentPixels) {
-            transIndex = findClosest(Color.TRANSPARENT);
-        }
-    }
-
-    /**
-     * Returns index of palette color closest to c
-     *
-     */
-    private int findClosest(int color) {
-        if (colorTab == null)
-            return -1;
-        int r = Color.red(color);
-        int g = Color.green(color);
-        int b = Color.blue(color);
-        int minpos = 0;
-        int dmin = 256 * 256 * 256;
-        int len = colorTab.length;
-        for (int i = 0; i < len;) {
-            int dr = r - (colorTab[i++] & 0xff);
-            int dg = g - (colorTab[i++] & 0xff);
-            int db = b - (colorTab[i] & 0xff);
-            int d = dr * dr + dg * dg + db * db;
-            int index = i / 3;
-            if (usedEntry[index] && (d < dmin)) {
-                dmin = d;
-                minpos = index;
-            }
-            i++;
-        }
-        return minpos;
-    }
-
-    /**
-     * Extracts image pixels into byte array "pixels"
-     */
-    private void getImagePixels() {
-        int w = image.getWidth();
-        int h = image.getHeight();
-
-        if ((w != width) || (h != height)) {
-            // create new image with right size/format
-            Bitmap temp = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
-            Canvas canvas = new Canvas(temp);
-            canvas.drawBitmap(temp, 0, 0, null);
-            image = temp;
-        }
-        int[] pixelsInt = new int[w * h];
-        image.getPixels(pixelsInt, 0, w, 0, 0, w, h);
-
-        // The algorithm requires 3 bytes per pixel as RGB.
-        pixels = new byte[pixelsInt.length * 3];
-
-        int pixelsIndex = 0;
-        hasTransparentPixels = false;
-        int totalTransparentPixels = 0;
-        for (final int pixel : pixelsInt) {
-            if (pixel == Color.TRANSPARENT) {
-                totalTransparentPixels++;
-            }
-            pixels[pixelsIndex++] = (byte) (pixel & 0xFF);
-            pixels[pixelsIndex++] = (byte) ((pixel >> 8) & 0xFF);
-            pixels[pixelsIndex++] = (byte) ((pixel >> 16) & 0xFF);
-        }
-
-        double transparentPercentage = 100 * totalTransparentPixels / (double) pixelsInt.length;
-        // Assume images with greater where more than n% of the pixels are transparent actually have
-        // transparency. See issue #214.
-        hasTransparentPixels = transparentPercentage > MIN_TRANSPARENT_PERCENTAGE;
-        if (Log.isLoggable(TAG, Log.DEBUG)) {
-            Log.d(TAG, "got pixels for frame with " + transparentPercentage
-                + "% transparent pixels");
-        }
-    }
-
-    /**
-     * Writes Graphic Control Extension
-     */
-    private void writeGraphicCtrlExt() throws IOException {
-        out.write(0x21); // extension introducer
-        out.write(0xf9); // GCE label
-        out.write(4); // data block size
-        int transp, disp;
-        if (transparent == null && !hasTransparentPixels) {
-            transp = 0;
-            disp = 0; // dispose = no action
-        } else {
-            transp = 1;
-            disp = 2; // force clear if using transparent color
-        }
-        if (dispose >= 0) {
-            disp = dispose & 7; // user override
-        }
-        disp <<= 2;
-
-        // packed fields
-        out.write(0 | // 1:3 reserved
-                disp | // 4:6 disposal
-                0 | // 7 user input - 0 = none
-                transp); // 8 transparency flag
-
-        writeShort(delay); // delay x 1/100 sec
-        out.write(transIndex); // transparent color index
-        out.write(0); // block terminator
-    }
-
-    /**
-     * Writes Image Descriptor
-     */
-    private void writeImageDesc(int x, int y) throws IOException {
-        out.write(0x2c); // image separator
-        writeShort(x); // image position
-        writeShort(y);
-        writeShort(width); // image size
-        writeShort(height);
-        // packed fields
-        if (firstFrame) {
-            // no LCT - GCT is used for first (or only) frame
-            out.write(0);
-        } else {
-            // specify normal LCT
-            out.write(0x80 | // 1 local color table 1=yes
-                    0 | // 2 interlace - 0=no
-                    0 | // 3 sorted - 0=no
-                    0 | // 4-5 reserved
-                    palSize); // 6-8 size of color table
-        }
-    }
-
-    /**
-     * Writes Logical Screen Descriptor
-     */
-    private void writeLSD() throws IOException {
-        // logical screen size
-        writeShort(width);
-        writeShort(height);
-        // packed fields
-        out.write((0x80 | // 1 : global color table flag = 1 (gct used)
-                0x70 | // 2-4 : color resolution = 7
-                0x00 | // 5 : gct sort flag = 0
-                palSize)); // 6-8 : gct size
-
-        out.write(0); // background color index
-        out.write(0); // pixel aspect ratio - assume 1:1
-    }
-
-    /**
-     * Writes Netscape application extension to define repeat count.
-     */
-    private void writeNetscapeExt() throws IOException {
-        out.write(0x21); // extension introducer
-        out.write(0xff); // app extension label
-        out.write(11); // block size
-        writeString("NETSCAPE" + "2.0"); // app id + auth code
-        out.write(3); // sub-block size
-        out.write(1); // loop sub-block id
-        writeShort(repeat); // loop count (extra iterations, 0=repeat forever)
-        out.write(0); // block terminator
-    }
-
-    /**
-     * Writes color table
-     */
-    private void writePalette() throws IOException {
-        out.write(colorTab, 0, colorTab.length);
-        int n = (3 * 256) - colorTab.length;
-        for (int i = 0; i < n; i++) {
-            out.write(0);
-        }
-    }
-
-    /**
-     * Encodes and writes pixel data
-     */
-    private void writePixels() throws IOException {
-        LZWEncoder encoder = new LZWEncoder(width, height, indexedPixels, colorDepth);
-        encoder.encode(out);
-    }
-
-    /**
-     * Write 16-bit value to output stream, LSB first
-     */
-    private void writeShort(int value) throws IOException {
-        out.write(value & 0xff);
-        out.write((value >> 8) & 0xff);
-    }
-
-    /**
-     * Writes string to output stream
-     */
-    private void writeString(String s) throws IOException {
-        for (int i = 0; i < s.length(); i++) {
-            out.write((byte) s.charAt(i));
-        }
-    }
-}
\ No newline at end of file
diff --git a/third_party/glide/gif_encoder/java/com/bumptech/glide/gifencoder/LZWEncoder.java b/third_party/glide/gif_encoder/java/com/bumptech/glide/gifencoder/LZWEncoder.java
deleted file mode 100644
index fed3330..0000000
--- a/third_party/glide/gif_encoder/java/com/bumptech/glide/gifencoder/LZWEncoder.java
+++ /dev/null
@@ -1,296 +0,0 @@
-package org.chromium.third_party.glide.gif_encoder;
-
-import java.io.IOException;
-import java.io.OutputStream;
-
-// ==============================================================================
-// Adapted from Jef Poskanzer's Java port by way of J. M. G. Elliott.
-// K Weiner 12/00
-class LZWEncoder {
-
-    private static final int EOF = -1;
-
-    private int imgW, imgH;
-
-    private byte[] pixAry;
-
-    private int initCodeSize;
-
-    private int remaining;
-
-    private int curPixel;
-
-    // GIFCOMPR.C - GIF Image compression routines
-    //
-    // Lempel-Ziv compression based on 'compress'. GIF modifications by
-    // David Rowley (mgardi@watdcsu.waterloo.edu)
-
-    // General DEFINEs
-
-    static final int BITS = 12;
-
-    static final int HSIZE = 5003; // 80% occupancy
-
-    // GIF Image compression - modified 'compress'
-    //
-    // Based on: compress.c - File compression ala IEEE Computer, June 1984.
-    //
-    // By Authors: Spencer W. Thomas (decvax!harpo!utah-cs!utah-gr!thomas)
-    // Jim McKie (decvax!mcvax!jim)
-    // Steve Davies (decvax!vax135!petsd!peora!srd)
-    // Ken Turkowski (decvax!decwrl!turtlevax!ken)
-    // James A. Woods (decvax!ihnp4!ames!jaw)
-    // Joe Orost (decvax!vax135!petsd!joe)
-
-    int n_bits; // number of bits/code
-
-    int maxbits = BITS; // user settable max # bits/code
-
-    int maxcode; // maximum code, given n_bits
-
-    int maxmaxcode = 1 << BITS; // should NEVER generate this code
-
-    int[] htab = new int[HSIZE];
-
-    int[] codetab = new int[HSIZE];
-
-    int hsize = HSIZE; // for dynamic table sizing
-
-    int free_ent = 0; // first unused entry
-
-    // block compression parameters -- after all codes are used up,
-    // and compression rate changes, start over.
-    boolean clear_flg = false;
-
-    // Algorithm: use open addressing double hashing (no chaining) on the
-    // prefix code / next character combination. We do a variant of Knuth's
-    // algorithm D (vol. 3, sec. 6.4) along with G. Knott's relatively-prime
-    // secondary probe. Here, the modular division first probe is gives way
-    // to a faster exclusive-or manipulation. Also do block compression with
-    // an adaptive reset, whereby the code table is cleared when the compression
-    // ratio decreases, but after the table fills. The variable-length output
-    // codes are re-sized at this point, and a special CLEAR code is generated
-    // for the decompressor. Late addition: construct the table according to
-    // file size for noticeable speed improvement on small files. Please direct
-    // questions about this implementation to ames!jaw.
-
-    int g_init_bits;
-
-    int ClearCode;
-
-    int EOFCode;
-
-    // output
-    //
-    // Output the given code.
-    // Inputs:
-    // code: A n_bits-bit integer. If == -1, then EOF. This assumes
-    // that n_bits =< wordsize - 1.
-    // Outputs:
-    // Outputs code to the file.
-    // Assumptions:
-    // Chars are 8 bits long.
-    // Algorithm:
-    // Maintain a BITS character long buffer (so that 8 codes will
-    // fit in it exactly). Use the VAX insv instruction to insert each
-    // code in turn. When the buffer fills up empty it and start over.
-
-    int cur_accum = 0;
-
-    int cur_bits = 0;
-
-    int masks[] = {0x0000, 0x0001, 0x0003, 0x0007, 0x000F, 0x001F, 0x003F, 0x007F, 0x00FF, 0x01FF,
-            0x03FF, 0x07FF, 0x0FFF, 0x1FFF, 0x3FFF, 0x7FFF, 0xFFFF};
-
-    // Number of characters so far in this 'packet'
-    int a_count;
-
-    // Define the storage for the packet accumulator
-    byte[] accum = new byte[256];
-
-    // ----------------------------------------------------------------------------
-    LZWEncoder(int width, int height, byte[] pixels, int color_depth) {
-        imgW = width;
-        imgH = height;
-        pixAry = pixels;
-        initCodeSize = Math.max(2, color_depth);
-    }
-
-    // Add a character to the end of the current packet, and if it is 254
-    // characters, flush the packet to disk.
-    void char_out(byte c, OutputStream outs) throws IOException {
-        accum[a_count++] = c;
-        if (a_count >= 254)
-            flush_char(outs);
-    }
-
-    // Clear out the hash table
-
-    // table clear for block compress
-    void cl_block(OutputStream outs) throws IOException {
-        cl_hash(hsize);
-        free_ent = ClearCode + 2;
-        clear_flg = true;
-
-        output(ClearCode, outs);
-    }
-
-    // reset code table
-    void cl_hash(int hsize) {
-        for (int i = 0; i < hsize; ++i)
-            htab[i] = -1;
-    }
-
-    void compress(int init_bits, OutputStream outs) throws IOException {
-        int fcode;
-        int i /* = 0 */;
-        int c;
-        int ent;
-        int disp;
-        int hsize_reg;
-        int hshift;
-
-        // Set up the globals: g_init_bits - initial number of bits
-        g_init_bits = init_bits;
-
-        // Set up the necessary values
-        clear_flg = false;
-        n_bits = g_init_bits;
-        maxcode = MAXCODE(n_bits);
-
-        ClearCode = 1 << (init_bits - 1);
-        EOFCode = ClearCode + 1;
-        free_ent = ClearCode + 2;
-
-        a_count = 0; // clear packet
-
-        ent = nextPixel();
-
-        hshift = 0;
-        for (fcode = hsize; fcode < 65536; fcode *= 2)
-            ++hshift;
-        hshift = 8 - hshift; // set hash code range bound
-
-        hsize_reg = hsize;
-        cl_hash(hsize_reg); // clear hash table
-
-        output(ClearCode, outs);
-
-        outer_loop:
-        while ((c = nextPixel()) != EOF) {
-            fcode = (c << maxbits) + ent;
-            i = (c << hshift) ^ ent; // xor hashing
-
-            if (htab[i] == fcode) {
-                ent = codetab[i];
-                continue;
-            } else if (htab[i] >= 0) // non-empty slot
-            {
-                disp = hsize_reg - i; // secondary hash (after G. Knott)
-                if (i == 0)
-                    disp = 1;
-                do {
-                    if ((i -= disp) < 0)
-                        i += hsize_reg;
-
-                    if (htab[i] == fcode) {
-                        ent = codetab[i];
-                        continue outer_loop;
-                    }
-                } while (htab[i] >= 0);
-            }
-            output(ent, outs);
-            ent = c;
-            if (free_ent < maxmaxcode) {
-                codetab[i] = free_ent++; // code -> hashtable
-                htab[i] = fcode;
-            } else
-                cl_block(outs);
-        }
-        // Put out the final code.
-        output(ent, outs);
-        output(EOFCode, outs);
-    }
-
-    // ----------------------------------------------------------------------------
-    void encode(OutputStream os) throws IOException {
-        os.write(initCodeSize); // write "initial code size" byte
-
-        remaining = imgW * imgH; // reset navigation variables
-        curPixel = 0;
-
-        compress(initCodeSize + 1, os); // compress and write the pixel data
-
-        os.write(0); // write block terminator
-    }
-
-    // Flush the packet to disk, and reset the accumulator
-    void flush_char(OutputStream outs) throws IOException {
-        if (a_count > 0) {
-            outs.write(a_count);
-            outs.write(accum, 0, a_count);
-            a_count = 0;
-        }
-    }
-
-    final int MAXCODE(int n_bits) {
-        return (1 << n_bits) - 1;
-    }
-
-    // ----------------------------------------------------------------------------
-    // Return the next pixel from the image
-    // ----------------------------------------------------------------------------
-    private int nextPixel() {
-        if (remaining == 0)
-            return EOF;
-
-        --remaining;
-
-        byte pix = pixAry[curPixel++];
-
-        return pix & 0xff;
-    }
-
-    void output(int code, OutputStream outs) throws IOException {
-        cur_accum &= masks[cur_bits];
-
-        if (cur_bits > 0)
-            cur_accum |= (code << cur_bits);
-        else
-            cur_accum = code;
-
-        cur_bits += n_bits;
-
-        while (cur_bits >= 8) {
-            char_out((byte) (cur_accum & 0xff), outs);
-            cur_accum >>= 8;
-            cur_bits -= 8;
-        }
-
-        // If the next entry is going to be too big for the code size,
-        // then increase it, if possible.
-        if (free_ent > maxcode || clear_flg) {
-            if (clear_flg) {
-                maxcode = MAXCODE(n_bits = g_init_bits);
-                clear_flg = false;
-            } else {
-                ++n_bits;
-                if (n_bits == maxbits)
-                    maxcode = maxmaxcode;
-                else
-                    maxcode = MAXCODE(n_bits);
-            }
-        }
-
-        if (code == EOFCode) {
-            // At EOF, write the rest of the buffer.
-            while (cur_bits > 0) {
-                char_out((byte) (cur_accum & 0xff), outs);
-                cur_accum >>= 8;
-                cur_bits -= 8;
-            }
-
-            flush_char(outs);
-        }
-    }
-}
\ No newline at end of file
diff --git a/third_party/glide/gif_encoder/java/com/bumptech/glide/gifencoder/NeuQuant.java b/third_party/glide/gif_encoder/java/com/bumptech/glide/gifencoder/NeuQuant.java
deleted file mode 100644
index 3d2c2cd..0000000
--- a/third_party/glide/gif_encoder/java/com/bumptech/glide/gifencoder/NeuQuant.java
+++ /dev/null
@@ -1,506 +0,0 @@
-package org.chromium.third_party.glide.gif_encoder;
-
-/*
- * NeuQuant Neural-Net Quantization Algorithm
- * ------------------------------------------
- *
- * Copyright (c) 1994 Anthony Dekker
- *
- * NEUQUANT Neural-Net quantization algorithm by Anthony Dekker, 1994. See
- * "Kohonen neural networks for optimal colour quantization" in "Network:
- * Computation in Neural Systems" Vol. 5 (1994) pp 351-367. for a discussion of
- * the algorithm.
- *
- * Any party obtaining a copy of these files from the author, directly or
- * indirectly, is granted, free of charge, a full and unrestricted irrevocable,
- * world-wide, paid up, royalty-free, nonexclusive right and license to deal in
- * this software and documentation files (the "Software"), including without
- * limitation the rights to use, copy, modify, merge, publish, distribute,
- * sublicense, and/or sell copies of the Software, and to permit persons who
- * receive copies from any such party to do so, with the only requirement being
- * that this copyright notice remain intact.
- */
-
-// Ported to Java 12/00 K Weiner
-class NeuQuant {
-
-    protected static final int netsize = 256; /* number of colours used */
-
-    /* four primes near 500 - assume no image has a length so large */
-  /* that it is divisible by all four primes */
-    protected static final int prime1 = 499;
-
-    protected static final int prime2 = 491;
-
-    protected static final int prime3 = 487;
-
-    protected static final int prime4 = 503;
-
-    protected static final int minpicturebytes = (3 * prime4);
-
-  /* minimum size for input image */
-
-  /*
-   * Program Skeleton ---------------- [select samplefac in range 1..30] [read
-   * image from input file] pic = (unsigned char*) malloc(3*width*height);
-   * initnet(pic,3*width*height,samplefac); learn(); unbiasnet(); [write output
-   * image header, using writecolourmap(f)] inxbuild(); write output image using
-   * inxsearch(b,g,r)
-   */
-
-  /*
-   * Network Definitions -------------------
-   */
-
-    protected static final int maxnetpos = (netsize - 1);
-
-    protected static final int netbiasshift = 4; /* bias for colour values */
-
-    protected static final int ncycles = 100; /* no. of learning cycles */
-
-    /* defs for freq and bias */
-    protected static final int intbiasshift = 16; /* bias for fractions */
-
-    protected static final int intbias = (((int) 1) << intbiasshift);
-
-    protected static final int gammashift = 10; /* gamma = 1024 */
-
-    protected static final int gamma = (((int) 1) << gammashift);
-
-    protected static final int betashift = 10;
-
-    protected static final int beta = (intbias >> betashift); /* beta = 1/1024 */
-
-    protected static final int betagamma = (intbias << (gammashift - betashift));
-
-    /* defs for decreasing radius factor */
-    protected static final int initrad = (netsize >> 3); /*
-                                                         * for 256 cols, radius
-                                                         * starts
-                                                         */
-
-    protected static final int radiusbiasshift = 6; /* at 32.0 biased by 6 bits */
-
-    protected static final int radiusbias = (((int) 1) << radiusbiasshift);
-
-    protected static final int initradius = (initrad * radiusbias); /*
-                                                                   * and
-                                                                   * decreases
-                                                                   * by a
-                                                                   */
-
-    protected static final int radiusdec = 30; /* factor of 1/30 each cycle */
-
-    /* defs for decreasing alpha factor */
-    protected static final int alphabiasshift = 10; /* alpha starts at 1.0 */
-
-    protected static final int initalpha = (((int) 1) << alphabiasshift);
-
-    protected int alphadec; /* biased by 10 bits */
-
-    /* radbias and alpharadbias used for radpower calculation */
-    protected static final int radbiasshift = 8;
-
-    protected static final int radbias = (((int) 1) << radbiasshift);
-
-    protected static final int alpharadbshift = (alphabiasshift + radbiasshift);
-
-    protected static final int alpharadbias = (((int) 1) << alpharadbshift);
-
-  /*
-   * Types and Global Variables --------------------------
-   */
-
-    protected byte[] thepicture; /* the input image itself */
-
-    protected int lengthcount; /* lengthcount = H*W*3 */
-
-    protected int samplefac; /* sampling factor 1..30 */
-
-    // typedef int pixel[4]; /* BGRc */
-    protected int[][] network; /* the network itself - [netsize][4] */
-
-    protected int[] netindex = new int[256];
-
-  /* for network lookup - really 256 */
-
-    protected int[] bias = new int[netsize];
-
-    /* bias and freq arrays for learning */
-    protected int[] freq = new int[netsize];
-
-    protected int[] radpower = new int[initrad];
-
-  /* radpower for precomputation */
-
-    /*
-     * Initialise network in range (0,0,0) to (255,255,255) and set parameters
-     * -----------------------------------------------------------------------
-     */
-    public NeuQuant(byte[] thepic, int len, int sample) {
-
-        int i;
-        int[] p;
-
-        thepicture = thepic;
-        lengthcount = len;
-        samplefac = sample;
-
-        network = new int[netsize][];
-        for (i = 0; i < netsize; i++) {
-            network[i] = new int[4];
-            p = network[i];
-            p[0] = p[1] = p[2] = (i << (netbiasshift + 8)) / netsize;
-            freq[i] = intbias / netsize; /* 1/netsize */
-            bias[i] = 0;
-        }
-    }
-
-    public byte[] colorMap() {
-        byte[] map = new byte[3 * netsize];
-        int[] index = new int[netsize];
-        for (int i = 0; i < netsize; i++)
-            index[network[i][3]] = i;
-        int k = 0;
-        for (int i = 0; i < netsize; i++) {
-            int j = index[i];
-            map[k++] = (byte) (network[j][0]);
-            map[k++] = (byte) (network[j][1]);
-            map[k++] = (byte) (network[j][2]);
-        }
-        return map;
-    }
-
-    /*
-     * Insertion sort of network and building of netindex[0..255] (to do after
-     * unbias)
-     * -------------------------------------------------------------------------------
-     */
-    public void inxbuild() {
-
-        int i, j, smallpos, smallval;
-        int[] p;
-        int[] q;
-        int previouscol, startpos;
-
-        previouscol = 0;
-        startpos = 0;
-        for (i = 0; i < netsize; i++) {
-            p = network[i];
-            smallpos = i;
-            smallval = p[1]; /* index on g */
-      /* find smallest in i..netsize-1 */
-            for (j = i + 1; j < netsize; j++) {
-                q = network[j];
-                if (q[1] < smallval) { /* index on g */
-                    smallpos = j;
-                    smallval = q[1]; /* index on g */
-                }
-            }
-            q = network[smallpos];
-      /* swap p (i) and q (smallpos) entries */
-            if (i != smallpos) {
-                j = q[0];
-                q[0] = p[0];
-                p[0] = j;
-                j = q[1];
-                q[1] = p[1];
-                p[1] = j;
-                j = q[2];
-                q[2] = p[2];
-                p[2] = j;
-                j = q[3];
-                q[3] = p[3];
-                p[3] = j;
-            }
-      /* smallval entry is now in position i */
-            if (smallval != previouscol) {
-                netindex[previouscol] = (startpos + i) >> 1;
-                for (j = previouscol + 1; j < smallval; j++)
-                    netindex[j] = i;
-                previouscol = smallval;
-                startpos = i;
-            }
-        }
-        netindex[previouscol] = (startpos + maxnetpos) >> 1;
-        for (j = previouscol + 1; j < 256; j++)
-            netindex[j] = maxnetpos; /* really 256 */
-    }
-
-    /*
-     * Main Learning Loop ------------------
-     */
-    public void learn() {
-
-        int i, j, b, g, r;
-        int radius, rad, alpha, step, delta, samplepixels;
-        byte[] p;
-        int pix, lim;
-
-        if (lengthcount < minpicturebytes)
-            samplefac = 1;
-        alphadec = 30 + ((samplefac - 1) / 3);
-        p = thepicture;
-        pix = 0;
-        lim = lengthcount;
-        samplepixels = lengthcount / (3 * samplefac);
-        delta = samplepixels / ncycles;
-        alpha = initalpha;
-        radius = initradius;
-
-        rad = radius >> radiusbiasshift;
-        if (rad <= 1)
-            rad = 0;
-        for (i = 0; i < rad; i++)
-            radpower[i] = alpha * (((rad * rad - i * i) * radbias) / (rad * rad));
-
-        // fprintf(stderr,"beginning 1D learning: initial radius=%d\n", rad);
-
-        if (lengthcount < minpicturebytes)
-            step = 3;
-        else if ((lengthcount % prime1) != 0)
-            step = 3 * prime1;
-        else {
-            if ((lengthcount % prime2) != 0)
-                step = 3 * prime2;
-            else {
-                if ((lengthcount % prime3) != 0)
-                    step = 3 * prime3;
-                else
-                    step = 3 * prime4;
-            }
-        }
-
-        i = 0;
-        while (i < samplepixels) {
-            b = (p[pix + 0] & 0xff) << netbiasshift;
-            g = (p[pix + 1] & 0xff) << netbiasshift;
-            r = (p[pix + 2] & 0xff) << netbiasshift;
-            j = contest(b, g, r);
-
-            altersingle(alpha, j, b, g, r);
-            if (rad != 0)
-                alterneigh(rad, j, b, g, r); /* alter neighbours */
-
-            pix += step;
-            if (pix >= lim)
-                pix -= lengthcount;
-
-            i++;
-            if (delta == 0)
-                delta = 1;
-            if (i % delta == 0) {
-                alpha -= alpha / alphadec;
-                radius -= radius / radiusdec;
-                rad = radius >> radiusbiasshift;
-                if (rad <= 1)
-                    rad = 0;
-                for (j = 0; j < rad; j++)
-                    radpower[j] = alpha * (((rad * rad - j * j) * radbias) / (rad * rad));
-            }
-        }
-        // fprintf(stderr,"finished 1D learning: final alpha=%f
-        // !\n",((float)alpha)/initalpha);
-    }
-
-    /*
-     * Search for BGR values 0..255 (after net is unbiased) and return colour
-     * index
-     * ----------------------------------------------------------------------------
-     */
-    public int map(int b, int g, int r) {
-
-        int i, j, dist, a, bestd;
-        int[] p;
-        int best;
-
-        bestd = 1000; /* biggest possible dist is 256*3 */
-        best = -1;
-        i = netindex[g]; /* index on g */
-        j = i - 1; /* start at netindex[g] and work outwards */
-
-        while ((i < netsize) || (j >= 0)) {
-            if (i < netsize) {
-                p = network[i];
-                dist = p[1] - g; /* inx key */
-                if (dist >= bestd)
-                    i = netsize; /* stop iter */
-                else {
-                    i++;
-                    if (dist < 0)
-                        dist = -dist;
-                    a = p[0] - b;
-                    if (a < 0)
-                        a = -a;
-                    dist += a;
-                    if (dist < bestd) {
-                        a = p[2] - r;
-                        if (a < 0)
-                            a = -a;
-                        dist += a;
-                        if (dist < bestd) {
-                            bestd = dist;
-                            best = p[3];
-                        }
-                    }
-                }
-            }
-            if (j >= 0) {
-                p = network[j];
-                dist = g - p[1]; /* inx key - reverse dif */
-                if (dist >= bestd)
-                    j = -1; /* stop iter */
-                else {
-                    j--;
-                    if (dist < 0)
-                        dist = -dist;
-                    a = p[0] - b;
-                    if (a < 0)
-                        a = -a;
-                    dist += a;
-                    if (dist < bestd) {
-                        a = p[2] - r;
-                        if (a < 0)
-                            a = -a;
-                        dist += a;
-                        if (dist < bestd) {
-                            bestd = dist;
-                            best = p[3];
-                        }
-                    }
-                }
-            }
-        }
-        return (best);
-    }
-
-    public byte[] process() {
-        learn();
-        unbiasnet();
-        inxbuild();
-        return colorMap();
-    }
-
-    /*
-     * Unbias network to give byte values 0..255 and record position i to prepare
-     * for sort
-     * -----------------------------------------------------------------------------------
-     */
-    public void unbiasnet() {
-
-        int i, j;
-
-        for (i = 0; i < netsize; i++) {
-            network[i][0] >>= netbiasshift;
-            network[i][1] >>= netbiasshift;
-            network[i][2] >>= netbiasshift;
-            network[i][3] = i; /* record colour no */
-        }
-    }
-
-    /*
-     * Move adjacent neurons by precomputed alpha*(1-((i-j)^2/[r]^2)) in
-     * radpower[|i-j|]
-     * ---------------------------------------------------------------------------------
-     */
-    protected void alterneigh(int rad, int i, int b, int g, int r) {
-
-        int j, k, lo, hi, a, m;
-        int[] p;
-
-        lo = i - rad;
-        if (lo < -1)
-            lo = -1;
-        hi = i + rad;
-        if (hi > netsize)
-            hi = netsize;
-
-        j = i + 1;
-        k = i - 1;
-        m = 1;
-        while ((j < hi) || (k > lo)) {
-            a = radpower[m++];
-            if (j < hi) {
-                p = network[j++];
-                try {
-                    p[0] -= (a * (p[0] - b)) / alpharadbias;
-                    p[1] -= (a * (p[1] - g)) / alpharadbias;
-                    p[2] -= (a * (p[2] - r)) / alpharadbias;
-                } catch (Exception e) {
-                } // prevents 1.3 miscompilation
-            }
-            if (k > lo) {
-                p = network[k--];
-                try {
-                    p[0] -= (a * (p[0] - b)) / alpharadbias;
-                    p[1] -= (a * (p[1] - g)) / alpharadbias;
-                    p[2] -= (a * (p[2] - r)) / alpharadbias;
-                } catch (Exception e) {
-                }
-            }
-        }
-    }
-
-    /*
-     * Move neuron i towards biased (b,g,r) by factor alpha
-     * ----------------------------------------------------
-     */
-    protected void altersingle(int alpha, int i, int b, int g, int r) {
-
-    /* alter hit neuron */
-        int[] n = network[i];
-        n[0] -= (alpha * (n[0] - b)) / initalpha;
-        n[1] -= (alpha * (n[1] - g)) / initalpha;
-        n[2] -= (alpha * (n[2] - r)) / initalpha;
-    }
-
-    /*
-     * Search for biased BGR values ----------------------------
-     */
-    protected int contest(int b, int g, int r) {
-
-    /* finds closest neuron (min dist) and updates freq */
-    /* finds best neuron (min dist-bias) and returns position */
-    /* for frequently chosen neurons, freq[i] is high and bias[i] is negative */
-    /* bias[i] = gamma*((1/netsize)-freq[i]) */
-
-        int i, dist, a, biasdist, betafreq;
-        int bestpos, bestbiaspos, bestd, bestbiasd;
-        int[] n;
-
-        bestd = ~(((int) 1) << 31);
-        bestbiasd = bestd;
-        bestpos = -1;
-        bestbiaspos = bestpos;
-
-        for (i = 0; i < netsize; i++) {
-            n = network[i];
-            dist = n[0] - b;
-            if (dist < 0)
-                dist = -dist;
-            a = n[1] - g;
-            if (a < 0)
-                a = -a;
-            dist += a;
-            a = n[2] - r;
-            if (a < 0)
-                a = -a;
-            dist += a;
-            if (dist < bestd) {
-                bestd = dist;
-                bestpos = i;
-            }
-            biasdist = dist - ((bias[i]) >> (intbiasshift - netbiasshift));
-            if (biasdist < bestbiasd) {
-                bestbiasd = biasdist;
-                bestbiaspos = i;
-            }
-            betafreq = (freq[i] >> betashift);
-            freq[i] -= betafreq;
-            bias[i] += (betafreq << gammashift);
-        }
-        freq[bestpos] += beta;
-        bias[bestpos] -= betagamma;
-        return (bestbiaspos);
-    }
-}
\ No newline at end of file
diff --git a/tools/binary_size/libsupersize/dir_metadata.py b/tools/binary_size/libsupersize/dir_metadata.py
index b74634a0..717b1e2 100644
--- a/tools/binary_size/libsupersize/dir_metadata.py
+++ b/tools/binary_size/libsupersize/dir_metadata.py
@@ -3,6 +3,7 @@
 # found in the LICENSE file.
 """Helpers for determining Component of directories."""
 
+import functools
 import os
 import re
 
@@ -29,6 +30,7 @@
     return ''
 
 
+@functools.lru_cache
 class _ComponentLookupContext:
   def __init__(self, source_directory):
     self._mixins_cache = {}
@@ -104,6 +106,11 @@
   """
   context = _ComponentLookupContext(source_directory)
   for symbol in raw_symbols:
+    found_component = ''
     if symbol.source_path:
       found_component = context.ComponentForSourcePath(symbol.source_path)
-      symbol.component = found_component or default_component
+    if not found_component and symbol.object_path:
+      # Some generated files and not put under their target_gen_dir (common
+      # grit _resources_maps.cc files). So also look at object path.
+      found_component = context.ComponentForSourcePath(symbol.object_path)
+    symbol.component = found_component or default_component
diff --git a/tools/binary_size/libsupersize/testdata/Archive.golden b/tools/binary_size/libsupersize/testdata/Archive.golden
index 06fd0ab4..857ca80 100644
--- a/tools/binary_size/libsupersize/testdata/Archive.golden
+++ b/tools/binary_size/libsupersize/testdata/Archive.golden
@@ -4,7 +4,7 @@
 Section .text: has 100.0% of 35982248 bytes accounted for from 17 symbols. 0 bytes are unaccounted for.
 * Padding accounts for 13808 bytes (0.0%)
 * 0 have source paths. Accounts for 0 bytes (0.0%).
-* 0 have a component assigned. Accounts for 0 bytes (0.0%).
+* 13 have a component assigned. Accounts for 73978 bytes (0.2%).
 * 5 placeholders exist (symbols that start with **). Accounts for 35912296 bytes (99.8%).
 * 0 symbols have shared ownership.
 * 1 symbols are marked as "hot". Accounts for 12 bytes (0.0%).
@@ -14,29 +14,29 @@
 Section .rodata: has 100.0% of 5927652 bytes accounted for from 10 symbols. 0 bytes are unaccounted for.
 * Padding accounts for 2637935 bytes (44.5%)
 * 0 have source paths. Accounts for 0 bytes (0.0%).
-* 0 have a component assigned. Accounts for 0 bytes (0.0%).
+* 5 have a component assigned. Accounts for 676128 bytes (11.4%).
 * 5 placeholders exist (symbols that start with **). Accounts for 5251524 bytes (88.6%).
 * 0 string literals exist. Accounts for 0 bytes (0.0%) padding is 0 bytes.
 * 0 symbols have shared ownership.
 Large padding of 675992 between:
-  A) .rodata@284e398(size_without_padding=32,padding=0,full_name=chrome::mojom::FilePatcher::Name_,object_path=third_party/WebKit.a/sub/ContiguousContainer.o,source_path=,flags={},num_aliases=1,component=)
-  B) .rodata@28f3450(size_without_padding=48,padding=675992,full_name=kAnimationFrameTimeHistogramClassPath,object_path=third_party/WebKit.a/./../third_party/sub/PaintChunker.o,source_path=,flags={anon},num_aliases=1,component=)
+  A) .rodata@284e398(size_without_padding=32,padding=0,full_name=chrome::mojom::FilePatcher::Name_,object_path=third_party/WebKit.a/sub/ContiguousContainer.o,source_path=,flags={},num_aliases=1,component=Internal>Android)
+  B) .rodata@28f3450(size_without_padding=48,padding=675992,full_name=kAnimationFrameTimeHistogramClassPath,object_path=third_party/WebKit.a/./../third_party/sub/PaintChunker.o,source_path=,flags={anon},num_aliases=1,component=Internal>Android)
 Section .data.rel.ro: has 100.0% of 1065224 bytes accounted for from 4 symbols. 0 bytes are unaccounted for.
 * Padding accounts for 0 bytes (0.0%)
 * 0 have source paths. Accounts for 0 bytes (0.0%).
-* 0 have a component assigned. Accounts for 0 bytes (0.0%).
+* 3 have a component assigned. Accounts for 92 bytes (0.0%).
 * 1 placeholders exist (symbols that start with **). Accounts for 1065132 bytes (100.0%).
 * 0 symbols have shared ownership.
 Section .data: has 100.0% of 101768 bytes accounted for from 6 symbols. 0 bytes are unaccounted for.
 * Padding accounts for 0 bytes (0.0%)
 * 0 have source paths. Accounts for 0 bytes (0.0%).
-* 0 have a component assigned. Accounts for 0 bytes (0.0%).
+* 5 have a component assigned. Accounts for 168 bytes (0.2%).
 * 1 placeholders exist (symbols that start with **). Accounts for 101600 bytes (99.8%).
 * 0 symbols have shared ownership.
 Section .bss: has 40.3% of 524520 bytes accounted for from 6 symbols. 775936 bytes are unaccounted for.
 * Padding accounts for 196 bytes (0.0%)
 * 0 have source paths. Accounts for 0 bytes (0.0%).
-* 0 have a component assigned. Accounts for 0 bytes (0.0%).
+* 6 have a component assigned. Accounts for 524520 bytes (100.0%).
 * 0 symbols have shared ownership.
 Section .dex: 0 bytes from 0 symbols.
 * Padding accounts for 0 bytes (0.0%)
@@ -64,54 +64,54 @@
 * 0 have a component assigned. Accounts for 0 bytes (0.0%).
 * 22 placeholders exist (symbols that start with **). Accounts for 56448494 bytes (100.0%).
 * 0 symbols have shared ownership.
-.text@28d900(size_without_padding=16,padding=0,full_name=_GLOBAL__sub_I_page_allocator.cc,object_path=base/base/page_allocator.o,source_path=,flags={startup},num_aliases=1,component=)
+.text@28d900(size_without_padding=16,padding=0,full_name=_GLOBAL__sub_I_page_allocator.cc,object_path=base/base/page_allocator.o,source_path=,flags={startup},num_aliases=1,component=Blink>Internal)
 .text@28d910(size_without_padding=56,padding=0,full_name=_GLOBAL__sub_I_bbr_sender.cc,object_path=$SYSTEM/path.a/foo.o,source_path=,flags={startup},num_aliases=1,component=)
-.text@28d948(size_without_padding=28,padding=0,full_name=_GLOBAL__sub_I_pacing_sender.cc,object_path=base/base/page_allocator.o,source_path=,flags={startup},num_aliases=1,component=)
-.text@28d964(size_without_padding=38,padding=0,full_name=extFromUUseMapping(signed char, unsigned int, int),object_path=base/base/page_allocator.o,source_path=,flags={},num_aliases=1,component=)
-.text@28d98a(size_without_padding=32,padding=0,full_name=extFromUUseMapping(aj, int),object_path=base/base/page_allocator.o,source_path=,flags={},num_aliases=1,component=)
+.text@28d948(size_without_padding=28,padding=0,full_name=_GLOBAL__sub_I_pacing_sender.cc,object_path=base/base/page_allocator.o,source_path=,flags={startup},num_aliases=1,component=Blink>Internal)
+.text@28d964(size_without_padding=38,padding=0,full_name=extFromUUseMapping(signed char, unsigned int, int),object_path=base/base/page_allocator.o,source_path=,flags={},num_aliases=1,component=Blink>Internal)
+.text@28d98a(size_without_padding=32,padding=0,full_name=extFromUUseMapping(aj, int),object_path=base/base/page_allocator.o,source_path=,flags={},num_aliases=1,component=Blink>Internal)
 .text@28f000(size_without_padding=0,padding=5718,full_name=** symbol gap 0,object_path=,source_path=,flags={},num_aliases=1,component=)
-.text@28f000(size_without_padding=448,padding=0,full_name=ucnv_extMatchFromU(int const*, int, unsigned short const*, int, unsigned short const*, int, unsigned int*, signed char, signed char),object_path=third_party/icu/icuuc/ucnv_ext.o,source_path=,flags={},num_aliases=1,component=)
-.text@28f1c8(size_without_padding=20,padding=8,full_name=_GLOBAL__sub_I_SkDeviceProfile.cpp,object_path=third_party/icu/icuuc/ucnv_ext.o,source_path=,flags={startup},num_aliases=1,component=)
-.text@28f1e0(size_without_padding=69120,padding=4,full_name=foo_bar,object_path=third_party/icu/icuuc/ucnv_ext.o,source_path=,flags={unlikely},num_aliases=1,component=)
-.text@2a0000(size_without_padding=16,padding=32,full_name=blink::ContiguousContainerBase::shrinkToFit(),object_path=third_party/WebKit.a/./../third_party/sub/PaintChunker.o,source_path=,flags={},num_aliases=1,component=)
-.text@2a0010(size_without_padding=12,padding=0,full_name=blink::ContiguousContainerBase::shrinkToFit(),object_path=third_party/WebKit.a/./../third_party/sub/PaintChunker.o,source_path=,flags={clone,hot},num_aliases=1,component=)
-.text@2a0020(size_without_padding=24,padding=4,full_name=blink::ContiguousContainerBase::ContiguousContainerBase(blink::ContiguousContainerBase&&),object_path=third_party/WebKit.a/sub/ContiguousContainer.o,source_path=,flags={},num_aliases=1,component=)
+.text@28f000(size_without_padding=448,padding=0,full_name=ucnv_extMatchFromU(int const*, int, unsigned short const*, int, unsigned short const*, int, unsigned int*, signed char, signed char),object_path=third_party/icu/icuuc/ucnv_ext.o,source_path=,flags={},num_aliases=1,component=Internal>Android)
+.text@28f1c8(size_without_padding=20,padding=8,full_name=_GLOBAL__sub_I_SkDeviceProfile.cpp,object_path=third_party/icu/icuuc/ucnv_ext.o,source_path=,flags={startup},num_aliases=1,component=Internal>Android)
+.text@28f1e0(size_without_padding=69120,padding=4,full_name=foo_bar,object_path=third_party/icu/icuuc/ucnv_ext.o,source_path=,flags={unlikely},num_aliases=1,component=Internal>Android)
+.text@2a0000(size_without_padding=16,padding=32,full_name=blink::ContiguousContainerBase::shrinkToFit(),object_path=third_party/WebKit.a/./../third_party/sub/PaintChunker.o,source_path=,flags={},num_aliases=1,component=Internal>Android)
+.text@2a0010(size_without_padding=12,padding=0,full_name=blink::ContiguousContainerBase::shrinkToFit(),object_path=third_party/WebKit.a/./../third_party/sub/PaintChunker.o,source_path=,flags={clone,hot},num_aliases=1,component=Internal>Android)
+.text@2a0020(size_without_padding=24,padding=4,full_name=blink::ContiguousContainerBase::ContiguousContainerBase(blink::ContiguousContainerBase&&),object_path=third_party/WebKit.a/sub/ContiguousContainer.o,source_path=,flags={},num_aliases=1,component=Internal>Android)
 .text@2a1000(size_without_padding=0,padding=4040,full_name=** symbol gap 1,object_path=,source_path=,flags={},num_aliases=1,component=)
-.text@2a1000(size_without_padding=94,padding=0,full_name=blink::PaintChunker::releasePaintChunks(),object_path=third_party/WebKit.a/sub/ContiguousContainer.o,source_path=,flags={anon,clone},num_aliases=1,component=)
-.text@2a2000(size_without_padding=32,padding=4002,full_name=** outlined function,object_path=third_party/WebKit.a/sub/ContiguousContainer.o,source_path=,flags={},num_aliases=1,component=)
-.text@2a2020(size_without_padding=48,padding=0,full_name=** outlined function,object_path=third_party/WebKit.a/sub/ContiguousContainer.o,source_path=,flags={},num_aliases=1,component=)
+.text@2a1000(size_without_padding=94,padding=0,full_name=blink::PaintChunker::releasePaintChunks(),object_path=third_party/WebKit.a/sub/ContiguousContainer.o,source_path=,flags={anon,clone},num_aliases=1,component=Internal>Android)
+.text@2a2000(size_without_padding=32,padding=4002,full_name=** outlined function,object_path=third_party/WebKit.a/sub/ContiguousContainer.o,source_path=,flags={},num_aliases=1,component=Internal>Android)
+.text@2a2020(size_without_padding=48,padding=0,full_name=** outlined function,object_path=third_party/WebKit.a/sub/ContiguousContainer.o,source_path=,flags={},num_aliases=1,component=Internal>Android)
 .text@2a2050(size_without_padding=35898456,padding=0,full_name=** .text (unattributed),object_path=,source_path=,flags={},num_aliases=1,component=)
 .rodata@266e600(size_without_padding=32,padding=0,full_name=** merge strings,object_path=,source_path=,flags={},num_aliases=1,component=)
 .rodata@266e630(size_without_padding=16,padding=16,full_name=** merge strings,object_path=,source_path=,flags={},num_aliases=1,component=)
 .rodata@284d600(size_without_padding=3425,padding=1961920,full_name=** merge constants,object_path=,source_path=,flags={},num_aliases=1,component=)
 .rodata@284e364(size_without_padding=0,padding=3,full_name=** symbol gap 0,object_path=,source_path=,flags={},num_aliases=1,component=)
-.rodata@284e364(size_without_padding=8,padding=0,full_name=,object_path=base/base/page_allocator.o,source_path=,flags={},num_aliases=1,component=)
-.rodata@284e370(size_without_padding=40,padding=4,full_name=Name,object_path=base/base/page_allocator.o,source_path=,flags={},num_aliases=1,component=)
-.rodata@284e398(size_without_padding=32,padding=0,full_name=chrome::mojom::FilePatcher::Name_,object_path=third_party/WebKit.a/sub/ContiguousContainer.o,source_path=,flags={},num_aliases=1,component=)
-.rodata@28f3450(size_without_padding=48,padding=675992,full_name=kAnimationFrameTimeHistogramClassPath,object_path=third_party/WebKit.a/./../third_party/sub/PaintChunker.o,source_path=,flags={anon},num_aliases=1,component=)
-.rodata@28f3480(size_without_padding=4,padding=0,full_name=blink::CSSValueKeywordsHash::findValueImpl(char const*, unsigned int)::value_word_list,object_path=third_party/WebKit.a/./../third_party/sub/PaintChunker.o,source_path=,flags={anon},num_aliases=1,component=)
+.rodata@284e364(size_without_padding=8,padding=0,full_name=,object_path=base/base/page_allocator.o,source_path=,flags={},num_aliases=1,component=Blink>Internal)
+.rodata@284e370(size_without_padding=40,padding=4,full_name=Name,object_path=base/base/page_allocator.o,source_path=,flags={},num_aliases=1,component=Blink>Internal)
+.rodata@284e398(size_without_padding=32,padding=0,full_name=chrome::mojom::FilePatcher::Name_,object_path=third_party/WebKit.a/sub/ContiguousContainer.o,source_path=,flags={},num_aliases=1,component=Internal>Android)
+.rodata@28f3450(size_without_padding=48,padding=675992,full_name=kAnimationFrameTimeHistogramClassPath,object_path=third_party/WebKit.a/./../third_party/sub/PaintChunker.o,source_path=,flags={anon},num_aliases=1,component=Internal>Android)
+.rodata@28f3480(size_without_padding=4,padding=0,full_name=blink::CSSValueKeywordsHash::findValueImpl(char const*, unsigned int)::value_word_list,object_path=third_party/WebKit.a/./../third_party/sub/PaintChunker.o,source_path=,flags={anon},num_aliases=1,component=Internal>Android)
 .rodata@28f3484(size_without_padding=3286112,padding=0,full_name=** .rodata (unattributed),object_path=,source_path=,flags={},num_aliases=1,component=)
-.data.rel.ro.local@2c176f0(size_without_padding=56,padding=0,full_name=ChromeMainDelegate [vtable],object_path=third_party/icu/icuuc/ucnv_ext.o,source_path=,flags={},num_aliases=1,component=)
-.data.rel.ro.local@2c17728(size_without_padding=24,padding=0,full_name=chrome::mojom::FieldTrialRecorder [vtable],object_path=third_party/icu/icuuc/ucnv_ext.o,source_path=,flags={},num_aliases=1,component=)
-.data.rel.ro.local@2c17740(size_without_padding=789904,padding=0,full_name=chrome::mojom::FieldTrialRecorderProxy [vtable],object_path=third_party/WebKit.a/sub/ContiguousContainer.o,source_path=,flags={},num_aliases=1,component=)
-.data.rel.ro.local@2cd84e0(size_without_padding=16,padding=16,full_name=.Lswitch.table.45,object_path=third_party/gvr-android-sdk/libgvr_shim_static_arm.a/libcontroller_api_impl.a_controller_api_impl.o,source_path=,flags={},num_aliases=1,component=)
-.data.rel.ro.local@2cd84f0(size_without_padding=8,padding=0,full_name=kSystemClassPrefixes,object_path=third_party/gvr-android-sdk/libgvr_shim_static_arm.a/libport_android_jni.a_jni_utils.o,source_path=,flags={anon},num_aliases=1,component=)
-.data.rel.ro@2cd8500(size_without_padding=56,padding=0,full_name=ChromeMainDelegateAndroid [vtable],object_path=third_party/WebKit.a/./../third_party/sub/PaintChunker.o,source_path=,flags={},num_aliases=1,component=)
-.data.rel.ro@2cd8538(size_without_padding=24,padding=0,full_name=mojo::MessageReceiver [vtable],object_path=base/base/page_allocator.o,source_path=,flags={},num_aliases=1,component=)
-.data.rel.ro@2cd8550(size_without_padding=12,padding=0,full_name=kMethodsAnimationFrameTimeHistogram,object_path=base/base/page_allocator.o,source_path=,flags={},num_aliases=1,component=)
+.data.rel.ro.local@2c176f0(size_without_padding=56,padding=0,full_name=ChromeMainDelegate [vtable],object_path=third_party/icu/icuuc/ucnv_ext.o,source_path=,flags={},num_aliases=1,component=Internal>Android)
+.data.rel.ro.local@2c17728(size_without_padding=24,padding=0,full_name=chrome::mojom::FieldTrialRecorder [vtable],object_path=third_party/icu/icuuc/ucnv_ext.o,source_path=,flags={},num_aliases=1,component=Internal>Android)
+.data.rel.ro.local@2c17740(size_without_padding=789904,padding=0,full_name=chrome::mojom::FieldTrialRecorderProxy [vtable],object_path=third_party/WebKit.a/sub/ContiguousContainer.o,source_path=,flags={},num_aliases=1,component=Internal>Android)
+.data.rel.ro.local@2cd84e0(size_without_padding=16,padding=16,full_name=.Lswitch.table.45,object_path=third_party/gvr-android-sdk/libgvr_shim_static_arm.a/libcontroller_api_impl.a_controller_api_impl.o,source_path=,flags={},num_aliases=1,component=Internal>Android)
+.data.rel.ro.local@2cd84f0(size_without_padding=8,padding=0,full_name=kSystemClassPrefixes,object_path=third_party/gvr-android-sdk/libgvr_shim_static_arm.a/libport_android_jni.a_jni_utils.o,source_path=,flags={anon},num_aliases=1,component=Internal>Android)
+.data.rel.ro@2cd8500(size_without_padding=56,padding=0,full_name=ChromeMainDelegateAndroid [vtable],object_path=third_party/WebKit.a/./../third_party/sub/PaintChunker.o,source_path=,flags={},num_aliases=1,component=Internal>Android)
+.data.rel.ro@2cd8538(size_without_padding=24,padding=0,full_name=mojo::MessageReceiver [vtable],object_path=base/base/page_allocator.o,source_path=,flags={},num_aliases=1,component=Blink>Internal)
+.data.rel.ro@2cd8550(size_without_padding=12,padding=0,full_name=kMethodsAnimationFrameTimeHistogram,object_path=base/base/page_allocator.o,source_path=,flags={},num_aliases=1,component=Blink>Internal)
 .data.rel.ro@2cd855c(size_without_padding=1065132,padding=0,full_name=** .data.rel.ro (unattributed),object_path=,source_path=,flags={},num_aliases=1,component=)
-.data@2de7000(size_without_padding=4,padding=0,full_name=google::protobuf::internal::pLinuxKernelCmpxchg,object_path=base/base/page_allocator.o,source_path=,flags={},num_aliases=1,component=)
-.data@2de7004(size_without_padding=4,padding=0,full_name=google::protobuf::internal::pLinuxKernelMemoryBarrier,object_path=third_party/WebKit.a/sub/ContiguousContainer.o,source_path=,flags={},num_aliases=1,component=)
-.data@2de7008(size_without_padding=152,padding=0,full_name=base::android::kBaseRegisteredMethods,object_path=third_party/WebKit.a/sub/ContiguousContainer.o,source_path=,flags={rel},num_aliases=1,component=)
-.data@2de70a0(size_without_padding=4,padding=0,full_name=base::android::g_renderer_histogram_code,object_path=third_party/WebKit.a/sub/ContiguousContainer.o,source_path=,flags={anon},num_aliases=1,component=)
-.data@2de70a4(size_without_padding=4,padding=0,full_name=base::android::g_library_version_number,object_path=third_party/WebKit.a/sub/ContiguousContainer.o,source_path=,flags={anon,rel.loc},num_aliases=1,component=)
+.data@2de7000(size_without_padding=4,padding=0,full_name=google::protobuf::internal::pLinuxKernelCmpxchg,object_path=base/base/page_allocator.o,source_path=,flags={},num_aliases=1,component=Blink>Internal)
+.data@2de7004(size_without_padding=4,padding=0,full_name=google::protobuf::internal::pLinuxKernelMemoryBarrier,object_path=third_party/WebKit.a/sub/ContiguousContainer.o,source_path=,flags={},num_aliases=1,component=Internal>Android)
+.data@2de7008(size_without_padding=152,padding=0,full_name=base::android::kBaseRegisteredMethods,object_path=third_party/WebKit.a/sub/ContiguousContainer.o,source_path=,flags={rel},num_aliases=1,component=Internal>Android)
+.data@2de70a0(size_without_padding=4,padding=0,full_name=base::android::g_renderer_histogram_code,object_path=third_party/WebKit.a/sub/ContiguousContainer.o,source_path=,flags={anon},num_aliases=1,component=Internal>Android)
+.data@2de70a4(size_without_padding=4,padding=0,full_name=base::android::g_library_version_number,object_path=third_party/WebKit.a/sub/ContiguousContainer.o,source_path=,flags={anon,rel.loc},num_aliases=1,component=Internal>Android)
 .data@2de70a8(size_without_padding=101600,padding=0,full_name=** .data (unattributed),object_path=,source_path=,flags={},num_aliases=1,component=)
-.bss@0(size_without_padding=262144,padding=0,full_name=ff_cos_131072,object_path=third_party/ffmpeg/libffmpeg_internal.a/fft_float.o,source_path=,flags={},num_aliases=1,component=)
-.bss@0(size_without_padding=131072,padding=0,full_name=ff_cos_131072_fixed,object_path=third_party/ffmpeg/libffmpeg_internal.a/fft_fixed.o,source_path=,flags={},num_aliases=1,component=)
-.bss@0(size_without_padding=131072,padding=0,full_name=ff_cos_65536,object_path=third_party/ffmpeg/libffmpeg_internal.a/fft_float.o,source_path=,flags={},num_aliases=1,component=)
-.bss@2dffda0(size_without_padding=28,padding=0,full_name=g_chrome_content_browser_client,object_path=third_party/icu/icuuc/ucnv_ext.o,source_path=,flags={},num_aliases=1,component=)
-.bss@2dffe80(size_without_padding=4,padding=196,full_name=SaveHistogram(_JNIEnv*, base::android::JavaParamRef<_jobject*> const&, base::android::JavaParamRef<_jstring*> const&, base::android::JavaParamRef<_jlongArray*> const&, int)::atomic_histogram_pointer,object_path=third_party/icu/icuuc/ucnv_ext.o,source_path=,flags={},num_aliases=1,component=)
-.bss@2dffe84(size_without_padding=4,padding=0,full_name=g_AnimationFrameTimeHistogram_clazz,object_path=third_party/icu/icuuc/ucnv_ext.o,source_path=,flags={anon},num_aliases=1,component=)
+.bss@0(size_without_padding=262144,padding=0,full_name=ff_cos_131072,object_path=third_party/ffmpeg/libffmpeg_internal.a/fft_float.o,source_path=,flags={},num_aliases=1,component=Internal>Android)
+.bss@0(size_without_padding=131072,padding=0,full_name=ff_cos_131072_fixed,object_path=third_party/ffmpeg/libffmpeg_internal.a/fft_fixed.o,source_path=,flags={},num_aliases=1,component=Internal>Android)
+.bss@0(size_without_padding=131072,padding=0,full_name=ff_cos_65536,object_path=third_party/ffmpeg/libffmpeg_internal.a/fft_float.o,source_path=,flags={},num_aliases=1,component=Internal>Android)
+.bss@2dffda0(size_without_padding=28,padding=0,full_name=g_chrome_content_browser_client,object_path=third_party/icu/icuuc/ucnv_ext.o,source_path=,flags={},num_aliases=1,component=Internal>Android)
+.bss@2dffe80(size_without_padding=4,padding=196,full_name=SaveHistogram(_JNIEnv*, base::android::JavaParamRef<_jobject*> const&, base::android::JavaParamRef<_jstring*> const&, base::android::JavaParamRef<_jlongArray*> const&, int)::atomic_histogram_pointer,object_path=third_party/icu/icuuc/ucnv_ext.o,source_path=,flags={},num_aliases=1,component=Internal>Android)
+.bss@2dffe84(size_without_padding=4,padding=0,full_name=g_AnimationFrameTimeHistogram_clazz,object_path=third_party/icu/icuuc/ucnv_ext.o,source_path=,flags={anon},num_aliases=1,component=Internal>Android)
 .other@0(size_without_padding=60,padding=0,full_name=** ELF Section: .ARM.attributes,object_path=,source_path=,flags={},num_aliases=1,component=)
 .other@0(size_without_padding=1536456,padding=0,full_name=** ELF Section: .ARM.exidx,object_path=,source_path=,flags={},num_aliases=1,component=)
 .other@0(size_without_padding=183632,padding=0,full_name=** ELF Section: .ARM.extab,object_path=,source_path=,flags={},num_aliases=1,component=)
diff --git a/tools/binary_size/libsupersize/testdata/ArchiveContainers.golden b/tools/binary_size/libsupersize/testdata/ArchiveContainers.golden
index 85b2dc6..37c5736 100644
--- a/tools/binary_size/libsupersize/testdata/ArchiveContainers.golden
+++ b/tools/binary_size/libsupersize/testdata/ArchiveContainers.golden
@@ -71,8 +71,8 @@
 <Container1:/test.so (armeabi-v7a)>.data.rel.ro.local@2c176f0(size_without_padding=56,padding=0,full_name=ChromeMainDelegate [vtable],object_path=third_party/icu/icuuc/ucnv_ext.o,source_path=third_party/icu/ucnv_ext.c,flags={gen},num_aliases=1,component=Internal>Android)
 <Container1:/test.so (armeabi-v7a)>.data.rel.ro.local@2c17728(size_without_padding=24,padding=0,full_name=chrome::mojom::FieldTrialRecorder [vtable],object_path=third_party/icu/icuuc/ucnv_ext.o,source_path=third_party/icu/ucnv_ext.c,flags={gen},num_aliases=1,component=Internal>Android)
 <Container1:/test.so (armeabi-v7a)>.data.rel.ro.local@2c17740(size_without_padding=789904,padding=0,full_name=chrome::mojom::FieldTrialRecorderProxy [vtable],object_path=third_party/sub/ContiguousContainer.o,source_path=third_party/container/container.c,flags={},num_aliases=1,component=UI>Browser)
-<Container1:/test.so (armeabi-v7a)>.data.rel.ro.local@2cd84e0(size_without_padding=16,padding=16,full_name=.Lswitch.table.45,object_path=third_party/gvr-android-sdk/libgvr_shim_static_arm.a/libcontroller_api_impl.a_controller_api_impl.o,source_path=,flags={},num_aliases=1,component=)
-<Container1:/test.so (armeabi-v7a)>.data.rel.ro.local@2cd84f0(size_without_padding=8,padding=0,full_name=kSystemClassPrefixes,object_path=third_party/gvr-android-sdk/libgvr_shim_static_arm.a/libport_android_jni.a_jni_utils.o,source_path=,flags={anon},num_aliases=1,component=)
+<Container1:/test.so (armeabi-v7a)>.data.rel.ro.local@2cd84e0(size_without_padding=16,padding=16,full_name=.Lswitch.table.45,object_path=third_party/gvr-android-sdk/libgvr_shim_static_arm.a/libcontroller_api_impl.a_controller_api_impl.o,source_path=,flags={},num_aliases=1,component=Internal>Android)
+<Container1:/test.so (armeabi-v7a)>.data.rel.ro.local@2cd84f0(size_without_padding=8,padding=0,full_name=kSystemClassPrefixes,object_path=third_party/gvr-android-sdk/libgvr_shim_static_arm.a/libport_android_jni.a_jni_utils.o,source_path=,flags={anon},num_aliases=1,component=Internal>Android)
 <Container1:/test.so (armeabi-v7a)>.data.rel.ro@2cd8500(size_without_padding=56,padding=0,full_name=ChromeMainDelegateAndroid [vtable],object_path=third_party/sub/PaintChunker.o,source_path=third_party/paint.cc,flags={},num_aliases=1,component=Internal>Android)
 <Container1:/test.so (armeabi-v7a)>.data.rel.ro@2cd8538(size_without_padding=24,padding=0,full_name=mojo::MessageReceiver [vtable],object_path=base/base/page_allocator.o,source_path=base/page_allocator.cc,flags={},num_aliases=1,component=Blink>Internal)
 <Container1:/test.so (armeabi-v7a)>.data.rel.ro@2cd8550(size_without_padding=12,padding=0,full_name=kMethodsAnimationFrameTimeHistogram,object_path=base/base/page_allocator.o,source_path=base/page_allocator.cc,flags={},num_aliases=1,component=Blink>Internal)
@@ -150,8 +150,8 @@
 <Container2:>.data.rel.ro.local@2c176f0(size_without_padding=56,padding=0,full_name=ChromeMainDelegate [vtable],object_path=third_party/icu/icuuc/ucnv_ext.o,source_path=third_party/icu/ucnv_ext.c,flags={gen},num_aliases=1,component=Internal>Android)
 <Container2:>.data.rel.ro.local@2c17728(size_without_padding=24,padding=0,full_name=chrome::mojom::FieldTrialRecorder [vtable],object_path=third_party/icu/icuuc/ucnv_ext.o,source_path=third_party/icu/ucnv_ext.c,flags={gen},num_aliases=1,component=Internal>Android)
 <Container2:>.data.rel.ro.local@2c17740(size_without_padding=789904,padding=0,full_name=chrome::mojom::FieldTrialRecorderProxy [vtable],object_path=third_party/sub/ContiguousContainer.o,source_path=third_party/container/container.c,flags={},num_aliases=1,component=UI>Browser)
-<Container2:>.data.rel.ro.local@2cd84e0(size_without_padding=16,padding=16,full_name=.Lswitch.table.45,object_path=third_party/gvr-android-sdk/libgvr_shim_static_arm.a/libcontroller_api_impl.a_controller_api_impl.o,source_path=,flags={},num_aliases=1,component=)
-<Container2:>.data.rel.ro.local@2cd84f0(size_without_padding=8,padding=0,full_name=kSystemClassPrefixes,object_path=third_party/gvr-android-sdk/libgvr_shim_static_arm.a/libport_android_jni.a_jni_utils.o,source_path=,flags={anon},num_aliases=1,component=)
+<Container2:>.data.rel.ro.local@2cd84e0(size_without_padding=16,padding=16,full_name=.Lswitch.table.45,object_path=third_party/gvr-android-sdk/libgvr_shim_static_arm.a/libcontroller_api_impl.a_controller_api_impl.o,source_path=,flags={},num_aliases=1,component=Internal>Android)
+<Container2:>.data.rel.ro.local@2cd84f0(size_without_padding=8,padding=0,full_name=kSystemClassPrefixes,object_path=third_party/gvr-android-sdk/libgvr_shim_static_arm.a/libport_android_jni.a_jni_utils.o,source_path=,flags={anon},num_aliases=1,component=Internal>Android)
 <Container2:>.data.rel.ro@2cd8500(size_without_padding=56,padding=0,full_name=ChromeMainDelegateAndroid [vtable],object_path=third_party/sub/PaintChunker.o,source_path=third_party/paint.cc,flags={},num_aliases=1,component=Internal>Android)
 <Container2:>.data.rel.ro@2cd8538(size_without_padding=24,padding=0,full_name=mojo::MessageReceiver [vtable],object_path=base/base/page_allocator.o,source_path=base/page_allocator.cc,flags={},num_aliases=1,component=Blink>Internal)
 <Container2:>.data.rel.ro@2cd8550(size_without_padding=12,padding=0,full_name=kMethodsAnimationFrameTimeHistogram,object_path=base/base/page_allocator.o,source_path=base/page_allocator.cc,flags={},num_aliases=1,component=Blink>Internal)
diff --git a/tools/binary_size/libsupersize/testdata/Archive_Apk.golden b/tools/binary_size/libsupersize/testdata/Archive_Apk.golden
index 38ed6df..3b47a86 100644
--- a/tools/binary_size/libsupersize/testdata/Archive_Apk.golden
+++ b/tools/binary_size/libsupersize/testdata/Archive_Apk.golden
@@ -235,8 +235,8 @@
 <test.apk/test.so (armeabi-v7a)>.data.rel.ro.local@2c176f0(size_without_padding=56,padding=0,full_name=ChromeMainDelegate [vtable],object_path=third_party/icu/icuuc/ucnv_ext.o,source_path=third_party/icu/ucnv_ext.c,flags={gen},num_aliases=1,component=Internal>Android)
 <test.apk/test.so (armeabi-v7a)>.data.rel.ro.local@2c17728(size_without_padding=24,padding=0,full_name=chrome::mojom::FieldTrialRecorder [vtable],object_path=third_party/icu/icuuc/ucnv_ext.o,source_path=third_party/icu/ucnv_ext.c,flags={gen},num_aliases=1,component=Internal>Android)
 <test.apk/test.so (armeabi-v7a)>.data.rel.ro.local@2c17740(size_without_padding=789904,padding=0,full_name=chrome::mojom::FieldTrialRecorderProxy [vtable],object_path=third_party/sub/ContiguousContainer.o,source_path=third_party/container/container.c,flags={},num_aliases=1,component=UI>Browser)
-<test.apk/test.so (armeabi-v7a)>.data.rel.ro.local@2cd84e0(size_without_padding=16,padding=16,full_name=.Lswitch.table.45,object_path=third_party/gvr-android-sdk/libgvr_shim_static_arm.a/libcontroller_api_impl.a_controller_api_impl.o,source_path=,flags={},num_aliases=1,component=)
-<test.apk/test.so (armeabi-v7a)>.data.rel.ro.local@2cd84f0(size_without_padding=8,padding=0,full_name=kSystemClassPrefixes,object_path=third_party/gvr-android-sdk/libgvr_shim_static_arm.a/libport_android_jni.a_jni_utils.o,source_path=,flags={anon},num_aliases=1,component=)
+<test.apk/test.so (armeabi-v7a)>.data.rel.ro.local@2cd84e0(size_without_padding=16,padding=16,full_name=.Lswitch.table.45,object_path=third_party/gvr-android-sdk/libgvr_shim_static_arm.a/libcontroller_api_impl.a_controller_api_impl.o,source_path=,flags={},num_aliases=1,component=Internal>Android)
+<test.apk/test.so (armeabi-v7a)>.data.rel.ro.local@2cd84f0(size_without_padding=8,padding=0,full_name=kSystemClassPrefixes,object_path=third_party/gvr-android-sdk/libgvr_shim_static_arm.a/libport_android_jni.a_jni_utils.o,source_path=,flags={anon},num_aliases=1,component=Internal>Android)
 <test.apk/test.so (armeabi-v7a)>.data.rel.ro@2cd8500(size_without_padding=56,padding=0,full_name=ChromeMainDelegateAndroid [vtable],object_path=third_party/sub/PaintChunker.o,source_path=third_party/paint.cc,flags={},num_aliases=1,component=Internal>Android)
 <test.apk/test.so (armeabi-v7a)>.data.rel.ro@2cd8538(size_without_padding=24,padding=0,full_name=mojo::MessageReceiver [vtable],object_path=base/base/page_allocator.o,source_path=base/page_allocator.cc,flags={},num_aliases=1,component=Blink>Internal)
 <test.apk/test.so (armeabi-v7a)>.data.rel.ro@2cd8550(size_without_padding=12,padding=0,full_name=kMethodsAnimationFrameTimeHistogram,object_path=base/base/page_allocator.o,source_path=base/page_allocator.cc,flags={},num_aliases=1,component=Blink>Internal)
diff --git a/tools/binary_size/libsupersize/testdata/Archive_Elf.golden b/tools/binary_size/libsupersize/testdata/Archive_Elf.golden
index afa097c..7a1d306 100644
--- a/tools/binary_size/libsupersize/testdata/Archive_Elf.golden
+++ b/tools/binary_size/libsupersize/testdata/Archive_Elf.golden
@@ -113,8 +113,8 @@
 .data.rel.ro.local@2c176f0(size_without_padding=56,padding=0,full_name=ChromeMainDelegate [vtable],object_path=third_party/icu/icuuc/ucnv_ext.o,source_path=third_party/icu/ucnv_ext.c,flags={gen},num_aliases=1,component=Internal>Android)
 .data.rel.ro.local@2c17728(size_without_padding=24,padding=0,full_name=chrome::mojom::FieldTrialRecorder [vtable],object_path=third_party/icu/icuuc/ucnv_ext.o,source_path=third_party/icu/ucnv_ext.c,flags={gen},num_aliases=1,component=Internal>Android)
 .data.rel.ro.local@2c17740(size_without_padding=789904,padding=0,full_name=chrome::mojom::FieldTrialRecorderProxy [vtable],object_path=third_party/sub/ContiguousContainer.o,source_path=third_party/container/container.c,flags={},num_aliases=1,component=UI>Browser)
-.data.rel.ro.local@2cd84e0(size_without_padding=16,padding=16,full_name=.Lswitch.table.45,object_path=third_party/gvr-android-sdk/libgvr_shim_static_arm.a/libcontroller_api_impl.a_controller_api_impl.o,source_path=,flags={},num_aliases=1,component=)
-.data.rel.ro.local@2cd84f0(size_without_padding=8,padding=0,full_name=kSystemClassPrefixes,object_path=third_party/gvr-android-sdk/libgvr_shim_static_arm.a/libport_android_jni.a_jni_utils.o,source_path=,flags={anon},num_aliases=1,component=)
+.data.rel.ro.local@2cd84e0(size_without_padding=16,padding=16,full_name=.Lswitch.table.45,object_path=third_party/gvr-android-sdk/libgvr_shim_static_arm.a/libcontroller_api_impl.a_controller_api_impl.o,source_path=,flags={},num_aliases=1,component=Internal>Android)
+.data.rel.ro.local@2cd84f0(size_without_padding=8,padding=0,full_name=kSystemClassPrefixes,object_path=third_party/gvr-android-sdk/libgvr_shim_static_arm.a/libport_android_jni.a_jni_utils.o,source_path=,flags={anon},num_aliases=1,component=Internal>Android)
 .data.rel.ro@2cd8500(size_without_padding=56,padding=0,full_name=ChromeMainDelegateAndroid [vtable],object_path=third_party/sub/PaintChunker.o,source_path=third_party/paint.cc,flags={},num_aliases=1,component=Internal>Android)
 .data.rel.ro@2cd8538(size_without_padding=24,padding=0,full_name=mojo::MessageReceiver [vtable],object_path=base/base/page_allocator.o,source_path=base/page_allocator.cc,flags={},num_aliases=1,component=Blink>Internal)
 .data.rel.ro@2cd8550(size_without_padding=12,padding=0,full_name=kMethodsAnimationFrameTimeHistogram,object_path=base/base/page_allocator.o,source_path=base/page_allocator.cc,flags={},num_aliases=1,component=Blink>Internal)
diff --git a/tools/binary_size/libsupersize/testdata/Archive_MinimalApks.golden b/tools/binary_size/libsupersize/testdata/Archive_MinimalApks.golden
index 4c359a91..76e1762 100644
--- a/tools/binary_size/libsupersize/testdata/Archive_MinimalApks.golden
+++ b/tools/binary_size/libsupersize/testdata/Archive_MinimalApks.golden
@@ -372,7 +372,7 @@
 Section .other: has 100.0% of 695 bytes accounted for from 2 symbols. 0 bytes are unaccounted for.
 * Padding accounts for 136 bytes (19.6%)
 * 1 have source paths. Accounts for 559 bytes (80.4%).
-* 1 have a component assigned. Accounts for 559 bytes (80.4%).
+* 2 have a component assigned. Accounts for 695 bytes (100.0%).
 * 0 symbols have shared ownership.
 <Bundle.minimal.apks/base.apk/test.so (armeabi-v7a)>.text@28d900(size_without_padding=16,padding=0,full_name=_GLOBAL__sub_I_page_allocator.cc,object_path=base/base/page_allocator.o,source_path=base/page_allocator.cc,flags={startup},num_aliases=1,component=Blink>Internal)
 <Bundle.minimal.apks/base.apk/test.so (armeabi-v7a)>.text@28d910(size_without_padding=56,padding=0,full_name=_GLOBAL__sub_I_bbr_sender.cc,object_path=$SYSTEM/path.a/foo.o,source_path=,flags={startup},num_aliases=2,component=)
@@ -412,8 +412,8 @@
 <Bundle.minimal.apks/base.apk/test.so (armeabi-v7a)>.data.rel.ro.local@2c176f0(size_without_padding=56,padding=0,full_name=ChromeMainDelegate [vtable],object_path=third_party/icu/icuuc/ucnv_ext.o,source_path=third_party/icu/ucnv_ext.c,flags={gen},num_aliases=1,component=Internal>Android)
 <Bundle.minimal.apks/base.apk/test.so (armeabi-v7a)>.data.rel.ro.local@2c17728(size_without_padding=24,padding=0,full_name=chrome::mojom::FieldTrialRecorder [vtable],object_path=third_party/icu/icuuc/ucnv_ext.o,source_path=third_party/icu/ucnv_ext.c,flags={gen},num_aliases=1,component=Internal>Android)
 <Bundle.minimal.apks/base.apk/test.so (armeabi-v7a)>.data.rel.ro.local@2c17740(size_without_padding=789904,padding=0,full_name=chrome::mojom::FieldTrialRecorderProxy [vtable],object_path=third_party/sub/ContiguousContainer.o,source_path=third_party/container/container.c,flags={},num_aliases=1,component=UI>Browser)
-<Bundle.minimal.apks/base.apk/test.so (armeabi-v7a)>.data.rel.ro.local@2cd84e0(size_without_padding=16,padding=16,full_name=.Lswitch.table.45,object_path=third_party/gvr-android-sdk/libgvr_shim_static_arm.a/libcontroller_api_impl.a_controller_api_impl.o,source_path=,flags={},num_aliases=1,component=)
-<Bundle.minimal.apks/base.apk/test.so (armeabi-v7a)>.data.rel.ro.local@2cd84f0(size_without_padding=8,padding=0,full_name=kSystemClassPrefixes,object_path=third_party/gvr-android-sdk/libgvr_shim_static_arm.a/libport_android_jni.a_jni_utils.o,source_path=,flags={anon},num_aliases=1,component=)
+<Bundle.minimal.apks/base.apk/test.so (armeabi-v7a)>.data.rel.ro.local@2cd84e0(size_without_padding=16,padding=16,full_name=.Lswitch.table.45,object_path=third_party/gvr-android-sdk/libgvr_shim_static_arm.a/libcontroller_api_impl.a_controller_api_impl.o,source_path=,flags={},num_aliases=1,component=Internal>Android)
+<Bundle.minimal.apks/base.apk/test.so (armeabi-v7a)>.data.rel.ro.local@2cd84f0(size_without_padding=8,padding=0,full_name=kSystemClassPrefixes,object_path=third_party/gvr-android-sdk/libgvr_shim_static_arm.a/libport_android_jni.a_jni_utils.o,source_path=,flags={anon},num_aliases=1,component=Internal>Android)
 <Bundle.minimal.apks/base.apk/test.so (armeabi-v7a)>.data.rel.ro@2cd8500(size_without_padding=56,padding=0,full_name=ChromeMainDelegateAndroid [vtable],object_path=third_party/sub/PaintChunker.o,source_path=third_party/paint.cc,flags={},num_aliases=1,component=Internal>Android)
 <Bundle.minimal.apks/base.apk/test.so (armeabi-v7a)>.data.rel.ro@2cd8538(size_without_padding=24,padding=0,full_name=mojo::MessageReceiver [vtable],object_path=base/base/page_allocator.o,source_path=base/page_allocator.cc,flags={},num_aliases=1,component=Blink>Internal)
 <Bundle.minimal.apks/base.apk/test.so (armeabi-v7a)>.data.rel.ro@2cd8550(size_without_padding=12,padding=0,full_name=kMethodsAnimationFrameTimeHistogram,object_path=base/base/page_allocator.o,source_path=base/page_allocator.cc,flags={},num_aliases=1,component=Blink>Internal)
@@ -711,4 +711,4 @@
 <Bundle.minimal.apks/not_on_demand.apk>.other@0(size_without_padding=560,padding=0,full_name=AndroidManifest.xml,object_path=,source_path=$APK/AndroidManifest.xml,flags={},num_aliases=1,component=)
 <Bundle.minimal.apks/not_on_demand.apk>.other@0(size_without_padding=0,padding=136,full_name=Overhead: APK file,object_path=,source_path=,flags={},num_aliases=1,component=)
 <Bundle.minimal.apks/on_demand.apk?>.other@0(size_without_padding=559,padding=0,full_name=AndroidManifest.xml,object_path=,source_path=$APK/AndroidManifest.xml,flags={},num_aliases=1,component=DEFAULT)
-<Bundle.minimal.apks/on_demand.apk?>.other@0(size_without_padding=0,padding=136,full_name=Overhead: APK file,object_path=,source_path=,flags={},num_aliases=1,component=)
+<Bundle.minimal.apks/on_demand.apk?>.other@0(size_without_padding=0,padding=136,full_name=Overhead: APK file,object_path=,source_path=,flags={},num_aliases=1,component=DEFAULT)
diff --git a/tools/binary_size/libsupersize/testdata/Archive_OutputDirectory.golden b/tools/binary_size/libsupersize/testdata/Archive_OutputDirectory.golden
index cfdbf666..edb21f5 100644
--- a/tools/binary_size/libsupersize/testdata/Archive_OutputDirectory.golden
+++ b/tools/binary_size/libsupersize/testdata/Archive_OutputDirectory.golden
@@ -98,8 +98,8 @@
 .data.rel.ro.local@2c176f0(size_without_padding=56,padding=0,full_name=ChromeMainDelegate [vtable],object_path=third_party/icu/icuuc/ucnv_ext.o,source_path=third_party/icu/ucnv_ext.c,flags={gen},num_aliases=1,component=Internal>Android)
 .data.rel.ro.local@2c17728(size_without_padding=24,padding=0,full_name=chrome::mojom::FieldTrialRecorder [vtable],object_path=third_party/icu/icuuc/ucnv_ext.o,source_path=third_party/icu/ucnv_ext.c,flags={gen},num_aliases=1,component=Internal>Android)
 .data.rel.ro.local@2c17740(size_without_padding=789904,padding=0,full_name=chrome::mojom::FieldTrialRecorderProxy [vtable],object_path=third_party/sub/ContiguousContainer.o,source_path=third_party/container/container.c,flags={},num_aliases=1,component=UI>Browser)
-.data.rel.ro.local@2cd84e0(size_without_padding=16,padding=16,full_name=.Lswitch.table.45,object_path=third_party/gvr-android-sdk/libgvr_shim_static_arm.a/libcontroller_api_impl.a_controller_api_impl.o,source_path=,flags={},num_aliases=1,component=)
-.data.rel.ro.local@2cd84f0(size_without_padding=8,padding=0,full_name=kSystemClassPrefixes,object_path=third_party/gvr-android-sdk/libgvr_shim_static_arm.a/libport_android_jni.a_jni_utils.o,source_path=,flags={anon},num_aliases=1,component=)
+.data.rel.ro.local@2cd84e0(size_without_padding=16,padding=16,full_name=.Lswitch.table.45,object_path=third_party/gvr-android-sdk/libgvr_shim_static_arm.a/libcontroller_api_impl.a_controller_api_impl.o,source_path=,flags={},num_aliases=1,component=Internal>Android)
+.data.rel.ro.local@2cd84f0(size_without_padding=8,padding=0,full_name=kSystemClassPrefixes,object_path=third_party/gvr-android-sdk/libgvr_shim_static_arm.a/libport_android_jni.a_jni_utils.o,source_path=,flags={anon},num_aliases=1,component=Internal>Android)
 .data.rel.ro@2cd8500(size_without_padding=56,padding=0,full_name=ChromeMainDelegateAndroid [vtable],object_path=third_party/sub/PaintChunker.o,source_path=third_party/paint.cc,flags={},num_aliases=1,component=Internal>Android)
 .data.rel.ro@2cd8538(size_without_padding=24,padding=0,full_name=mojo::MessageReceiver [vtable],object_path=base/base/page_allocator.o,source_path=base/page_allocator.cc,flags={},num_aliases=1,component=Blink>Internal)
 .data.rel.ro@2cd8550(size_without_padding=12,padding=0,full_name=kMethodsAnimationFrameTimeHistogram,object_path=base/base/page_allocator.o,source_path=base/page_allocator.cc,flags={},num_aliases=1,component=Blink>Internal)
diff --git a/tools/binary_size/libsupersize/testdata/Archive_Pak_Files.golden b/tools/binary_size/libsupersize/testdata/Archive_Pak_Files.golden
index 99ac06d..3ee3cfc 100644
--- a/tools/binary_size/libsupersize/testdata/Archive_Pak_Files.golden
+++ b/tools/binary_size/libsupersize/testdata/Archive_Pak_Files.golden
@@ -115,8 +115,8 @@
 .data.rel.ro.local@2c176f0(size_without_padding=56,padding=0,full_name=ChromeMainDelegate [vtable],object_path=third_party/icu/icuuc/ucnv_ext.o,source_path=third_party/icu/ucnv_ext.c,flags={gen},num_aliases=1,component=Internal>Android)
 .data.rel.ro.local@2c17728(size_without_padding=24,padding=0,full_name=chrome::mojom::FieldTrialRecorder [vtable],object_path=third_party/icu/icuuc/ucnv_ext.o,source_path=third_party/icu/ucnv_ext.c,flags={gen},num_aliases=1,component=Internal>Android)
 .data.rel.ro.local@2c17740(size_without_padding=789904,padding=0,full_name=chrome::mojom::FieldTrialRecorderProxy [vtable],object_path=third_party/sub/ContiguousContainer.o,source_path=third_party/container/container.c,flags={},num_aliases=1,component=UI>Browser)
-.data.rel.ro.local@2cd84e0(size_without_padding=16,padding=16,full_name=.Lswitch.table.45,object_path=third_party/gvr-android-sdk/libgvr_shim_static_arm.a/libcontroller_api_impl.a_controller_api_impl.o,source_path=,flags={},num_aliases=1,component=)
-.data.rel.ro.local@2cd84f0(size_without_padding=8,padding=0,full_name=kSystemClassPrefixes,object_path=third_party/gvr-android-sdk/libgvr_shim_static_arm.a/libport_android_jni.a_jni_utils.o,source_path=,flags={anon},num_aliases=1,component=)
+.data.rel.ro.local@2cd84e0(size_without_padding=16,padding=16,full_name=.Lswitch.table.45,object_path=third_party/gvr-android-sdk/libgvr_shim_static_arm.a/libcontroller_api_impl.a_controller_api_impl.o,source_path=,flags={},num_aliases=1,component=Internal>Android)
+.data.rel.ro.local@2cd84f0(size_without_padding=8,padding=0,full_name=kSystemClassPrefixes,object_path=third_party/gvr-android-sdk/libgvr_shim_static_arm.a/libport_android_jni.a_jni_utils.o,source_path=,flags={anon},num_aliases=1,component=Internal>Android)
 .data.rel.ro@2cd8500(size_without_padding=56,padding=0,full_name=ChromeMainDelegateAndroid [vtable],object_path=third_party/sub/PaintChunker.o,source_path=third_party/paint.cc,flags={},num_aliases=1,component=Internal>Android)
 .data.rel.ro@2cd8538(size_without_padding=24,padding=0,full_name=mojo::MessageReceiver [vtable],object_path=base/base/page_allocator.o,source_path=base/page_allocator.cc,flags={},num_aliases=1,component=Blink>Internal)
 .data.rel.ro@2cd8550(size_without_padding=12,padding=0,full_name=kMethodsAnimationFrameTimeHistogram,object_path=base/base/page_allocator.o,source_path=base/page_allocator.cc,flags={},num_aliases=1,component=Blink>Internal)
diff --git a/tools/cast3p/cast_core.version b/tools/cast3p/cast_core.version
index 1317a249..440c24e 100644
--- a/tools/cast3p/cast_core.version
+++ b/tools/cast3p/cast_core.version
@@ -1 +1 @@
-cast_20230205_2231_RC00
+cast_20230217_0600_RC00
diff --git a/tools/cast3p/runtime.version b/tools/cast3p/runtime.version
index 626e403..c00952a 100644
--- a/tools/cast3p/runtime.version
+++ b/tools/cast3p/runtime.version
@@ -1 +1 @@
-344536
+344759
diff --git a/tools/code_coverage/js_source_maps/merge_js_source_maps/merge_js_source_maps.gni b/tools/code_coverage/js_source_maps/merge_js_source_maps/merge_js_source_maps.gni
index 9262f9a..1a2f1cc 100644
--- a/tools/code_coverage/js_source_maps/merge_js_source_maps/merge_js_source_maps.gni
+++ b/tools/code_coverage/js_source_maps/merge_js_source_maps/merge_js_source_maps.gni
@@ -18,7 +18,11 @@
     args = [ "--manifest-files" ] +
            rebase_path(invoker.manifest_files, root_out_dir) + [ "--sources" ] +
            rebase_path(invoker.sources, root_out_dir) + [ "--outputs" ] +
-           rebase_path(invoker.outputs, root_out_dir)
+           rebase_path(invoker.outputs, root_out_dir) +
+           [
+             "--out-dir",
+             rebase_path(invoker.out_dir, root_build_dir),
+           ]
     inputs = [ "//tools/code_coverage/js_source_maps/merge_js_source_maps/merge_js_source_maps.js" ]
     foreach(manifest, invoker.manifest_files) {
       outputs += [ get_path_info(manifest, "dir") + "/" +
diff --git a/tools/code_coverage/js_source_maps/merge_js_source_maps/merge_js_source_maps.js b/tools/code_coverage/js_source_maps/merge_js_source_maps/merge_js_source_maps.js
index 8af05e81..76b69ee1 100644
--- a/tools/code_coverage/js_source_maps/merge_js_source_maps/merge_js_source_maps.js
+++ b/tools/code_coverage/js_source_maps/merge_js_source_maps/merge_js_source_maps.js
@@ -134,6 +134,8 @@
   const parser =
       new ArgumentParser({description: 'Merge multiple inlined sourcemaps'});
 
+  parser.add_argument(
+      '--out-dir', {help: 'Dir where all output files reside', required: true});
   parser.add_argument('--sources', {help: 'Input files', nargs: '*'});
   parser.add_argument('--outputs', {help: 'Output files', nargs: '*'});
   parser.add_argument(
@@ -143,15 +145,12 @@
   await processFiles(argv.sources, argv.outputs);
 
   if (argv.manifest_files) {
-    // TODO(crbug/1337530): Currently we just remove the final directory of the
-    // `base_dir` key. This is definitely brittle and also subject to changes
-    // made to the output directory. Consider updating this to be more robust.
     for (const manifestFile of argv.manifest_files) {
       try {
         const manifestFileContents =
             fs.readFileSync(manifestFile).toString('utf-8');
         const manifest = JSON.parse(manifestFileContents);
-        manifest.base_dir = path.parse(manifest.base_dir).dir;
+        manifest.base_dir = argv.out_dir;
         const parsedPath = path.parse(manifestFile);
         fs.writeFileSync(
             path.join(
@@ -165,4 +164,4 @@
   }
 }
 
-(async () => main())();
\ No newline at end of file
+(async () => main())();
diff --git a/tools/code_coverage/js_source_maps/merge_js_source_maps/merge_js_source_maps.py b/tools/code_coverage/js_source_maps/merge_js_source_maps/merge_js_source_maps.py
index a5725dd..bc97bad 100755
--- a/tools/code_coverage/js_source_maps/merge_js_source_maps/merge_js_source_maps.py
+++ b/tools/code_coverage/js_source_maps/merge_js_source_maps/merge_js_source_maps.py
@@ -18,14 +18,22 @@
 
 def main(argv):
   parser = argparse.ArgumentParser()
+  parser.add_argument('--out-dir', required=True)
   parser.add_argument('--sources', required=True, nargs="*")
   parser.add_argument('--outputs', required=True, nargs="*")
   parser.add_argument('--manifest-files', required=True, nargs="*")
   args = parser.parse_args(argv)
 
   node.RunNode([
-      str(_SOURCE_MAP_MERGER), '--manifest-files', *args.manifest_files,
-      '--sources', *args.sources, '--outputs', *args.outputs
+      str(_SOURCE_MAP_MERGER),
+      '--manifest-files',
+      *args.manifest_files,
+      '--sources',
+      *args.sources,
+      '--outputs',
+      *args.outputs,
+      '--out-dir',
+      args.out_dir,
   ])
 
 
diff --git a/tools/code_coverage/js_source_maps/merge_js_source_maps/test/merge_js_source_maps_test.py b/tools/code_coverage/js_source_maps/merge_js_source_maps/test/merge_js_source_maps_test.py
index f97cc9a..8965ea1 100755
--- a/tools/code_coverage/js_source_maps/merge_js_source_maps/test/merge_js_source_maps_test.py
+++ b/tools/code_coverage/js_source_maps/merge_js_source_maps/test/merge_js_source_maps_test.py
@@ -74,6 +74,8 @@
         str(input_file_name),
         '--outputs',
         str(output_file_name),
+        '--out-dir',
+        'tsc',
     ])
 
     source_map = None
@@ -173,6 +175,8 @@
         str(output_file_name),
         '--manifest-files',
         str(manifest_file),
+        '--out-dir',
+        'tsc',
     ])
 
     manifest_file_contents = '{"base_dir":"tsc"}'
diff --git a/tools/crates/gnrt/BUILD.gn b/tools/crates/gnrt/BUILD.gn
index 0fc3f6a..ea6c441 100644
--- a/tools/crates/gnrt/BUILD.gn
+++ b/tools/crates/gnrt/BUILD.gn
@@ -17,6 +17,7 @@
   crate_name = "gnrt_lib"
   crate_root = "lib.rs"
   sources = [
+    "config.rs",
     "crates.rs",
     "deps.rs",
     "download.rs",
diff --git a/tools/crates/gnrt/config.rs b/tools/crates/gnrt/config.rs
new file mode 100644
index 0000000..ea18692b
--- /dev/null
+++ b/tools/crates/gnrt/config.rs
@@ -0,0 +1,28 @@
+// 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.
+
+//! Parsing configuration file that customizes gnrt BUILD.gn output. Currently
+//! only used for std bindings.
+
+use std::collections::BTreeMap;
+
+use serde::Deserialize;
+
+/// Extra GN configuration for targets. Contains one entry for each crate with
+/// custom config.
+#[derive(Clone, Debug, Deserialize)]
+pub struct ConfigFile {
+    #[serde(flatten)]
+    pub per_lib_config: BTreeMap<String, PerLibConfig>,
+}
+
+#[derive(Clone, Debug, Deserialize)]
+pub struct PerLibConfig {
+    /// List of `cfg(...)` options for building this crate.
+    #[serde(default)]
+    pub cfg: Vec<String>,
+    /// List of compile-time environment variables for this crate.
+    #[serde(default)]
+    pub env: Vec<String>,
+}
diff --git a/tools/crates/gnrt/lib.rs b/tools/crates/gnrt/lib.rs
index 53368c4..445d305 100644
--- a/tools/crates/gnrt/lib.rs
+++ b/tools/crates/gnrt/lib.rs
@@ -2,6 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+pub mod config;
 pub mod crates;
 pub mod deps;
 pub mod download;
diff --git a/tools/crates/gnrt/main.rs b/tools/crates/gnrt/main.rs
index 032a293..e88189c4 100644
--- a/tools/crates/gnrt/main.rs
+++ b/tools/crates/gnrt/main.rs
@@ -297,11 +297,26 @@
 }
 
 fn generate_for_std(_args: &clap::ArgMatches, paths: &paths::ChromiumPaths) -> ExitCode {
+    // Load config file, which applies rustenv and cfg flags to some std crates.
+    let config_file_contents = std::fs::read_to_string(paths.std_config_file).unwrap();
+    let config: config::ConfigFile = toml::de::from_str(&config_file_contents).unwrap();
+
     // Run `cargo metadata` from the std package in the Rust source tree (which
     // is a workspace).
     let mut command = cargo_metadata::MetadataCommand::new();
     command.current_dir(paths.std_fake_root);
 
+    // Delete the Cargo.lock if it exists.
+    let mut std_fake_root_cargo_lock = paths.std_fake_root.to_path_buf();
+    std_fake_root_cargo_lock.push("Cargo.lock");
+    if let Err(e) = std::fs::remove_file(std_fake_root_cargo_lock) {
+        match e.kind() {
+            // Ignore if it already doesn't exist.
+            std::io::ErrorKind::NotFound => (),
+            _ => panic!("io error while deleting Cargo.lock: {e}"),
+        }
+    }
+
     // Use offline to constrain dependency resolution to those in the Rust src
     // tree and vendored crates. Ideally, we'd use "--locked" and use the
     // upstream Cargo.lock, but this is not straightforward since the rust-src
@@ -374,22 +389,36 @@
         }
     }
 
+    // Load extra cfg flags and environment variables needed while building std
+    // crates.
+    //
     // TODO(crbug.com/1368806):
-    // * Find a more sustainable way to supply these extra args, preferably in a
-    //   config file somewhere.
     // * Supply `-Zforce-unstable-if-unmarked` to all std crates, which ensures deps
     //   of std aren't visible to consumers.
     let mut extra_gn = HashMap::new();
-    // std requires:
-    // * cfg(backtrace_in_libstd) because it directly includes .rs files from the
-    //   backtrace code rather than including it as a dependency. backtrace's
-    //   implementation has special-purpose code to handle this.
-    // * STD_ENV_ARCH is referenced in architecture-dependent code.
-    extra_gn.insert("std".to_string(), "rustflags = [\"--cfg=backtrace_in_libstd\"]\nrustenv = [\"STD_ENV_ARCH=$rust_target_arch\"]".to_string());
-    // libc requires:
-    // * cfg(libc_align) for new enough rustc, which is normally provided by
-    //   build.rs but we don't run build scripts for std crates.
-    extra_gn.insert("libc".to_string(), "rustflags = [\"--cfg=libc_align\"]".to_string());
+    for (lib, config) in config.per_lib_config {
+        let mut rustflags = String::new();
+        let mut rustenv = String::new();
+        if !config.cfg.is_empty() {
+            rustflags = "rustflags = [".to_string();
+            rustflags.extend(config.cfg.into_iter().map(|cfg| format!("\"--cfg={cfg}\",")));
+            rustflags.push(']');
+        }
+        if !config.env.is_empty() {
+            rustenv = "rustenv = [".to_string();
+            rustenv.extend(config.env.into_iter().map(|env| format!("\"{env}\",")));
+            rustenv.push(']');
+        }
+
+        let strs = [rustflags.as_str(), rustenv.as_str()];
+        let extra = strs.join("\n");
+
+        assert!(
+            !extra.is_empty(),
+            "if a config entry was present, we should've generated something..."
+        );
+        extra_gn.insert(lib, extra);
+    }
 
     let build_file = gn::build_file_from_std_deps(dependencies.iter(), paths, &extra_gn);
     write_build_file(&paths.std_build.join("BUILD.gn"), &build_file).unwrap();
diff --git a/tools/crates/gnrt/paths.rs b/tools/crates/gnrt/paths.rs
index 08525d3b..d7b24ac 100644
--- a/tools/crates/gnrt/paths.rs
+++ b/tools/crates/gnrt/paths.rs
@@ -20,6 +20,7 @@
     pub rust_src: &'static Path,
     pub rust_src_vendor: &'static Path,
     pub rust_std: &'static Path,
+    pub std_config_file: &'static Path,
     pub std_build: &'static Path,
     pub std_fake_root: &'static Path,
 }
@@ -37,6 +38,7 @@
             rust_src: check_path(&cur_dir, RUST_SRC_DIR)?,
             rust_src_vendor: check_path(&cur_dir, RUST_SRC_VENDOR_DIR)?,
             rust_std: check_path(&cur_dir, RUST_STD_DIR)?,
+            std_config_file: check_path(&cur_dir, STD_CONFIG_FILE)?,
             std_build: check_path(&cur_dir, STD_BUILD_DIR)?,
             std_fake_root: check_path(&cur_dir, STD_FAKE_ROOT)?,
         })
@@ -54,7 +56,7 @@
 
 fn check_path<'a>(root: &Path, p_str: &'a str) -> io::Result<&'a Path> {
     let p = Path::new(p_str);
-    if !root.join(p).is_dir() {
+    if !root.join(p).exists() {
         return Err(io::Error::new(
             io::ErrorKind::Other,
             format!(
@@ -71,5 +73,6 @@
 static RUST_SRC_DIR: &str = "third_party/rust-toolchain/lib/rustlib/src/rust";
 static RUST_SRC_VENDOR_DIR: &str = "third_party/rust-toolchain/lib/rustlib/src/rust/vendor";
 static RUST_STD_DIR: &str = "third_party/rust-toolchain/lib/rustlib/src/rust/library/std";
+static STD_CONFIG_FILE: &str = "build/rust/std/gnrt_config.toml";
 static STD_BUILD_DIR: &str = "build/rust/std/rules";
 static STD_FAKE_ROOT: &str = "build/rust/std/fake_root";
diff --git a/tools/mac/show_mod_init_func.py b/tools/mac/show_mod_init_func.py
index b71881d..0886cd78 100755
--- a/tools/mac/show_mod_init_func.py
+++ b/tools/mac/show_mod_init_func.py
@@ -1,11 +1,13 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
 
 # Copyright 2016 The Chromium Authors
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
 """
-Prints the contents of the __DATA,__mod_init_func section of a Mach-O image.
+Prints the contents of the module initialization functions stored in sections
+matching the flag S_MOD_INIT_FUNC_POINTERS or S_INIT_FUNC_OFFSETS of a Mach-O
+image.
 
 Usage:
   tools/mac/show_mod_init_func.py out/gn/Chromium\ Framework.unstripped
@@ -14,52 +16,162 @@
 dump-static-initializers.py instead.
 """
 
-from __future__ import print_function
-
-import optparse
+import argparse
 import os
+import platform
+import re
 import subprocess
 import sys
 
 
+# From <mach-o/loader.h>
+# Section flag with only function pointers for initializers.
+S_MOD_INIT_FUNC_POINTERS = 0x9
+# Section flag with only 32-bit offsets to initializers.
+S_INIT_FUNC_OFFSETS = 0x16
+
+
+def GetArchitecture(binary, xcode_path):
+  """If the binary is a fat file with multiple architectures, return its
+  architecture that matches the host. If such an architecture is not present in
+  the fat file print an error and exit. If the binary is a thin file or a
+  single-architecture fat file, return the single architecture."""
+  if xcode_path:
+    lipo_path = os.path.join(xcode_path, 'Contents', 'Developer', 'Toolchains',
+                             'XcodeDefault.xctoolchain', 'usr', 'bin', 'lipo')
+  else:
+    lipo_path = 'lipo'
+
+  architectures = subprocess.check_output([lipo_path, '-archs', binary],
+                                          encoding='utf-8').strip().split(' ')
+  if len(architectures) == 1:
+    return architectures[0]
+
+  host_arch = platform.machine()
+  if host_arch in architectures:
+    return host_arch
+
+  raise Exception('Host architecture ' + host_arch +
+                  ' not present in fat binary')
+
+
+def GetTextBase(load_commands):
+  """Returns the base address of the __TEXT segment."""
+  return int(
+      re.search('segname __TEXT\n.*vmaddr (0x[0-9a-f]+)', load_commands,
+                re.MULTILINE).group(1), 16)
+
+
 def ShowModuleInitializers(binary, xcode_path):
   """Gathers the module initializers for |binary| and symbolizes the addresses.
   """
-  initializers = GetModuleInitializers(binary, xcode_path)
+  # Get the architecture to operate on.
+  architecture = GetArchitecture(binary, xcode_path)
+
+  initializers = GetModuleInitializers(binary, architecture, xcode_path)
   if not initializers:
     # atos will do work even if there are no addresses, so bail early.
     return
-  symbols = SymbolizeAddresses(binary, initializers, xcode_path)
+  symbols = SymbolizeAddresses(binary, architecture, initializers, xcode_path)
 
   print(binary)
   for initializer in zip(initializers, symbols):
     print('%s @ %s' % initializer)
 
 
-def GetModuleInitializers(binary, xcode_path):
+def GetStaticInitializerSection(load_commands):
+  """Returns the static initializer location based on the binary load commands.
+  Static initializers are stored in sections with flag S_MOD_INIT_FUNC_POINTERS
+  or S_INIT_FUNC_OFFSETS. Below are some expected names of the the (sectname,
+  segname,flags) that ld64 and lld would use:
+  - deployment target macOS < 10.15 or iOS 14:
+    (__mod_init_func,__DATA,S_MOD_INIT_FUNC_POINTERS)
+  - deployment target macOS >= 10.15 or iOS 14:
+    (__mod_init_func,__DATA_CONST,S_MOD_INIT_FUNC_POINTERS)
+  - ld64 with a deployment target macOS >= 12 or iOS >= 16 or lldb with
+    `-fixup_chains`:
+    (__init_offsets,__TEXT,S_INIT_FUNC_OFFSETS)"""
+  matches = re.findall(
+      r'sectname (.*)\n\s+segname (.*)\n(?:.|\n)*?flags (0x[0-9a-f]*)\n',
+      load_commands, re.MULTILINE)
+  sections = []
+  for sectname, segname, flags in matches:
+    flags = int(flags, 16)
+    if flags in (S_MOD_INIT_FUNC_POINTERS, S_INIT_FUNC_OFFSETS):
+      sections.append((sectname, segname, flags))
+  return sections
+
+
+def GetModuleInitializers(binary, architecture, xcode_path):
   """Parses the __DATA,__mod_init_func segment of |binary| and returns a list
   of string hexadecimal addresses of the module initializers.
   """
   if xcode_path:
-    otool_path = os.path.join(xcode_path, 'Contents', 'Developer',
-        'Toolchains', 'XcodeDefault.xctoolchain', 'usr', 'bin', 'otool')
+    otool_path = os.path.join(xcode_path, 'Contents', 'Developer', 'Toolchains',
+                              'XcodeDefault.xctoolchain', 'usr', 'bin', 'otool')
   else:
     otool_path = 'otool'
-  # The -v flag will display the addresses in a usable form (as opposed to
-  # just its on-disk little-endian byte representation).
-  otool = [otool_path, '-v', '-s', '__DATA', '__mod_init_func', binary]
-  lines = subprocess.check_output(otool).strip().split('\n')
 
-  # Skip the first two header lines and then get the address of the
-  # initializer in the second column. The first address is the address
-  # of the initializer pointer.
-  #   out/gn/Chromium Framework.unstripped:
-  #   Contents of (__DATA,__mod_init_func) section
-  #   0x0000000008761498 0x000000000385d120
-  return [line.split(' ')[1] for line in lines[2:]]
+  load_commands = subprocess.check_output(
+      [otool_path, '-l', '-arch', architecture, binary], encoding='utf-8')
+
+  static_initializer_sections = GetStaticInitializerSection(load_commands)
+  addresses = []
+  for sectname, segname, flags in static_initializer_sections:
+    # The -v flag will display the addresses in a usable form (as opposed to
+    # just its on-disk little-endian byte representation).
+    otool = [
+        otool_path, '-arch', architecture, '-v', '-s', segname, sectname, binary
+    ]
+    lines = subprocess.check_output(otool, encoding='utf-8').splitlines()
+    # Skip the first two header lines and then get the address of the
+    # initializer in the second column. The first address is the address of the
+    # initializer pointer.
+    #   out/gn/Chromium Framework.unstripped:
+    #   Contents of (__DATA,__mod_init_func) section
+    #   0x0000000008761498 0x000000000385d120
+    if flags == S_MOD_INIT_FUNC_POINTERS:
+      sect_address = [line.split(' ')[1] for line in lines[2:]]
+      addresses.extend(sect_address)
+      continue
+
+    # If otool adds a proper implementation for S_INIT_FUNC_OFFSETS the
+    # sections below building `sect_address` can be removed. The logic to add
+    # the __TEXT base address will remain.
+    if architecture not in ('arm64', 'x86_64'):
+      raise Exception(
+          "Parsing otool's S_INIT_FUNC_OFFSETS output on architectures other "
+          "than arm64 on x86_64 is unsupported.")
+
+    # Trim the warning that otool doesn't understand S_INIT_FUNC_OFFSETS.
+    lines = [i for i in lines if not i.startswith('Unknown section')]
+
+    # From https://github.com/apple-oss-distributions/cctools/blob/cctools-973.0.1/otool/ofile_print.c#L9553
+    if architecture == 'arm64':
+      # For arm64 otool hex dumps as 4-byte words.  Since the offsets
+      # in S_INIT_FUNC_OFFSETS arm64 are 32 bits simply trim the first column
+      sect_address = [line.split('\t')[1].strip() for line in lines[2:]]
+      sect_address = (' '.join(sect_address)).split(' ')
+
+    if architecture == 'x86_64':
+      # For x86_64 otool dumps as byte-oriented output. Here, trim the first
+      # column and recreate each 32 bit address from the 8 bit groups.
+      octets = [line.split('\t')[1].strip() for line in lines[2:]]
+      octets = (' '.join(octets)).split(' ')
+      sect_address = []
+      for i in range(0, len(octets), 4):
+        # Take four octets and interpret as little-endian.
+        sect_address.append(''.join(octets[i:i + 4][::-1]))
+
+    # S_INIT_FUNC_OFFSETS are __TEXT relative. Add the __TEXT base
+    # address to each initializer offset.
+    text_base = GetTextBase(load_commands)
+    sect_address = [hex(int(x, 16) + text_base) for x in sect_address]
+    addresses.extend(sect_address)
+  return addresses
 
 
-def SymbolizeAddresses(binary, addresses, xcode_path):
+def SymbolizeAddresses(binary, architecture, addresses, xcode_path):
   """Given a |binary| and a list of |addresses|, symbolizes them using atos.
   """
   if xcode_path:
@@ -68,26 +180,24 @@
   else:
     atos_path = 'atos'
 
-  atos = [atos_path, '-o', binary] + addresses
-  lines = subprocess.check_output(atos).strip().split('\n')
+  atos = [atos_path, '-arch', architecture, '-o', binary] + addresses
+  lines = subprocess.check_output(atos, encoding='utf-8').splitlines()
   return lines
 
 
-def Main():
-  parser = optparse.OptionParser(usage='%prog filename')
-  parser.add_option(
+def main(args):
+  parser = argparse.ArgumentParser()
+  parser.add_argument(
       '--xcode-path',
       default=None,
       help='Optional custom path to xcode binaries. By default, commands such '
       'as `otool` will be run as `/usr/bin/otool` which only works '
       'if there is a system-wide install of Xcode.')
-  opts, args = parser.parse_args()
-  if len(args) != 1:
-    parser.error('missing binary filename')
-    return 1
+  parser.add_argument('filename', nargs=1)
+  options = parser.parse_args(args)
 
-  ShowModuleInitializers(args[0], opts.xcode_path)
+  ShowModuleInitializers(options.filename[0], options.xcode_path)
   return 0
 
 if __name__ == '__main__':
-  sys.exit(Main())
+  sys.exit(main(sys.argv[1:]))
diff --git a/tools/mb/mb_config.pyl b/tools/mb/mb_config.pyl
index 07ffb29..9983ef3 100644
--- a/tools/mb/mb_config.pyl
+++ b/tools/mb/mb_config.pyl
@@ -406,6 +406,7 @@
       # TODO(crbug.com/1235218): remove after the migration.
       'chromeos-amd64-generic-rel (reclient compare)': 'chromeos_amd64-generic-vm_use_fake_dbus_clients_reclient',
       'chromeos-amd64-generic-rel (reclient)': 'chromeos_amd64-generic-vm_use_fake_dbus_clients_reclient',
+      'ios-blink-dbg-fyi': 'ios_simulator_blink_xctest',
       'ios-fieldtrial-rel': 'ios_simulator_debug_static_bot_xctest_arm64_reclient',
       'ios-m1-simulator': 'ios_simulator_debug_static_bot_xctest_arm64_reclient',
       'ios-m1-simulator-cronet': 'ios_cronet_xctest_arm64_reclient',
@@ -2884,6 +2885,10 @@
         'ios', 'ios_device', 'ios_cpu_arm64', 'ios_disable_code_signing', 'release_bot_reclient', 'xctest',
     ],
 
+    'ios_simulator_blink_xctest': [
+      'ios', 'ios_simulator', 'debug', 'use_blink', 'xctest'
+    ],
+
     'ios_simulator_code_coverage_partial_instrumentation_xctest': [
       'use_clang_coverage', 'debug_static_bot', 'x64', 'ios', 'ios_simulator', 'partial_code_coverage_instrumentation', 'xctest',
     ],
@@ -4695,6 +4700,10 @@
       'gn_args': 'use_chromium_rust_toolchain=false',
     },
 
+    'use_blink': {
+      'gn_args': 'use_blink=true'
+    },
+
     'use_clang_coverage': {
       'gn_args': 'use_clang_coverage=true',
     },
diff --git a/tools/mb/mb_config_expectations/chromium.fyi.json b/tools/mb/mb_config_expectations/chromium.fyi.json
index f35a774b..2276838 100644
--- a/tools/mb/mb_config_expectations/chromium.fyi.json
+++ b/tools/mb/mb_config_expectations/chromium.fyi.json
@@ -511,6 +511,15 @@
       "use_remoteexec": true
     }
   },
+  "ios-blink-dbg-fyi": {
+    "gn_args": {
+      "enable_run_ios_unittests_with_xctest": true,
+      "is_debug": true,
+      "target_environment": "simulator",
+      "target_os": "ios",
+      "use_blink": true
+    }
+  },
   "ios-fieldtrial-rel": {
     "gn_args": {
       "enable_run_ios_unittests_with_xctest": true,
diff --git a/tools/metrics/histograms/enums.xml b/tools/metrics/histograms/enums.xml
index cd2ae33..c0e995d 100644
--- a/tools/metrics/histograms/enums.xml
+++ b/tools/metrics/histograms/enums.xml
@@ -24244,6 +24244,8 @@
   <int value="48"
       label="Secondary toolbar swipe-up pending intent
              EXTRA_SECONDARY_TOOLBAR_SWIPE_UP_ACTION"/>
+  <int value="49"
+      label="Decoration type EXTRA_ACTIVITY_SIDE_SHEET_DECORATION_TYPE"/>
 </enum>
 
 <enum name="CustomTabsParallelRequestStatusOnStart">
@@ -32777,6 +32779,7 @@
   <int value="1076" label="DeviceActivityHeartbeatCollectionRateMs"/>
   <int value="1077" label="WallpaperGooglePhotosIntegrationEnabled"/>
   <int value="1078" label="WebRtcTextLogCollectionAllowed"/>
+  <int value="1079" label="EnforceLocalAnchorConstraintsEnabled"/>
 </enum>
 
 <enum name="EnterprisePoliciesSources">
diff --git a/tools/metrics/histograms/metadata/arc/histograms.xml b/tools/metrics/histograms/metadata/arc/histograms.xml
index e786cd0..af36f04a 100644
--- a/tools/metrics/histograms/metadata/arc/histograms.xml
+++ b/tools/metrics/histograms/metadata/arc/histograms.xml
@@ -2110,7 +2110,7 @@
 </histogram>
 
 <histogram name="Arc.Session.HasWebViewUsage" enum="Boolean"
-    expires_after="2023-02-01">
+    expires_after="2023-04-15">
   <owner>hungmn@google.com</owner>
   <owner>khmel@google.com</owner>
   <owner>arc-performance@google.com</owner>
diff --git a/tools/metrics/histograms/metadata/ash/histograms.xml b/tools/metrics/histograms/metadata/ash/histograms.xml
index 9810d0d..00618cd4 100644
--- a/tools/metrics/histograms/metadata/ash/histograms.xml
+++ b/tools/metrics/histograms/metadata/ash/histograms.xml
@@ -4423,13 +4423,14 @@
   <owner>cros-status-area-eng@google.com</owner>
   <summary>
     Recorded when an action is done on any FeaturePod in the quick settings
-    view. {FeaturePodBehavior} can be toggled to enable the feature, toggle to
-    disable this feature, or go to the feature's detailed page.
+    view. {FeaturePodBehavior} can be a toggle to enable the feature, toggle to
+    disable this feature, going to the feature's detailed page, or making the
+    button visible.
   </summary>
   <token key="FeaturePodBehavior">
-    <variant name="DiveIn" summary="Go to the feature detailed page"/>
-    <variant name="ToggleOff" summary="Toggle to disable the feature"/>
-    <variant name="ToggleOn" summary="Toggle to enable the feature"/>
+    <variant name="DiveIn" summary="Go to the feature's detailed page"/>
+    <variant name="ToggledOff" summary="Toggle to disable the feature"/>
+    <variant name="ToggledOn" summary="Toggle to enable the feature"/>
     <variant name="Visible" summary="The feature pod is shown"/>
   </token>
 </histogram>
@@ -5483,14 +5484,14 @@
   <owner>cros-status-area-eng@google.com</owner>
   <summary>
     Recorded when an action is done on any FeaturePod in the old quick settings
-    view. {FeaturePodBehavior} can be toggled to enable the feature, toggle to
-    disable this feature, go to the feature's detailed page, or the feature pod
-    is shown (visible).
+    view. {FeaturePodBehavior} can be a toggle to enable the feature, toggle to
+    disable this feature, going to the feature's detailed page, or making the
+    button visible.
   </summary>
   <token key="FeaturePodBehavior">
-    <variant name="DiveIn" summary="Go to the feature detailed page"/>
-    <variant name="ToggleOff" summary="Toggle to disable the feature"/>
-    <variant name="ToggleOn" summary="Toggle to enable the feature"/>
+    <variant name="DiveIn" summary="Go to the feature's detailed page"/>
+    <variant name="ToggledOff" summary="Toggle to disable the feature"/>
+    <variant name="ToggledOn" summary="Toggle to enable the feature"/>
     <variant name="Visible" summary="The feature pod is shown"/>
   </token>
 </histogram>
diff --git a/tools/metrics/histograms/metadata/history/histograms.xml b/tools/metrics/histograms/metadata/history/histograms.xml
index 0a8a9ec9..42e54fac 100644
--- a/tools/metrics/histograms/metadata/history/histograms.xml
+++ b/tools/metrics/histograms/metadata/history/histograms.xml
@@ -1224,6 +1224,8 @@
     <variant name="PersistClustersLatency"
         summary="receiving acknowledgement for completion of persisting the
                  clusters to the history DB"/>
+    <variant name="Total"
+        summary="the main thread being notified that the work was completed"/>
   </token>
 </histogram>
 
@@ -1314,6 +1316,8 @@
         summary="receiving acknowledgement for completion of persisting the
                  context clusters formed from unclustered visits to the
                  history DB"/>
+    <variant name="Total"
+        summary="the main thread being notified that the work was completed"/>
   </token>
 </histogram>
 
diff --git a/tools/metrics/histograms/metadata/ios/histograms.xml b/tools/metrics/histograms/metadata/ios/histograms.xml
index b20d046..c3c9ead3 100644
--- a/tools/metrics/histograms/metadata/ios/histograms.xml
+++ b/tools/metrics/histograms/metadata/ios/histograms.xml
@@ -1368,7 +1368,7 @@
 </histogram>
 
 <histogram name="IOS.PasswordsInOtherApps.AutoFillStatusChange"
-    enum="PasswordAutoFillEnrollmentStatus" expires_after="2023-03-11">
+    enum="PasswordAutoFillEnrollmentStatus" expires_after="2023-09-01">
   <owner>ginnyhuang@chromium.org</owner>
   <owner>bling-team@google.com</owner>
   <summary>
@@ -1379,7 +1379,7 @@
 </histogram>
 
 <histogram name="IOS.PasswordsInOtherApps.Dismiss"
-    enum="PasswordAutoFillEnrollmentStatus" expires_after="2023-03-11">
+    enum="PasswordAutoFillEnrollmentStatus" expires_after="2023-09-01">
   <owner>ginnyhuang@chromium.org</owner>
   <owner>bling-team@google.com</owner>
   <summary>
diff --git a/tools/metrics/ukm/ukm.xml b/tools/metrics/ukm/ukm.xml
index cbbc68c4..79909a0d 100644
--- a/tools/metrics/ukm/ukm.xml
+++ b/tools/metrics/ukm/ukm.xml
@@ -13410,8 +13410,7 @@
   </summary>
   <metric name="AnchorIndex">
     <summary>
-      The index of the clicked-on anchor element in NavigationPredictor's
-      |top_urls_|.
+      The index of the clicked-on anchor element in the tracked list.
     </summary>
   </metric>
   <metric name="BucketedPathHash">
@@ -13515,6 +13514,13 @@
       the same as the href when the element was created. 0 if the href changed.
     </summary>
   </metric>
+  <metric name="NavigationStartToLinkClickedMs">
+    <summary>
+      The time in milliseconds between the moment an entry for the anchor
+      element was logged to UKM and the moment the user clicked on it.
+      Exponentially bucketed, 1.3 factor.
+    </summary>
+  </metric>
 </event>
 
 <event name="NavigationPredictorPageLinkMetrics" singular="True">
@@ -13621,6 +13627,50 @@
   </metric>
 </event>
 
+<event name="NavigationPredictorUserInteractions">
+  <owner>ryansturm@chromium.org</owner>
+  <owner>isaboori@google.com</owner>
+  <summary>
+    Metrics that describe user interaction with an anchor element. This event is
+    emitted once per randomly selected anchor elements.
+  </summary>
+  <metric name="AnchorIndex">
+    <summary>
+      The index of the anchor element in NavigationPredictor's |top_urls_|.
+    </summary>
+  </metric>
+  <metric name="IsInViewport">
+    <summary>
+      1 if anchor element is in viewport, otherwise 0.
+    </summary>
+  </metric>
+  <metric name="IsPointerHoveringOver">
+    <summary>
+      1 if the pointer is hovering over the anchor element, otherwise 0.
+    </summary>
+  </metric>
+  <metric name="MaxEnteredViewportToLeftViewportMs">
+    <summary>
+      Maximum time between the moment the anchor element entered the viewport
+      and the moment it left the viewport in milliseconds. Exponentially
+      bucketed using GetExponentialBucketMin a value of 1.3.
+    </summary>
+  </metric>
+  <metric name="MaxHoverDwellTimeMs">
+    <summary>
+      Maximum dwelltime of pointer hovering over the anchor element in
+      milliseconds. Exponentially bucketed using GetExponentialBucketMin a value
+      of 1.3.
+    </summary>
+  </metric>
+  <metric name="PointerHoveringOverCount">
+    <summary>
+      How many times the pointer was hovering over the anchor element.
+      Exponentially bucketed using GetExponentialBucketMin a value of 1.3.
+    </summary>
+  </metric>
+</event>
+
 <event name="NavigationThrottleDeferredTime">
   <owner>ryansturm@chromium.org</owner>
   <owner>chrome-brapp-loading@google.com</owner>
diff --git a/tools/perf/core/perfetto_binary_roller/binary_deps.json b/tools/perf/core/perfetto_binary_roller/binary_deps.json
index cb317dd0..ec4beae 100644
--- a/tools/perf/core/perfetto_binary_roller/binary_deps.json
+++ b/tools/perf/core/perfetto_binary_roller/binary_deps.json
@@ -5,8 +5,8 @@
             "full_remote_path": "perfetto-luci-artifacts/3b59f000c939bfe4d05267fd68d282ef0b541334/linux-arm64/trace_processor_shell"
         },
         "win": {
-            "hash": "5804b6d194c554726237f00739e8c468efbe28c0",
-            "full_remote_path": "chromium-telemetry/perfetto_binaries/trace_processor_shell/win/1046bf928b4441cb79549691d76eeabec48e7274/trace_processor_shell.exe"
+            "hash": "5952b645b8e655e24391ee5ca52e0f767c46a6dc",
+            "full_remote_path": "chromium-telemetry/perfetto_binaries/trace_processor_shell/win/58600633967db2ab113cf205eb2be6b76aef97d4/trace_processor_shell.exe"
         },
         "linux_arm": {
             "hash": "e945a99da7a66211f847b8049627bbec846d2d1d",
@@ -21,8 +21,8 @@
             "full_remote_path": "perfetto-luci-artifacts/ea816328b746af31c8b7441db2ae0c3f561e629d/mac-arm64/trace_processor_shell"
         },
         "linux": {
-            "hash": "524701dfeed6fa1f92c9e91822b57d95482f0526",
-            "full_remote_path": "chromium-telemetry/perfetto_binaries/trace_processor_shell/linux/1046bf928b4441cb79549691d76eeabec48e7274/trace_processor_shell"
+            "hash": "15df64f8578f9b3f9c96fa89d636fd32d4814942",
+            "full_remote_path": "chromium-telemetry/perfetto_binaries/trace_processor_shell/linux/0d180f46481a96cbe8340734fa5cdce3bba636c8/trace_processor_shell"
         }
     },
     "power_profile.sql": {
diff --git a/tools/typescript/ts_library.py b/tools/typescript/ts_library.py
index df490887..b99bbfa 100644
--- a/tools/typescript/ts_library.py
+++ b/tools/typescript/ts_library.py
@@ -19,7 +19,7 @@
 import node_modules
 
 from path_mappings import GetDepToPathMappings
-from validate_tsconfig import validateTsconfigJson, validateJavaScriptAllowed
+from validate_tsconfig import validateTsconfigJson, validateJavaScriptAllowed, validateRootDir
 
 
 def _write_tsconfig_json(gen_dir, tsconfig, tsconfig_file):
@@ -54,6 +54,12 @@
 
   root_dir = os.path.relpath(args.root_dir, args.gen_dir)
   out_dir = os.path.relpath(args.out_dir, args.gen_dir)
+
+  is_root_dir_valid, error = validateRootDir(args.root_dir, args.gen_dir,
+                                             args.root_gen_dir, args.is_ios)
+  if not is_root_dir_valid:
+    raise AssertionError(error)
+
   TSCONFIG_BASE_PATH = os.path.join(_HERE_DIR, 'tsconfig_base.json')
 
   tsconfig = collections.OrderedDict()
diff --git a/tools/typescript/ts_library_test.py b/tools/typescript/ts_library_test.py
index e455f1c..0a23828e 100755
--- a/tools/typescript/ts_library_test.py
+++ b/tools/typescript/ts_library_test.py
@@ -12,6 +12,7 @@
 import unittest
 
 _HERE_DIR = os.path.dirname(__file__)
+_CWD = os.getcwd()
 
 
 class TsLibraryTest(unittest.TestCase):
@@ -24,7 +25,8 @@
       shutil.rmtree(self._out_folder)
 
   def _build_project1(self, enable_source_maps=False):
-    gen_dir = os.path.join(self._out_folder, 'project1')
+    gen_dir = os.path.join(self._out_folder, 'tools', 'typescript', 'tests',
+                           'project1')
 
     # Generate definition .d.ts file for legacy JS file.
     ts_definitions.main([
@@ -45,11 +47,11 @@
         '--root_gen_dir',
         os.path.relpath(self._out_folder, gen_dir),
         '--root_dir',
-        os.path.join(_HERE_DIR, 'tests', 'project1'),
+        os.path.relpath(os.path.join(_HERE_DIR, 'tests', 'project1'), _CWD),
         '--gen_dir',
-        gen_dir,
+        os.path.relpath(gen_dir, _CWD),
         '--out_dir',
-        gen_dir,
+        os.path.relpath(gen_dir, _CWD),
         '--in_files',
         'foo.ts',
         '--definitions',
@@ -86,7 +88,8 @@
   def _build_project2(self, project1_gen_dir, project3_gen_dir,
                       project6_gen_dir):
     root_dir = os.path.join(_HERE_DIR, 'tests', 'project2')
-    gen_dir = os.path.join(self._out_folder, 'project2')
+    gen_dir = os.path.join(self._out_folder, 'tools', 'typescript', 'tests',
+                           'project2')
     project1_gen_dir = os.path.relpath(project1_gen_dir, gen_dir)
     project3_gen_dir = os.path.relpath(project3_gen_dir, gen_dir)
     project6_gen_dir = os.path.relpath(project6_gen_dir, gen_dir)
@@ -99,11 +102,11 @@
         '--raw_deps',
         '//ui/webui/resources/js:build_ts',
         '--root_dir',
-        root_dir,
+        os.path.relpath(root_dir, _CWD),
         '--gen_dir',
-        gen_dir,
+        os.path.relpath(gen_dir, _CWD),
         '--out_dir',
-        gen_dir,
+        os.path.relpath(gen_dir, _CWD),
         '--in_files',
         'bar.ts',
         '--deps',
@@ -135,7 +138,8 @@
 
   # Builds project3, which includes only definition files.
   def _build_project3(self):
-    gen_dir = os.path.join(self._out_folder, 'project3')
+    gen_dir = os.path.join(self._out_folder, 'tools', 'typescript', 'tests',
+                           'project3')
 
     ts_library.main([
         '--output_suffix',
@@ -143,13 +147,14 @@
         '--root_gen_dir',
         os.path.relpath(self._out_folder, gen_dir),
         '--root_dir',
-        os.path.join(_HERE_DIR, 'tests', 'project3'),
+        os.path.relpath(os.path.join(_HERE_DIR, 'tests', 'project3'), _CWD),
         '--gen_dir',
-        gen_dir,
+        os.path.relpath(gen_dir, _CWD),
         '--out_dir',
-        gen_dir,
+        os.path.relpath(gen_dir, _CWD),
         '--definitions',
-        '../../tests/project3/baz.d.ts',
+        os.path.relpath(
+            os.path.join(_HERE_DIR, 'tests', 'project3', 'baz.d.ts'), gen_dir),
         '--composite',
     ])
     return gen_dir
@@ -162,7 +167,8 @@
     self.assertFalse(os.path.exists(os.path.join(gen_dir, 'build_ts.manifest')))
 
   def _build_project4(self):
-    gen_dir = os.path.join(self._out_folder, 'project4')
+    gen_dir = os.path.join(self._out_folder, 'tools', 'typescript', 'tests',
+                           'project4')
 
     # Build project4, which includes multiple TS files, only one of which should
     # be included in the manifest.
@@ -172,11 +178,11 @@
         '--root_gen_dir',
         os.path.relpath(self._out_folder, gen_dir),
         '--root_dir',
-        os.path.join(_HERE_DIR, 'tests', 'project4'),
+        os.path.relpath(os.path.join(_HERE_DIR, 'tests', 'project4'), _CWD),
         '--gen_dir',
-        gen_dir,
+        os.path.relpath(gen_dir, _CWD),
         '--out_dir',
-        gen_dir,
+        os.path.relpath(gen_dir, _CWD),
         '--in_files',
         'include.ts',
         'exclude.ts',
@@ -205,7 +211,8 @@
       self.assertEqual(data['files'], expected_files)
 
   def _build_project5(self):
-    gen_dir = os.path.join(self._out_folder, 'project5')
+    gen_dir = os.path.join(self._out_folder, 'tools', 'typescript', 'tests',
+                           'project5')
     out_dir_test = os.path.join(self._out_folder, 'project5_test')
 
     # Build project5, which includes 2 TS projects one for prod and one for
@@ -218,11 +225,11 @@
         '--root_gen_dir',
         os.path.relpath(self._out_folder, gen_dir),
         '--root_dir',
-        os.path.join(_HERE_DIR, 'tests', 'project5'),
+        os.path.relpath(os.path.join(_HERE_DIR, 'tests', 'project5'), _CWD),
         '--gen_dir',
-        gen_dir,
+        os.path.relpath(gen_dir, _CWD),
         '--out_dir',
-        gen_dir,
+        os.path.relpath(gen_dir, _CWD),
         '--in_files',
         'bar.ts',
     ])
@@ -237,11 +244,11 @@
         '--root_gen_dir',
         os.path.relpath(self._out_folder, gen_dir),
         '--root_dir',
-        os.path.join(_HERE_DIR, 'tests', 'project5'),
+        os.path.relpath(os.path.join(_HERE_DIR, 'tests', 'project5'), _CWD),
         '--gen_dir',
-        gen_dir,
+        os.path.relpath(gen_dir, _CWD),
         '--out_dir',
-        gen_dir,
+        os.path.relpath(gen_dir, _CWD),
         '--in_files',
         'bar_test.ts',
     ])
@@ -264,7 +271,8 @@
     self._assert_manifest_files(manifest_test, ['bar_test.js'])
 
   def _build_project6(self):
-    gen_dir = os.path.join(self._out_folder, 'ui', 'webui', 'resources', 'js')
+    gen_dir = os.path.join(self._out_folder, 'tools', 'typescript', 'tests',
+                           'ui', 'webui', 'resources', 'js')
     out_dir = os.path.join(self._out_folder, 'ui', 'webui', 'resources', 'tsc',
                            'js')
 
@@ -277,11 +285,13 @@
         '--root_gen_dir',
         os.path.relpath(self._out_folder, gen_dir),
         '--root_dir',
-        os.path.join(_HERE_DIR, 'tests', 'ui', 'webui', 'resources', 'js'),
+        os.path.relpath(
+            os.path.join(_HERE_DIR, 'tests', 'ui', 'webui', 'resources', 'js'),
+            _CWD),
         '--gen_dir',
-        gen_dir,
+        os.path.relpath(gen_dir, _CWD),
         '--out_dir',
-        out_dir,
+        os.path.relpath(out_dir, _CWD),
         '--in_files',
         'assert.ts',
         '--composite',
@@ -313,7 +323,7 @@
   # Test success case where both project1 and project2 are compiled successfully
   # and no errors are thrown.
   def testSuccess(self):
-    self._out_folder = tempfile.mkdtemp(dir=_HERE_DIR)
+    self._out_folder = tempfile.mkdtemp(dir=_CWD)
     project1_gen_dir = self._build_project1()
     self._assert_project1_output(project1_gen_dir)
 
@@ -336,8 +346,9 @@
   # Test error case where a type violation exists, ensure that an error is
   # thrown.
   def testError(self):
-    self._out_folder = tempfile.mkdtemp(dir=_HERE_DIR)
-    gen_dir = os.path.join(self._out_folder, 'project1')
+    self._out_folder = tempfile.mkdtemp(dir=_CWD)
+    gen_dir = os.path.join(self._out_folder, 'tools', 'typescript', 'tests',
+                           'project1')
     try:
       ts_library.main([
           '--output_suffix',
@@ -345,11 +356,11 @@
           '--root_gen_dir',
           os.path.relpath(self._out_folder, gen_dir),
           '--root_dir',
-          os.path.join(_HERE_DIR, 'tests', 'project1'),
+          os.path.relpath(os.path.join(_HERE_DIR, 'tests', 'project1'), _CWD),
           '--gen_dir',
-          gen_dir,
+          os.path.relpath(gen_dir, _CWD),
           '--out_dir',
-          gen_dir,
+          os.path.relpath(gen_dir, _CWD),
           '--in_files',
           'errors.ts',
           '--composite',
@@ -365,9 +376,10 @@
 
   # Test error case where the project's tsconfig file is failing validation.
   def testTsConfigValidationError(self):
-    self._out_folder = tempfile.mkdtemp(dir=_HERE_DIR)
+    self._out_folder = tempfile.mkdtemp(dir=_CWD)
     root_dir = os.path.join(_HERE_DIR, 'tests', 'project5')
-    gen_dir = os.path.join(self._out_folder, 'project5')
+    gen_dir = os.path.join(self._out_folder, 'tools', 'typescript', 'tests',
+                           'project5')
     try:
       ts_library.main([
           '--output_suffix',
@@ -375,11 +387,11 @@
           '--root_gen_dir',
           os.path.relpath(self._out_folder, gen_dir),
           '--root_dir',
-          root_dir,
+          os.path.relpath(root_dir, _CWD),
           '--gen_dir',
-          gen_dir,
+          os.path.relpath(gen_dir, _CWD),
           '--out_dir',
-          gen_dir,
+          os.path.relpath(gen_dir, _CWD),
           '--in_files',
           'bar.ts',
           '--tsconfig_base',
@@ -394,7 +406,7 @@
 
   # Test case where |enable_source_maps| is specified.
   def testEnableSourceMaps(self):
-    self._out_folder = tempfile.mkdtemp(dir=_HERE_DIR)
+    self._out_folder = tempfile.mkdtemp(dir=_CWD)
 
     expectations_dir = os.path.join(_HERE_DIR, 'tests', 'expected', 'project1')
 
diff --git a/tools/typescript/validate_tsconfig.py b/tools/typescript/validate_tsconfig.py
index 43ba234..e84d4ba 100644
--- a/tools/typescript/validate_tsconfig.py
+++ b/tools/typescript/validate_tsconfig.py
@@ -10,6 +10,13 @@
 # args corresponding to config options to limit the possibility of unsupported
 # configurations proliferating in the codebase.
 
+import os
+
+_CWD = os.getcwd().replace('\\', '/')
+_HERE_DIR = os.path.dirname(__file__)
+_SRC_DIR = os.path.normpath(os.path.join(_HERE_DIR, '..',
+                                         '..')).replace('\\', '/')
+
 # Options configured by the ts_library should not be set separately.
 _tsconfig_compiler_options_mappings = {
     'allowJs': 'allow_js=true',
@@ -132,3 +139,55 @@
 
   return False, 'Invalid allow_js detected for input directory ' + \
       f'{source_dir} and output directory {out_dir}'
+
+
+# |root_dir| shouldn't refer to any parent directories. Specifically it should
+# be either:
+#   - within the folder tree starting at the ts_library() target's location
+#   - within the folder tree starting at the ts_library() target's corresponding
+#     target_gen_dir location.
+def validateRootDir(root_dir, gen_dir, root_gen_dir, is_ios):
+  root_gen_dir_from_build = os.path.normpath(os.path.join(
+      gen_dir, root_gen_dir)).replace('\\', '/')
+  target_path = os.path.relpath(gen_dir,
+                                root_gen_dir_from_build).replace('\\', '/')
+
+  # Broadly special casing ios/ for now, since compile_ts.gni relies on
+  # unsupported behavior of setting the root_dir to src/.
+  # TODO (https://www.crbug.com/1412158): Make iOS TypeScript build tools use
+  # ts_library in a supported way, or change them to not rely on ts_library.
+  if (is_ios and (target_path.startswith('ios') or '/ios/' in target_path)):
+    return True, None
+
+  # Legacy cases supported for backward-compatibility. Do not add new targets
+  # here. The existing exceptions should be removed over time.
+  exceptions = [
+      # TODO (https://www.crbug.com/1412158): Update this folder to copy files
+      # instead of setting $root_gen_dir/mojom-webui as the root_dir.
+      'ui/webui/resources/mojo',
+
+      # ChromeOS cases
+      'ash/webui/camera_app_ui/resources/js',
+      'ash/webui/color_internals/mojom',
+      'ash/webui/face_ml_app_ui/mojom',
+      'ash/webui/sample_system_web_app_ui/mojom',
+      'chrome/browser/resources/chromeos/accessibility/select_to_speak',
+  ]
+
+  if target_path in exceptions:
+    return True, None
+
+  target_path_src = os.path.relpath(os.path.join(_SRC_DIR, target_path),
+                                    _CWD).replace('\\', '/')
+  root_path_from_gen = os.path.relpath(root_dir,
+                                       root_gen_dir_from_build).replace(
+                                           '\\', '/')
+  root_path_from_src = os.path.relpath(os.path.join(_CWD, root_dir),
+                                       _SRC_DIR).replace('\\', '/')
+
+  if (root_path_from_gen.startswith(target_path)
+      or root_path_from_src.startswith(target_path)):
+    return True, None
+
+  return False, f'Error: root_dir ({root_dir}) should be within {gen_dir} ' + \
+      f'or {target_path_src}.'
diff --git a/ui/android/resources/resource_manager.h b/ui/android/resources/resource_manager.h
index bff2565..80fa5d5 100644
--- a/ui/android/resources/resource_manager.h
+++ b/ui/android/resources/resource_manager.h
@@ -62,6 +62,8 @@
                                                : 0;
   }
 
+  virtual void MarkTintNonDiscardable(SkColor tint_color) = 0;
+
   // A notification that all updates have finished for the current frame.
   virtual void OnFrameUpdatesFinished() = 0;
 
diff --git a/ui/android/resources/resource_manager_impl.cc b/ui/android/resources/resource_manager_impl.cc
index ac65593..171d2c85 100644
--- a/ui/android/resources/resource_manager_impl.cc
+++ b/ui/android/resources/resource_manager_impl.cc
@@ -129,6 +129,10 @@
   }
 }
 
+void ResourceManagerImpl::MarkTintNonDiscardable(SkColor tint_color) {
+  used_tints_.insert(tint_color);
+}
+
 void ResourceManagerImpl::OnFrameUpdatesFinished() {
   RemoveUnusedTints();
   used_tints_.clear();
diff --git a/ui/android/resources/resource_manager_impl.h b/ui/android/resources/resource_manager_impl.h
index d5a1bb8..30eba34 100644
--- a/ui/android/resources/resource_manager_impl.h
+++ b/ui/android/resources/resource_manager_impl.h
@@ -49,6 +49,7 @@
                                       SkColor tint_color,
                                       bool preserve_color_alpha) override;
   void PreloadResource(AndroidResourceType res_type, int res_id) override;
+  void MarkTintNonDiscardable(SkColor tint_color) override;
   void OnFrameUpdatesFinished() override;
 
   // Called from Java
@@ -96,7 +97,7 @@
   TintedResourceMap tinted_resources_;
 
   // The set of tints that are used for resources in the current frame.
-  std::unordered_set<int> used_tints_;
+  std::unordered_set<SkColor> used_tints_;
 
   base::android::ScopedJavaGlobalRef<jobject> java_obj_;
 };
diff --git a/ui/color/chromeos/native_color_mixers_chromeos.cc b/ui/color/chromeos/native_color_mixers_chromeos.cc
index 891e67b..df6e202 100644
--- a/ui/color/chromeos/native_color_mixers_chromeos.cc
+++ b/ui/color/chromeos/native_color_mixers_chromeos.cc
@@ -23,6 +23,8 @@
   mixer[kColorAshSystemUIMenuItemBackgroundSelected] = {
       kColorMenuItemBackgroundSelected};
   mixer[kColorAshSystemUIMenuSeparator] = {kColorMenuSeparator};
+  mixer[kColorMultitaskMenuNudgePulse] = {kColorEndpointForeground};
+
   bool dark_mode = key.color_mode == ColorProviderManager::ColorMode::kDark;
 
   // Add color initializations for highlight border.
diff --git a/ui/color/color_id.h b/ui/color/color_id.h
index cc98f7c..5196cf2 100644
--- a/ui/color/color_id.h
+++ b/ui/color/color_id.h
@@ -221,6 +221,7 @@
   E_CPONLY(kColorButtonForegroundUnchecked) \
   E_CPONLY(kColorMultitaskFeedbackButtonLabelBackground) \
   E_CPONLY(kColorMultitaskFeedbackButtonLabelForeground) \
+  E_CPONLY(kColorMultitaskMenuNudgePulse) \
   E_CPONLY(kColorComboboxBackground) \
   E_CPONLY(kColorComboboxBackgroundDisabled) \
   E_CPONLY(kColorCustomFrameCaptionForeground) \
diff --git a/ui/file_manager/file_manager/widgets/xf_search_options.html b/ui/file_manager/file_manager/widgets/xf_search_options.html
index 2933ba26..54e70f3 100644
--- a/ui/file_manager/file_manager/widgets/xf_search_options.html
+++ b/ui/file_manager/file_manager/widgets/xf_search_options.html
@@ -10,9 +10,9 @@
   }
 </style>
 
-<xf-select id="location-selector" icon="select-location">
+<xf-select id="location-selector" icon="select-location" menuAlignment="start">
 </xf-select>
-<xf-select id="recency-selector" icon="select-time">
+<xf-select id="recency-selector" icon="select-time" menuAlignment="start">
 </xf-select>
-<xf-select id="type-selector" icon="select-filetype">
+<xf-select id="type-selector" icon="select-filetype" menuAlignment="start">
 </xf-select>
diff --git a/ui/file_manager/file_manager/widgets/xf_select.ts b/ui/file_manager/file_manager/widgets/xf_select.ts
index 0b9e5fa9..fdf7a81 100644
--- a/ui/file_manager/file_manager/widgets/xf_select.ts
+++ b/ui/file_manager/file_manager/widgets/xf_select.ts
@@ -77,6 +77,12 @@
    */
   @property({type: String, reflect: true}) value: string = '';
 
+  /**
+   * The alignment of items in the dropdown menu. Can be one of
+   * 'start', 'center', 'end'.
+   */
+  @property({type: String, reflect: true}) menuAlignment: string = 'center';
+
   static get events() {
     return {
       /** emits when the currently selected option changed. */
@@ -161,10 +167,11 @@
    * Returns a template of the dropdown which shows available choices.
    */
   private renderDropdown_() {
+    const alignment = this.menuAlignment || 'center';
     return html`<cr-action-menu>
         ${this.options.map((option, index) => html`
           <cr-button
-              class="dropdown-item"
+              class="dropdown-item dropdown-item-${alignment}"
               role="menuitem"
               @click=${() => this.onOptionSelected_(index)}
               ?selected=${this.selectedOption_!.value === option.value}>
@@ -352,6 +359,15 @@
     cr-button.dropdown-item {
       --focus-shadow-color: none;
     }
+    cr-button.dropdown-item-center {
+      justify-content: center;
+    }
+    cr-button.dropdown-item-start {
+      justify-content: start;
+    }
+    cr-button.dropdown-item-end {
+      justify-content: end;
+    }
   `;
 }
 
diff --git a/ui/ozone/platform/drm/common/drm_wrapper.h b/ui/ozone/platform/drm/common/drm_wrapper.h
index 6247fa0..297177e 100644
--- a/ui/ozone/platform/drm/common/drm_wrapper.h
+++ b/ui/ozone/platform/drm/common/drm_wrapper.h
@@ -13,7 +13,6 @@
 #include "base/files/file.h"
 #include "base/files/file_path.h"
 #include "base/functional/callback.h"
-#include "base/memory/ref_counted.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
 #include "third_party/perfetto/include/perfetto/tracing/traced_value_forward.h"
 #include "ui/display/types/gamma_ramp_rgb_entry.h"
@@ -54,13 +53,14 @@
 // Wraps DRM calls into a tight interface. Used to provide different
 // implementations of the DRM calls. For the actual implementation the DRM API
 // would be called. In unit tests this interface would be stubbed.
-class DrmWrapper : public base::RefCountedThreadSafe<DrmWrapper> {
+class DrmWrapper {
  public:
   DrmWrapper(const base::FilePath& device_path,
              base::File file,
              bool is_primary_device);
   DrmWrapper(const DrmWrapper&) = delete;
   DrmWrapper& operator=(const DrmWrapper&) = delete;
+  virtual ~DrmWrapper();
 
   // Open device.
   virtual bool Initialize();
@@ -252,10 +252,6 @@
   bool is_primary_device() const { return is_primary_device_; }
 
  protected:
-  friend class base::RefCountedThreadSafe<DrmWrapper>;
-
-  virtual ~DrmWrapper();
-
   // Path to the DRM device (in sysfs).
   const base::FilePath device_path_;
   // DRM device.
diff --git a/ui/ozone/platform/drm/gpu/drm_device.h b/ui/ozone/platform/drm/gpu/drm_device.h
index 23d2daf..f3bdf5b 100644
--- a/ui/ozone/platform/drm/gpu/drm_device.h
+++ b/ui/ozone/platform/drm/gpu/drm_device.h
@@ -24,7 +24,8 @@
 class HardwareDisplayPlaneManager;
 class GbmDevice;
 
-class DrmDevice : public DrmWrapper {
+class DrmDevice : public DrmWrapper,
+                  public base::RefCountedThreadSafe<DrmDevice> {
  public:
   using PageFlipCallback =
       base::OnceCallback<void(unsigned int /* frame */,
@@ -76,6 +77,8 @@
   GbmDevice* gbm_device() const { return gbm_.get(); }
 
  protected:
+  friend class base::RefCountedThreadSafe<DrmDevice>;
+
   friend class DrmDisplayTest;
 
   ~DrmDevice() override;
diff --git a/ui/views/controls/button/image_button.cc b/ui/views/controls/button/image_button.cc
index 7f65886..32de82bb 100644
--- a/ui/views/controls/button/image_button.cc
+++ b/ui/views/controls/button/image_button.cc
@@ -141,6 +141,14 @@
   return views::PaintInfo::ScaleType::kUniformScaling;
 }
 
+void ImageButton::OnThemeChanged() {
+  Button::OnThemeChanged();
+
+  // If we have any `ImageModel`s, they may need repaint upon a `ColorProvider`
+  // change.
+  SchedulePaint();
+}
+
 void ImageButton::PaintButtonContents(gfx::Canvas* canvas) {
   // TODO(estade|tdanderson|bruthig): The ink drop layer should be positioned
   // behind the button's image which means the image needs to be painted to its
diff --git a/ui/views/controls/button/image_button.h b/ui/views/controls/button/image_button.h
index 8eb1f73b..a3aea3f 100644
--- a/ui/views/controls/button/image_button.h
+++ b/ui/views/controls/button/image_button.h
@@ -73,6 +73,7 @@
   // Overridden from View:
   gfx::Size CalculatePreferredSize() const override;
   views::PaintInfo::ScaleType GetPaintScaleType() const override;
+  void OnThemeChanged() override;
 
  protected:
   // Overridden from Button:
diff --git a/weblayer/browser/translate_compact_infobar.cc b/weblayer/browser/translate_compact_infobar.cc
index 1747423..23f55c0 100644
--- a/weblayer/browser/translate_compact_infobar.cc
+++ b/weblayer/browser/translate_compact_infobar.cc
@@ -118,13 +118,11 @@
   if (option == translate::TranslateUtils::OPTION_SOURCE_CODE) {
     std::string source_code =
         base::android::ConvertJavaStringToUTF8(env, value);
-    if (delegate->source_language_code().compare(source_code) != 0)
-      delegate->UpdateSourceLanguage(source_code);
+    delegate->UpdateSourceLanguage(source_code);
   } else if (option == translate::TranslateUtils::OPTION_TARGET_CODE) {
     std::string target_code =
         base::android::ConvertJavaStringToUTF8(env, value);
-    if (delegate->target_language_code().compare(target_code) != 0)
-      delegate->UpdateTargetLanguage(target_code);
+    delegate->UpdateTargetLanguage(target_code);
   } else {
     DCHECK(false);
   }