diff --git a/DEPS b/DEPS
index 23ce4fcc..365ef56 100644
--- a/DEPS
+++ b/DEPS
@@ -282,19 +282,19 @@
   # 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': '7736fbaf84f06057637538449a2a62f442fff378',
+  'skia_revision': 'bac819cdc94a0a9fc4b3954f2ea5eec4150be103',
   # 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': 'fd29d37630914966ff447d57b897f32e5ebea8c6',
+  'v8_revision': '809cca39541254a33491abaf04826f5a1abcc911',
   # 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': '9c376a02deade716a29d2a2c85393c28ed0ac578',
+  'angle_revision': 'd7a684e954378109774950238a7496039b3a0cd4',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling SwiftShader
   # and whatever else without interference from each other.
-  'swiftshader_revision': 'ecefa6c6596982616fa1f564fcee363ef8533808',
+  'swiftshader_revision': '9ebfb6b54814268d079e0a7084bac82531626f99',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling PDFium
   # and whatever else without interference from each other.
@@ -341,7 +341,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling HarfBuzz
   # and whatever else without interference from each other.
-  'harfbuzz_revision': '2175f5d050743317c563ec9414e0f83a47f7fbc4',
+  'harfbuzz_revision': '8df5cdbcda495a582e72a7e2ce35d6106401edce',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling Emoji Segmenter
   # and whatever else without interference from each other.
@@ -409,7 +409,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': 'a3a0b3299b6c0c6c54c2e3637fc80791f1fb379a',
+  'dawn_revision': 'dc271941243568d0d1ac59c10dc878cb9b821ea0',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling feed
   # and whatever else without interference from each other.
@@ -769,7 +769,7 @@
 
   'src/clank': {
     'url': Var('chrome_git') + '/clank/internal/apps.git' + '@' +
-    'dbdb19ed5e143ef6a2831b3fb50404253ad9a3f1',
+    '699137f5cdfe04accf44a6dab88f2e617810eea8',
     'condition': 'checkout_android and checkout_src_internal',
   },
 
@@ -958,7 +958,7 @@
     'packages': [
       {
           'package': 'chromium/third_party/androidx',
-          'version': 'XXY63ZMXsEh9Q723ZYgW4JxYtzVhxNIoUxUwwjMSeBAC',
+          'version': 'nz4cOkIvPx0feghxVzKjJJRpe97BWgexvaskqE7EWVQC',
       },
     ],
     'condition': 'checkout_android',
@@ -1795,7 +1795,7 @@
     Var('chromium_git') + '/external/github.com/GoogleChromeLabs/text-fragments-polyfill.git' + '@' + 'c036420683f672d685e27415de0a5f5e85bdc23f',
 
   'src/third_party/tflite/src':
-    Var('chromium_git') + '/external/github.com/tensorflow/tensorflow.git' + '@' + '28399abb7f7381b5862ea5baf1ce6bd04f9486d1',
+    Var('chromium_git') + '/external/github.com/tensorflow/tensorflow.git' + '@' + '9becb72d9b656f48de2bf00094910189434bc21e',
 
   'src/third_party/turbine': {
       'packages': [
@@ -1938,7 +1938,7 @@
     Var('chromium_git') + '/v8/v8.git' + '@' +  Var('v8_revision'),
 
   'src-internal': {
-    'url': Var('chrome_git') + '/chrome/src-internal.git@97d0780bd20fabcc1fea568fcdab9bfbea2c1301',
+    'url': Var('chrome_git') + '/chrome/src-internal.git@eb3f9b1a2bac7485bc94ed6b770e48e4053514bc',
     'condition': 'checkout_src_internal',
   },
 
@@ -1979,7 +1979,7 @@
     'packages': [
       {
         'package': 'chromeos_internal/apps/media_app/app',
-        'version': 'AdkvfRcY9Se5qfTBb08GDOPgU5Jwj4jGmWklIxH1aHUC',
+        'version': 'XhSGJrFknGIAkZg7vuh9dh_xD_VP3US1fqb1ZNzKk1IC',
       },
     ],
     'condition': 'checkout_chromeos and checkout_src_internal',
diff --git a/ash/BUILD.gn b/ash/BUILD.gn
index 75e70ad..8bbc7ee 100644
--- a/ash/BUILD.gn
+++ b/ash/BUILD.gn
@@ -887,6 +887,8 @@
     "user_education/user_education_delegate.h",
     "user_education/user_education_feature_controller.cc",
     "user_education/user_education_feature_controller.h",
+    "user_education/user_education_ping_controller.cc",
+    "user_education/user_education_ping_controller.h",
     "user_education/user_education_private_api_key.h",
     "user_education/user_education_types.h",
     "user_education/user_education_util.cc",
@@ -3266,6 +3268,9 @@
     "system/locale/locale_feature_pod_controller_unittest.cc",
     "system/lock_screen_notification_controller_unittest.cc",
     "system/media/media_tray_unittest.cc",
+    "system/media/quick_settings_media_view_container_unittest.cc",
+    "system/media/quick_settings_media_view_controller_unittest.cc",
+    "system/media/quick_settings_media_view_unittest.cc",
     "system/media/unified_media_controls_container_unittest.cc",
     "system/media/unified_media_controls_controller_unittest.cc",
     "system/media/unified_media_controls_detailed_view_controller_unittest.cc",
@@ -3433,6 +3438,7 @@
     "user_education/capture_mode_tour/capture_mode_tour_controller_unittest.cc",
     "user_education/holding_space_tour/holding_space_tour_controller_unittest.cc",
     "user_education/user_education_controller_unittest.cc",
+    "user_education/user_education_ping_controller_unittest.cc",
     "user_education/user_education_util_unittest.cc",
     "user_education/welcome_tour/welcome_tour_controller_unittest.cc",
     "utility/cropping_util_unittest.cc",
@@ -3655,6 +3661,7 @@
     "//components/language/core/browser:browser",
     "//components/live_caption:constants",
     "//components/media_message_center",
+    "//components/media_message_center:test_support",
     "//components/onc",
     "//components/password_manager/core/browser:hash_password_manager",
     "//components/prefs:test_support",
diff --git a/ash/app_list/app_list_presenter_unittest.cc b/ash/app_list/app_list_presenter_unittest.cc
index e0c337c..abde981 100644
--- a/ash/app_list/app_list_presenter_unittest.cc
+++ b/ash/app_list/app_list_presenter_unittest.cc
@@ -39,6 +39,8 @@
 #include "ash/app_list/views/search_result_page_anchored_dialog.h"
 #include "ash/app_list/views/search_result_page_view.h"
 #include "ash/assistant/ui/assistant_ui_constants.h"
+#include "ash/display/display_configuration_controller.h"
+#include "ash/display/display_configuration_controller_test_api.h"
 #include "ash/drag_drop/drag_drop_controller.h"
 #include "ash/keyboard/keyboard_controller_impl.h"
 #include "ash/keyboard/ui/keyboard_ui_controller.h"
@@ -52,6 +54,7 @@
 #include "ash/public/cpp/shell_window_ids.h"
 #include "ash/public/cpp/test/shell_test_api.h"
 #include "ash/root_window_controller.h"
+#include "ash/rotator/screen_rotation_animator.h"
 #include "ash/shelf/home_button.h"
 #include "ash/shelf/scrollable_shelf_view.h"
 #include "ash/shelf/shelf.h"
@@ -2229,6 +2232,55 @@
   widget_close_waiter.Wait();
 }
 
+// Verifies that rotation the screen when launcher is shown does not crash.
+TEST_P(AppListBubbleAndTabletTest, RotationAnimationSmoke) {
+  test::AppListTestModel* model = GetAppListModel();
+  model->PopulateApps(15);
+  model->CreateAndPopulateFolderWithApps(3);
+  model->PopulateApps(15);
+
+  EnableTabletMode(tablet_mode_param());
+  EnsureLauncherShown();
+
+  display::Display display = display::Screen::GetScreen()->GetPrimaryDisplay();
+  ScreenRotationAnimator* animator =
+      DisplayConfigurationControllerTestApi(
+          Shell::Get()->display_configuration_controller())
+          .GetScreenRotationAnimatorForDisplay(display.id());
+  animator->Rotate(display::Display::ROTATE_90,
+                   display::Display::RotationSource::USER,
+                   DisplayConfigurationController::ANIMATION_SYNC);
+}
+
+TEST_P(AppListBubbleAndTabletTest, RotationAnimationInSearchSmoke) {
+  EnableTabletMode(tablet_mode_param());
+  EnsureLauncherShown();
+
+  // Show search page.
+  ui::test::EventGenerator* generator = GetEventGenerator();
+  generator->PressKey(ui::VKEY_A, 0);
+  EXPECT_TRUE(AppListSearchResultPageVisible());
+
+  // Add suggestion results - the result that will be tested is in
+  // the second place.
+  GetSearchModel()->results()->Add(
+      CreateOmniboxSuggestionResult("Another suggestion"));
+  const std::string kTestResultId = "Test suggestion";
+  GetSearchModel()->results()->Add(
+      CreateOmniboxSuggestionResult(kTestResultId));
+  // The result list is updated asynchronously.
+  base::RunLoop().RunUntilIdle();
+
+  display::Display display = display::Screen::GetScreen()->GetPrimaryDisplay();
+  ScreenRotationAnimator* animator =
+      DisplayConfigurationControllerTestApi(
+          Shell::Get()->display_configuration_controller())
+          .GetScreenRotationAnimatorForDisplay(display.id());
+  animator->Rotate(display::Display::ROTATE_90,
+                   display::Display::RotationSource::USER,
+                   DisplayConfigurationController::ANIMATION_SYNC);
+}
+
 // Tests that mouse app list item drag is cancelled when mouse capture is lost
 // (e.g. on screen rotation).
 TEST_P(PopulatedAppListTest, CancelItemDragOnMouseCaptureLoss) {
diff --git a/ash/app_list/views/app_list_item_view.cc b/ash/app_list/views/app_list_item_view.cc
index 2d4cd820..648c093 100644
--- a/ash/app_list/views/app_list_item_view.cc
+++ b/ash/app_list/views/app_list_item_view.cc
@@ -114,6 +114,35 @@
 // icons.
 constexpr size_t kMaxItemCounterCount = 100u;
 
+class IconBackgroundLayer : public ui::LayerOwner {
+ public:
+  explicit IconBackgroundLayer(views::View* icon_view)
+      : ui::LayerOwner(std::make_unique<ui::Layer>(ui::LAYER_SOLID_COLOR)),
+        icon_view_(icon_view) {
+    layer()->SetName("icon_background_layer");
+    icon_view_->AddLayerToRegion(layer(), views::LayerRegion::kBelow);
+  }
+
+  IconBackgroundLayer(const IconBackgroundLayer&) = delete;
+  IconBackgroundLayer& operator=(const IconBackgroundLayer) = delete;
+
+  ~IconBackgroundLayer() override {
+    icon_view_->RemoveLayerFromRegions(layer());
+  }
+
+  // ui::LayerOwner:
+  std::unique_ptr<ui::Layer> RecreateLayer() override {
+    std::unique_ptr<ui::Layer> old_layer = ui::LayerOwner::RecreateLayer();
+
+    icon_view_->RemoveLayerFromRegionsKeepInLayerTree(old_layer.get());
+    icon_view_->AddLayerToRegion(layer(), views::LayerRegion::kBelow);
+    return old_layer;
+  }
+
+ private:
+  views::View* const icon_view_;
+};
+
 // The class clips the provided folder icon image.
 class ClippedFolderIconImageSource : public gfx::CanvasImageSource {
  public:
@@ -596,6 +625,7 @@
     item_weak_->RemoveObserver(this);
   }
   StopObservingImplicitAnimations();
+  icon_background_layer_.reset();
 }
 
 void AppListItemView::UpdateIconView(bool update_item_icon) {
@@ -1433,8 +1463,8 @@
         is_folder_ ? GetColorProvider()->GetColor(cros_tokens::kIconColorBlue)
                    : item_weak_->GetNotificationBadgeColor();
     notification_indicator_->SetColor(notification_indicator_color);
-    if (icon_background_layer_.OwnsLayer()) {
-      icon_background_layer_.layer()->SetColor(
+    if (icon_background_layer_) {
+      icon_background_layer_->layer()->SetColor(
           GetColorProvider()->GetColor(GetBackgroundLayerColorId()));
     }
   }
@@ -1831,19 +1861,15 @@
 void AppListItemView::EnsureIconBackgroundLayer() {
   const bool clip_inner_icons =
       is_folder_ && !features::IsAppCollectionFolderRefreshEnabled();
-  if (clip_inner_icons || icon_background_layer_.OwnsLayer()) {
+  if (clip_inner_icons || icon_background_layer_) {
     return;
   }
 
-  icon_background_layer_.Reset(
-      std::make_unique<ui::Layer>(ui::LAYER_SOLID_COLOR));
-  auto* background_layer = icon_background_layer_.layer();
-  background_layer->SetName("icon_background_layer");
+  icon_background_layer_ = std::make_unique<IconBackgroundLayer>(GetIconView());
   if (GetColorProvider()) {
-    background_layer->SetColor(
+    icon_background_layer_->layer()->SetColor(
         GetColorProvider()->GetColor(GetBackgroundLayerColorId()));
   }
-  GetIconView()->AddLayerToRegion(background_layer, views::LayerRegion::kBelow);
 }
 
 ui::ColorId AppListItemView::GetBackgroundLayerColorId() const {
@@ -1864,8 +1890,7 @@
 
 void AppListItemView::OnExtendingAnimationEnded(bool extend_icon) {
   if (!setting_up_icon_animation_ && !extend_icon && !is_folder_) {
-    GetIconView()->RemoveLayerFromRegions(icon_background_layer_.layer());
-    icon_background_layer_.ReleaseLayer();
+    icon_background_layer_.reset();
   }
 }
 
@@ -1874,7 +1899,10 @@
     return GetIconView()->layer();
   }
 
-  return icon_background_layer_.layer();
+  if (!icon_background_layer_) {
+    return nullptr;
+  }
+  return icon_background_layer_->layer();
 }
 
 BEGIN_METADATA(AppListItemView, views::Button)
diff --git a/ash/app_list/views/app_list_item_view.h b/ash/app_list/views/app_list_item_view.h
index 5eae1ff..97f2626 100644
--- a/ash/app_list/views/app_list_item_view.h
+++ b/ash/app_list/views/app_list_item_view.h
@@ -289,7 +289,10 @@
   void reset_has_pending_row_change() { has_pending_row_change_ = false; }
 
   const ui::Layer* icon_background_layer_for_test() const {
-    return icon_background_layer_.layer();
+    if (!icon_background_layer_) {
+      return nullptr;
+    }
+    return icon_background_layer_->layer();
   }
   bool is_icon_extended_for_test() const { return is_icon_extended_; }
   absl::optional<size_t> item_counter_count_for_test() const;
@@ -447,7 +450,7 @@
 
   // The background layer added under the `icon_` layer to paint the background
   // of the icon.
-  ui::LayerOwner icon_background_layer_;
+  std::unique_ptr<ui::LayerOwner> icon_background_layer_;
 
   // Draws a dot next to the title for newly installed apps.
   raw_ptr<views::View, ExperimentalAsh> new_install_dot_ = nullptr;
diff --git a/ash/app_list/views/search_box_view.cc b/ash/app_list/views/search_box_view.cc
index fb20699..ba3df35 100644
--- a/ash/app_list/views/search_box_view.cc
+++ b/ash/app_list/views/search_box_view.cc
@@ -185,36 +185,37 @@
 
 }  // namespace
 
-class SearchBoxView::FocusRingLayer : public ui::Layer, ui::LayerDelegate {
+class SearchBoxView::FocusRingLayer : public ui::LayerOwner, ui::LayerDelegate {
  public:
-  FocusRingLayer() : Layer(ui::LAYER_TEXTURED) {
-    SetName("search_box/FocusRing");
-    SetFillsBoundsOpaquely(false);
-    set_delegate(this);
+  FocusRingLayer()
+      : LayerOwner(std::make_unique<ui::Layer>(ui::LAYER_TEXTURED)) {
+    layer()->SetName("search_box/FocusRing");
+    layer()->SetFillsBoundsOpaquely(false);
+    layer()->set_delegate(this);
   }
   FocusRingLayer(const FocusRingLayer&) = delete;
   FocusRingLayer& operator=(const FocusRingLayer&) = delete;
-  ~FocusRingLayer() override {}
+  ~FocusRingLayer() override = default;
 
   void SetColor(SkColor color) {
     if (color == color_) {
       return;
     }
     color_ = color;
-    SchedulePaint(gfx::Rect(size()));
+    layer()->SchedulePaint(gfx::Rect(layer()->size()));
   }
 
  private:
   // views::LayerDelegate:
   void OnPaintLayer(const ui::PaintContext& context) override {
-    ui::PaintRecorder recorder(context, bounds().size());
+    ui::PaintRecorder recorder(context, layer()->size());
     gfx::Canvas* canvas = recorder.canvas();
 
     // When using strokes to draw a rect, the bounds set is the center of the
     // rect, which means that setting draw bounds to `bounds()` will leave half
     // of the border outside the layer that may not be painted. Shrink the draw
     // bounds by half of the width to solve this problem.
-    gfx::Rect draw_bounds(bounds().size());
+    gfx::Rect draw_bounds(layer()->size());
     draw_bounds.Inset(kSearchBoxFocusRingWidth / 2);
 
     cc::PaintFlags flags;
@@ -226,7 +227,7 @@
   }
   void OnDeviceScaleFactorChanged(float old_device_scale_factor,
                                   float new_device_scale_factor) override {
-    SchedulePaint(gfx::Rect(size()));
+    layer()->SchedulePaint(gfx::Rect(layer()->size()));
   }
 
   SkColor color_ = gfx::kPlaceholderColor;
@@ -524,7 +525,7 @@
 
 void SearchBoxView::OnBoundsChanged(const gfx::Rect& previous_bounds) {
   if (focus_ring_layer_)
-    focus_ring_layer_->SetBounds(bounds());
+    focus_ring_layer_->layer()->SetBounds(bounds());
 }
 
 void SearchBoxView::AddedToWidget() {
@@ -534,8 +535,8 @@
     focus_ring_layer_ = std::make_unique<FocusRingLayer>();
     focus_ring_layer_->SetColor(
         GetColorProvider()->GetColor(GetFocusColorId(is_jelly_enabled_)));
-    layer()->parent()->Add(focus_ring_layer_.get());
-    layer()->parent()->StackAtBottom(focus_ring_layer_.get());
+    layer()->parent()->Add(focus_ring_layer_->layer());
+    layer()->parent()->StackAtBottom(focus_ring_layer_->layer());
     UpdateSearchBoxFocusPaint();
   }
 }
@@ -580,9 +581,9 @@
   // Paints the focus ring if the search box is focused.
   if (search_box()->HasFocus() && !is_search_box_active() &&
       view_delegate_->KeyboardTraversalEngaged()) {
-    focus_ring_layer_->SetVisible(true);
+    focus_ring_layer_->layer()->SetVisible(true);
   } else {
-    focus_ring_layer_->SetVisible(false);
+    focus_ring_layer_->layer()->SetVisible(false);
   }
 }
 
diff --git a/ash/ash_prefs.cc b/ash/ash_prefs.cc
index b2859af..bcc8efe 100644
--- a/ash/ash_prefs.cc
+++ b/ash/ash_prefs.cc
@@ -154,6 +154,12 @@
                                  std::string());
     registry->RegisterStringPref(language::prefs::kPreferredLanguages,
                                  std::string());
+    registry->RegisterBooleanPref(prefs::kEventRemappedToRightClick, false);
+    registry->RegisterIntegerPref(prefs::kKeyEventRemappedToSixPackDelete, 0);
+    registry->RegisterIntegerPref(prefs::kKeyEventRemappedToSixPackEnd, 0);
+    registry->RegisterIntegerPref(prefs::kKeyEventRemappedToSixPackHome, 0);
+    registry->RegisterIntegerPref(prefs::kKeyEventRemappedToSixPackPageUp, 0);
+    registry->RegisterIntegerPref(prefs::kKeyEventRemappedToSixPackPageDown, 0);
   }
 }
 
diff --git a/ash/capture_mode/capture_mode_bar_view.cc b/ash/capture_mode/capture_mode_bar_view.cc
index 557f2c83..eaed47b 100644
--- a/ash/capture_mode/capture_mode_bar_view.cc
+++ b/ash/capture_mode/capture_mode_bar_view.cc
@@ -17,8 +17,6 @@
 #include "ash/constants/ash_features.h"
 #include "ash/public/cpp/style/color_provider.h"
 #include "ash/resources/vector_icons/vector_icons.h"
-#include "ash/shelf/shelf.h"
-#include "ash/shelf/shelf_layout_manager.h"
 #include "ash/strings/grit/ash_strings.h"
 #include "ash/style/ash_color_id.h"
 #include "ash/style/ash_color_provider.h"
@@ -44,21 +42,12 @@
 
 namespace {
 
-// Full size of capture mode bar view, the width of which will be
-// adjusted in projector mode.
-constexpr gfx::Size kFullBarSize{376, 64};
-
 constexpr auto kBarPadding = gfx::Insets::VH(14, 16);
 
 constexpr int kBorderRadius = 20;
 
 constexpr int kSeparatorHeight = 20;
 
-// Distance from the bottom of the bar to the bottom of the display, top of the
-// hotseat or top of the shelf depending on the shelf alignment or hotseat
-// visibility.
-constexpr int kDistanceFromShelfOrHotseatTopDp = 16;
-
 }  // namespace
 
 CaptureModeBarView::CaptureModeBarView(CaptureModeBehavior* active_behavior)
@@ -125,39 +114,6 @@
 
 CaptureModeBarView::~CaptureModeBarView() = default;
 
-// static
-gfx::Rect CaptureModeBarView::GetBounds(aura::Window* root,
-                                        CaptureModeBehavior* active_behavior) {
-  DCHECK(root);
-
-  auto bounds = root->GetBoundsInScreen();
-  int bar_y = bounds.bottom();
-  Shelf* shelf = Shelf::ForWindow(root);
-  if (shelf->IsHorizontalAlignment()) {
-    // Get the widget which has the shelf icons. This is the hotseat widget if
-    // the hotseat is extended, shelf widget otherwise.
-    const bool hotseat_extended =
-        shelf->shelf_layout_manager()->hotseat_state() ==
-        HotseatState::kExtended;
-    views::Widget* shelf_widget =
-        hotseat_extended ? static_cast<views::Widget*>(shelf->hotseat_widget())
-                         : static_cast<views::Widget*>(shelf->shelf_widget());
-    bar_y = shelf_widget->GetWindowBoundsInScreen().y();
-  }
-
-  gfx::Size bar_size = kFullBarSize;
-  CHECK(active_behavior);
-  if (!active_behavior->ShouldImageCaptureTypeBeAllowed()) {
-    bar_size.set_width(kFullBarSize.width() -
-                       capture_mode::kButtonSize.width() -
-                       capture_mode::kSpaceBetweenCaptureModeTypeButtons);
-  }
-  bar_y -= (kDistanceFromShelfOrHotseatTopDp + bar_size.height());
-  bounds.ClampToCenteredSize(bar_size);
-  bounds.set_y(bar_y);
-  return bounds;
-}
-
 void CaptureModeBarView::OnCaptureSourceChanged(CaptureModeSource new_source) {
   capture_source_view_->OnCaptureSourceChanged(new_source);
 }
diff --git a/ash/capture_mode/capture_mode_bar_view.h b/ash/capture_mode/capture_mode_bar_view.h
index fde635d..9be7334 100644
--- a/ash/capture_mode/capture_mode_bar_view.h
+++ b/ash/capture_mode/capture_mode_bar_view.h
@@ -6,7 +6,6 @@
 #define ASH_CAPTURE_MODE_CAPTURE_MODE_BAR_VIEW_H_
 
 #include "ash/ash_export.h"
-#include "ash/capture_mode/capture_mode_behavior.h"
 #include "ash/capture_mode/capture_mode_types.h"
 #include "base/memory/raw_ptr.h"
 #include "ui/base/metadata/metadata_header_macros.h"
@@ -18,9 +17,10 @@
 
 namespace ash {
 
-class IconButton;
+class CaptureModeBehavior;
 class CaptureModeSourceView;
 class CaptureModeTypeView;
+class IconButton;
 class SystemShadow;
 
 // A view that acts as the content view of the capture mode bar widget.
@@ -62,12 +62,6 @@
   IconButton* settings_button() const { return settings_button_; }
   IconButton* close_button() const { return close_button_; }
 
-  // Gets the ideal bounds in screen coordinates of the bar of widget on the
-  // given `root` window. The `image_toggle_button` will not be shown in the bar
-  // The width of the bar will be adjusted based on the current active behavior
-  static gfx::Rect GetBounds(aura::Window* root,
-                             CaptureModeBehavior* active_behavior);
-
   // Called when either the capture mode source or type changes.
   void OnCaptureSourceChanged(CaptureModeSource new_source);
   void OnCaptureTypeChanged(CaptureModeType new_type);
diff --git a/ash/capture_mode/capture_mode_session.cc b/ash/capture_mode/capture_mode_session.cc
index 7e5ce0f..357167d 100644
--- a/ash/capture_mode/capture_mode_session.cc
+++ b/ash/capture_mode/capture_mode_session.cc
@@ -498,7 +498,8 @@
   ClampCaptureRegionToRootWindowSize();
 
   capture_mode_bar_widget_->Init(CreateWidgetParams(
-      parent, CaptureModeBarView::GetBounds(current_root_, active_behavior_),
+      parent,
+      capture_mode_util::GetCaptureBarBounds(current_root_, active_behavior_),
       "CaptureModeBarWidget"));
   capture_mode_bar_view_ = capture_mode_bar_widget_->SetContentsView(
       std::make_unique<CaptureModeBarView>(active_behavior_));
@@ -1570,7 +1571,7 @@
   // The sequence matters here since settings bounds depend on capture bar
   // bounds.
   capture_mode_bar_widget_->SetBounds(
-      CaptureModeBarView::GetBounds(current_root_, active_behavior_));
+      capture_mode_util::GetCaptureBarBounds(current_root_, active_behavior_));
   MaybeUpdateSettingsBounds();
   if (user_nudge_controller_)
     user_nudge_controller_->Reposition();
diff --git a/ash/capture_mode/capture_mode_util.cc b/ash/capture_mode/capture_mode_util.cc
index 6f466ba..f04926f 100644
--- a/ash/capture_mode/capture_mode_util.cc
+++ b/ash/capture_mode/capture_mode_util.cc
@@ -16,6 +16,8 @@
 #include "ash/public/cpp/window_finder.h"
 #include "ash/resources/vector_icons/vector_icons.h"
 #include "ash/root_window_controller.h"
+#include "ash/shelf/shelf.h"
+#include "ash/shelf/shelf_layout_manager.h"
 #include "ash/shell.h"
 #include "ash/strings/grit/ash_strings.h"
 #include "ash/style/ash_color_id.h"
@@ -49,6 +51,15 @@
 constexpr int kBannerViewBottomRadius = 8;
 constexpr float kScaleUpFactor = 0.8f;
 
+// Full size of capture mode bar view, the width of which will be
+// adjusted according to the provided active behavior.
+constexpr gfx::Size kFullBarSize{376, 64};
+
+// Distance from the bottom of the capture bar to the bottom of the display, top
+// of the hotseat or top of the shelf depending on the shelf alignment or
+// hotseat visibility.
+constexpr int kDistanceFromShelfOrHotseatTopDp = 16;
+
 // The app ID used for the capture mode privacy indicators.
 constexpr char kCaptureModePrivacyIndicatorsId[] = "system-capture-mode";
 
@@ -555,4 +566,36 @@
       std::make_unique<views::HighlightBorder>(corner_radius, type));
 }
 
+gfx::Rect GetCaptureBarBounds(aura::Window* root,
+                              CaptureModeBehavior* active_behavior) {
+  DCHECK(root);
+
+  auto bounds = root->GetBoundsInScreen();
+  int bar_y = bounds.bottom();
+  Shelf* shelf = Shelf::ForWindow(root);
+  if (shelf->IsHorizontalAlignment()) {
+    // Get the widget which has the shelf icons. This is the hotseat widget if
+    // the hotseat is extended, shelf widget otherwise.
+    const bool hotseat_extended =
+        shelf->shelf_layout_manager()->hotseat_state() ==
+        HotseatState::kExtended;
+    views::Widget* shelf_widget =
+        hotseat_extended ? static_cast<views::Widget*>(shelf->hotseat_widget())
+                         : static_cast<views::Widget*>(shelf->shelf_widget());
+    bar_y = shelf_widget->GetWindowBoundsInScreen().y();
+  }
+
+  gfx::Size bar_size = kFullBarSize;
+  CHECK(active_behavior);
+  if (!active_behavior->ShouldImageCaptureTypeBeAllowed()) {
+    bar_size.set_width(kFullBarSize.width() -
+                       capture_mode::kButtonSize.width() -
+                       capture_mode::kSpaceBetweenCaptureModeTypeButtons);
+  }
+  bar_y -= (kDistanceFromShelfOrHotseatTopDp + bar_size.height());
+  bounds.ClampToCenteredSize(bar_size);
+  bounds.set_y(bar_y);
+  return bounds;
+}
+
 }  // namespace ash::capture_mode_util
diff --git a/ash/capture_mode/capture_mode_util.h b/ash/capture_mode/capture_mode_util.h
index 057c419..1ab410f 100644
--- a/ash/capture_mode/capture_mode_util.h
+++ b/ash/capture_mode/capture_mode_util.h
@@ -42,6 +42,7 @@
 
 namespace ash {
 
+class CaptureModeBehavior;
 class StopRecordingButtonTray;
 
 namespace capture_mode_util {
@@ -211,6 +212,11 @@
                         int corner_radius,
                         views::HighlightBorder::Type type);
 
+// Gets the bounds in screen coordinates of the capture bar in the given `root`
+// window. Its width will be adjusted based on the `active_behavior`.
+gfx::Rect GetCaptureBarBounds(aura::Window* root,
+                              CaptureModeBehavior* active_behavior);
+
 }  // namespace capture_mode_util
 
 }  // namespace ash
diff --git a/ash/constants/ash_features.cc b/ash/constants/ash_features.cc
index fcd25b28..20166d6 100644
--- a/ash/constants/ash_features.cc
+++ b/ash/constants/ash_features.cc
@@ -1551,6 +1551,11 @@
              "OobeDrivePinning",
              base::FEATURE_DISABLED_BY_DEFAULT);
 
+// Enables the Gaia info screen in OOBE.
+BASE_FEATURE(kOobeGaiaInfoScreen,
+             "OobeGaiaInfoScreen",
+             base::FEATURE_DISABLED_BY_DEFAULT);
+
 // If enabled, TouchPadScreen will be shown in CHOOBE.
 // enabling this without enabling OobeChoobe flag will have no effect
 BASE_FEATURE(kOobeTouchpadScroll,
@@ -3061,6 +3066,10 @@
   return base::FeatureList::IsEnabled(kEnableOobeChromeVoxHint);
 }
 
+bool IsOobeGaiaInfoScreenEnabled() {
+  return base::FeatureList::IsEnabled(kOobeGaiaInfoScreen);
+}
+
 bool IsOobeHidDetectionRevampEnabled() {
   return base::FeatureList::IsEnabled(kOobeHidDetectionRevamp);
 }
diff --git a/ash/constants/ash_features.h b/ash/constants/ash_features.h
index 5c97225..73bb7f27 100644
--- a/ash/constants/ash_features.h
+++ b/ash/constants/ash_features.h
@@ -458,6 +458,7 @@
 COMPONENT_EXPORT(ASH_CONSTANTS) BASE_DECLARE_FEATURE(kOobeChoobe);
 COMPONENT_EXPORT(ASH_CONSTANTS) BASE_DECLARE_FEATURE(kOobeDrivePinning);
 COMPONENT_EXPORT(ASH_CONSTANTS) BASE_DECLARE_FEATURE(kOobeHidDetectionRevamp);
+COMPONENT_EXPORT(ASH_CONSTANTS) BASE_DECLARE_FEATURE(kOobeGaiaInfoScreen);
 COMPONENT_EXPORT(ASH_CONSTANTS) BASE_DECLARE_FEATURE(kOobeJelly);
 COMPONENT_EXPORT(ASH_CONSTANTS) BASE_DECLARE_FEATURE(kOobeSimon);
 COMPONENT_EXPORT(ASH_CONSTANTS)
@@ -835,6 +836,7 @@
 COMPONENT_EXPORT(ASH_CONSTANTS) bool IsOobeDrivePinningEnabled();
 COMPONENT_EXPORT(ASH_CONSTANTS) bool IsOobeChromeVoxHintEnabled();
 COMPONENT_EXPORT(ASH_CONSTANTS) bool IsOobeHidDetectionRevampEnabled();
+COMPONENT_EXPORT(ASH_CONSTANTS) bool IsOobeGaiaInfoScreenEnabled();
 COMPONENT_EXPORT(ASH_CONSTANTS) bool IsOobeJellyEnabled();
 COMPONENT_EXPORT(ASH_CONSTANTS) bool IsOobeSimonEnabled();
 COMPONENT_EXPORT(ASH_CONSTANTS) bool IsOobeSkipAssistantEnabled();
diff --git a/ash/constants/ash_pref_names.cc b/ash/constants/ash_pref_names.cc
index 4e27d569..ca8d0d4 100644
--- a/ash/constants/ash_pref_names.cc
+++ b/ash/constants/ash_pref_names.cc
@@ -1462,6 +1462,63 @@
 // Copy of owner tap-to-click option to use on login screen.
 const char kOwnerTapToClickEnabled[] = "owner.touchpad.enable_tap_to_click";
 
+// A boolean pref set to true if a user simulates a right click using their
+// keyboard and touchpad with either Alt+Click or Search+Click.
+// The value of this pref will be used to set the default behavior for
+// remapping to right click once the setting is added/configurable in device
+// settings.
+// Default setting:
+//  Boolean Pref is false: Off
+//  Boolean Pref is true: Use state of "kUseSearchForRightClick" flag to
+//  determine if Alt+Click or Search+Click should be the default.
+const char kEventRemappedToRightClick[] =
+    "ash.settings.event_remapped_to_right_click";
+
+// An integer pref for tracking Alt and Search based key event rewrites for
+// the Delete "six pack" key. The value of this pref will be used to set the
+// default behavior for remapping a key event to Delete.
+// Default setting:
+//  Pref contains a positive value: Alt+BackSpace
+//  Pref contains a negative value: Search+BackSpace
+const char kKeyEventRemappedToSixPackDelete[] =
+    "ash.settings.key_event_remapped_to_six_pack_delete";
+
+// An integer pref for tracking Alt and Search based key event rewrites for
+// the Home "six pack" key. The value of this pref will be used to set the
+// default behavior for remapping a key event to Home.
+// Default setting:
+//  Pref contains a positive value: Control+Alt+Up
+//  Pref contains a negative value: Search+Left
+const char kKeyEventRemappedToSixPackHome[] =
+    "ash.settings.key_event_remapped_to_six_pack_home";
+
+// An integer pref for tracking Alt and Search based key event rewrites for
+// the End "six pack" key. The value of this pref will be used to set the
+// default behavior for remapping a key event to End.
+// Default setting:
+//  Pref contains a positive value: Control+Alt+Down
+//  Pref contains a negative value: Search+Right
+const char kKeyEventRemappedToSixPackEnd[] =
+    "ash.settings.key_event_remapped_to_six_pack_end";
+
+// An integer pref for tracking Alt and Search based key event rewrites for
+// the PageUp "six pack" key. The value of this pref will be used to set the
+// default behavior for remapping a key event to PageUp.
+// Default setting:
+//  Pref contains a positive value: Alt+Up
+//  Pref contains a negative value: Search+Up
+const char kKeyEventRemappedToSixPackPageUp[] =
+    "ash.settings.key_event_remapped_to_six_pack_page_up";
+
+// An integer pref for tracking Alt and Search based key event rewrites for
+// the PageDown "six pack" key. The value of this pref will be used to set the
+// default behavior for remapping a key event to PageDown.
+// Default setting:
+//  Pref contains a positive value: Alt+Down
+//  Pref contains a negative value: Search+Down
+const char kKeyEventRemappedToSixPackPageDown[] =
+    "ash.settings.key_event_remapped_to_six_pack_page_down";
+
 // NOTE: New prefs should start with the "ash." prefix. Existing prefs moved
 // into this file should not be renamed, since they may be synced.
 
diff --git a/ash/constants/ash_pref_names.h b/ash/constants/ash_pref_names.h
index f184262..97205814 100644
--- a/ash/constants/ash_pref_names.h
+++ b/ash/constants/ash_pref_names.h
@@ -698,6 +698,23 @@
 COMPONENT_EXPORT(ASH_CONSTANTS)
 extern const char kOwnerTapToClickEnabled[];
 
+COMPONENT_EXPORT(ASH_CONSTANTS)
+extern const char kEventRemappedToRightClick[];
+
+COMPONENT_EXPORT(ASH_CONSTANTS)
+extern const char kKeyEventRemappedToSixPackDelete[];
+
+COMPONENT_EXPORT(ASH_CONSTANTS)
+extern const char kKeyEventRemappedToSixPackHome[];
+
+COMPONENT_EXPORT(ASH_CONSTANTS)
+extern const char kKeyEventRemappedToSixPackEnd[];
+
+COMPONENT_EXPORT(ASH_CONSTANTS)
+extern const char kKeyEventRemappedToSixPackPageUp[];
+
+COMPONENT_EXPORT(ASH_CONSTANTS)
+extern const char kKeyEventRemappedToSixPackPageDown[];
 }  // namespace prefs
 }  // namespace ash
 
diff --git a/ash/display/display_prefs.cc b/ash/display/display_prefs.cc
index a24ae9ac2..7c5ccabd 100644
--- a/ash/display/display_prefs.cc
+++ b/ash/display/display_prefs.cc
@@ -434,26 +434,22 @@
   // be empty.
   DCHECK(!GetDisplayManager()->mixed_mirror_mode_params());
 
-  auto* mirroring_source_id_value = pref_data.Find(kMirroringSourceId);
-  if (!mirroring_source_id_value)
+  auto* mirroring_source_id_string = pref_data.FindString(kMirroringSourceId);
+  if (!mirroring_source_id_string)
     return;
 
-  DCHECK(mirroring_source_id_value->is_string());
   int64_t mirroring_source_id;
-  if (!base::StringToInt64(mirroring_source_id_value->GetString(),
-                           &mirroring_source_id)) {
+  if (!base::StringToInt64(*mirroring_source_id_string, &mirroring_source_id)) {
     return;
   }
 
-  auto* mirroring_destination_ids_value =
-      pref_data.Find(kMirroringDestinationIds);
-  if (!mirroring_destination_ids_value)
+  auto* mirroring_destination_ids_list =
+      pref_data.FindList(kMirroringDestinationIds);
+  if (!mirroring_destination_ids_list)
     return;
 
-  DCHECK(mirroring_destination_ids_value->is_list());
   display::DisplayIdList mirroring_destination_ids;
-  for (const auto& entry : mirroring_destination_ids_value->GetList()) {
-    DCHECK(entry.is_string());
+  for (const auto& entry : *mirroring_destination_ids_list) {
     int64_t id;
     if (!base::StringToInt64(entry.GetString(), &id))
       return;
diff --git a/ash/events/accessibility_event_rewriter_unittest.cc b/ash/events/accessibility_event_rewriter_unittest.cc
index a5ab2d1..8a5aa0a 100644
--- a/ash/events/accessibility_event_rewriter_unittest.cc
+++ b/ash/events/accessibility_event_rewriter_unittest.cc
@@ -210,6 +210,9 @@
   bool NotifyDeprecatedSixPackKeyRewrite(ui::KeyboardCode key_code) override {
     return false;
   }
+  void RecordEventRemappedToRightClick() override {}
+  void RecordSixPackEventRewrite(ui::KeyboardCode key_code,
+                                 bool alt_based) override {}
 
   std::map<std::string, ui::mojom::ModifierKey> modifier_remapping_;
 };
@@ -611,6 +614,10 @@
     return false;
   }
 
+  void RecordEventRemappedToRightClick() override {}
+  void RecordSixPackEventRewrite(ui::KeyboardCode key_code,
+                                 bool alt_based) override {}
+
   std::map<std::string, ui::mojom::ModifierKey> modifier_remapping_;
 
  protected:
diff --git a/ash/public/cpp/login_types.h b/ash/public/cpp/login_types.h
index 2912a39a..3f83c83e1 100644
--- a/ash/public/cpp/login_types.h
+++ b/ash/public/cpp/login_types.h
@@ -85,6 +85,9 @@
   // Closing the login screen extension UI created by a Chrome extension using
   // chrome.loginScreenUi API.
   EXTENSION_LOGIN_CLOSED = 20,
+
+  // Showing Gaia Info screen
+  GAIA_INFO = 21,
 };
 
 // Modes of the managed device, which is used to update the visibility of
diff --git a/ash/public/cpp/notification_utils.cc b/ash/public/cpp/notification_utils.cc
index e8ac8ced..aa988ee 100644
--- a/ash/public/cpp/notification_utils.cc
+++ b/ash/public/cpp/notification_utils.cc
@@ -37,10 +37,10 @@
                                             optional_fields,
                                             delegate};
   if (chromeos::features::IsJellyEnabled()) {
-    ui::ColorId color_id = cros_tokens::kCrosSysOnPrimary;
+    ui::ColorId color_id = cros_tokens::kCrosSysPrimary;
     switch (warning_level) {
       case message_center::SystemNotificationWarningLevel::NORMAL:
-        color_id = cros_tokens::kCrosSysOnPrimary;
+        color_id = cros_tokens::kCrosSysPrimary;
         break;
       case message_center::SystemNotificationWarningLevel::WARNING:
         color_id = cros_tokens::kCrosSysWarning;
diff --git a/ash/shelf/login_shelf_view.cc b/ash/shelf/login_shelf_view.cc
index 12cea6ff..f254cd97 100644
--- a/ash/shelf/login_shelf_view.cc
+++ b/ash/shelf/login_shelf_view.cc
@@ -970,7 +970,8 @@
 bool LoginShelfView::ShouldShowGuestAndAppsButtons() const {
   bool dialog_state_allowed = false;
   if (dialog_state_ == OobeDialogState::USER_CREATION ||
-      dialog_state_ == OobeDialogState::GAIA_SIGNIN) {
+      dialog_state_ == OobeDialogState::GAIA_SIGNIN ||
+      dialog_state_ == OobeDialogState::GAIA_INFO) {
     dialog_state_allowed = !login_screen_has_users_ && is_first_signin_step_;
   } else if (dialog_state_ == OobeDialogState::ERROR ||
              dialog_state_ == OobeDialogState::HIDDEN ||
diff --git a/ash/system/audio/audio_detailed_view.cc b/ash/system/audio/audio_detailed_view.cc
index b930d3d4..aea9a86 100644
--- a/ash/system/audio/audio_detailed_view.cc
+++ b/ash/system/audio/audio_detailed_view.cc
@@ -32,6 +32,7 @@
 #include "base/functional/bind.h"
 #include "base/memory/raw_ptr.h"
 #include "base/strings/utf_string_conversions.h"
+#include "chromeos/ash/components/audio/audio_device.h"
 #include "chromeos/ash/components/audio/cras_audio_handler.h"
 #include "chromeos/constants/chromeos_features.h"
 #include "components/live_caption/caption_util.h"
@@ -164,28 +165,30 @@
   CreateItems();
 
   Shell::Get()->accessibility_controller()->AddObserver(this);
+  CrasAudioHandler::Get()->AddAudioObserver(this);
 
-  if (!captions::IsLiveCaptionFeatureSupported()) {
-    return;
-  }
-  speech::SodaInstaller* soda_installer = speech::SodaInstaller::GetInstance();
-  if (soda_installer) {
-    soda_installer->AddObserver(this);
+  if (captions::IsLiveCaptionFeatureSupported()) {
+    speech::SodaInstaller* soda_installer =
+        speech::SodaInstaller::GetInstance();
+    if (soda_installer) {
+      soda_installer->AddObserver(this);
+    }
   }
 }
 
 AudioDetailedView::~AudioDetailedView() {
+  if (captions::IsLiveCaptionFeatureSupported()) {
+    speech::SodaInstaller* soda_installer =
+        speech::SodaInstaller::GetInstance();
+    // `soda_installer` is not guaranteed to be valid, since it's possible for
+    // this class to out-live it. This means that this class cannot use
+    // ScopedObservation and needs to manage removing the observer itself.
+    if (soda_installer) {
+      soda_installer->RemoveObserver(this);
+    }
+  }
+  CrasAudioHandler::Get()->RemoveAudioObserver(this);
   Shell::Get()->accessibility_controller()->RemoveObserver(this);
-  if (!captions::IsLiveCaptionFeatureSupported()) {
-    return;
-  }
-  speech::SodaInstaller* soda_installer = speech::SodaInstaller::GetInstance();
-  // `soda_installer` is not guaranteed to be valid, since it's possible for
-  // this class to out-live it. This means that this class cannot use
-  // ScopedObservation and needs to manage removing the observer itself.
-  if (soda_installer) {
-    soda_installer->RemoveObserver(this);
-  }
 }
 
 views::View* AudioDetailedView::GetAsView() {
@@ -249,10 +252,15 @@
   device_name_container->tri_view()->SetContainerBorder(
       TriView::Container::CENTER,
       views::CreateEmptyBorder(kDevicesTriViewBorder));
-  // TODO(b/262281693): Update the font for `device_name_container` text label.
-  device_name_container->text_label()->SetEnabledColorId(
-      device.active ? cros_tokens::kCrosSysSystemOnPrimaryContainer
-                    : cros_tokens::kCrosSysOnSurfaceVariant);
+  const bool is_muted =
+      is_output_device
+          ? CrasAudioHandler::Get()->IsOutputMutedForDevice(device.id)
+          : CrasAudioHandler::Get()->IsInputMutedForDevice(device.id);
+  UpdateDeviceContainerColor(device_name_container, is_muted);
+  if (chromeos::features::IsJellyEnabled()) {
+    TypographyProvider::Get()->StyleLabel(TypographyToken::kCrosButton2,
+                                          *device_name_container->text_label());
+  }
   device_name_container->SetPaintToLayer();
   // If this device is the active one, disables event handling on
   // `device_name_container` so that `slider` can handle the events.
@@ -340,12 +348,15 @@
                            : kUnifiedMenuLiveCaptionOffIcon,
       cros_tokens::kCrosSysOnSurface, kQsSliderIconSize));
   live_caption_icon_ = toggle_icon.get();
-  // TODO(b/262281693): Update the font and color for `live_caption_view_` text.
   live_caption_view_->AddViewAndLabel(
       std::move(toggle_icon),
       l10n_util::GetStringUTF16(IDS_ASH_STATUS_TRAY_LIVE_CAPTION));
-  live_caption_view_->text_label()->SetEnabledColorId(
-      cros_tokens::kCrosSysOnSurface);
+  if (chromeos::features::IsJellyEnabled()) {
+    live_caption_view_->text_label()->SetEnabledColorId(
+        cros_tokens::kCrosSysOnSurface);
+    TypographyProvider::Get()->StyleLabel(TypographyToken::kCrosButton1,
+                                          *live_caption_view_->text_label());
+  }
 
   // Creates a toggle button on the right.
   auto toggle = std::make_unique<Switch>(base::BindRepeating(
@@ -517,7 +528,7 @@
 }
 
 void AudioDetailedView::OnSettingsButtonClicked() {
-  DCHECK(features::IsAudioSettingsPageEnabled());
+  CHECK(features::IsAudioSettingsPageEnabled());
   if (!TrayPopupUtils::CanOpenWebUISettings()) {
     return;
   }
@@ -639,6 +650,11 @@
     device_map_[device_name_container] = device;
 
     if (features::IsQsRevampEnabled()) {
+      // Sets this flag to false to make the assigned color id effective.
+      // Otherwise it will use `color_utils::BlendForMinContrast()` to improve
+      // label readability over the background.
+      device_name_container->text_label()->SetAutoColorReadabilityEnabled(
+          /*enabled=*/false);
       last_output_device =
           AddDeviceSlider(container, device, device_name_container,
                           /*is_output_device=*/true);
@@ -668,6 +684,9 @@
     device_map_[device_name_container] = device;
 
     if (features::IsQsRevampEnabled()) {
+      // Sets this flag to false to make the assigned color id effective.
+      device_name_container->text_label()->SetAutoColorReadabilityEnabled(
+          /*enabled=*/false);
       AddDeviceSlider(container, device, device_name_container,
                       /*is_output_device=*/false);
     }
@@ -703,6 +722,42 @@
   scroller()->Layout();
 }
 
+void AudioDetailedView::UpdateDeviceContainerColor(
+    HoverHighlightView* device_name_container,
+    bool is_muted) {
+  AudioDeviceMap::iterator iter = device_map_.find(device_name_container);
+  if (iter == device_map_.end()) {
+    return;
+  }
+  const ui::ColorId color_id =
+      iter->second.active
+          ? (is_muted ? cros_tokens::kCrosSysOnSurface
+                      : cros_tokens::kCrosSysSystemOnPrimaryContainer)
+          : cros_tokens::kCrosSysOnSurfaceVariant;
+  device_name_container->text_label()->SetEnabledColorId(color_id);
+  TrayPopupUtils::UpdateCheckMarkColor(device_name_container, color_id);
+}
+
+void AudioDetailedView::UpdateActiveDeviceColor(bool is_input, bool is_muted) {
+  uint64_t device_id =
+      is_input ? CrasAudioHandler::Get()->GetPrimaryActiveInputNode()
+               : CrasAudioHandler::Get()->GetPrimaryActiveOutputNode();
+  // Only the active node could trigger the mute state change. Iterates the
+  // `device_map_` to find the corresponding `device_name_container` and updates
+  // the color.
+  auto it = std::find_if(
+      std::begin(device_map_), std::end(device_map_),
+      [device_id](const std::pair<views::View*, AudioDevice>& audio_device) {
+        return device_id == audio_device.second.id;
+      });
+
+  if (it == std::end(device_map_)) {
+    return;
+  }
+  UpdateDeviceContainerColor(static_cast<HoverHighlightView*>(it->first),
+                             is_muted);
+}
+
 void AudioDetailedView::HandleViewClicked(views::View* view) {
   if (live_caption_view_ && view == live_caption_view_) {
     ToggleLiveCaptionState();
@@ -783,6 +838,20 @@
   MaybeShowSodaMessage(language_code, message);
 }
 
+void AudioDetailedView::OnOutputMuteChanged(bool mute_on) {
+  UpdateActiveDeviceColor(/*is_input=*/false, mute_on);
+}
+
+void AudioDetailedView::OnInputMuteChanged(
+    bool mute_on,
+    CrasAudioHandler::InputMuteChangeMethod method) {
+  UpdateActiveDeviceColor(/*is_input=*/true, mute_on);
+}
+
+void AudioDetailedView::OnInputMutedByMicrophoneMuteSwitchChanged(bool muted) {
+  UpdateActiveDeviceColor(/*is_input=*/true, muted);
+}
+
 BEGIN_METADATA(AudioDetailedView, views::View)
 END_METADATA
 
diff --git a/ash/system/audio/audio_detailed_view.h b/ash/system/audio/audio_detailed_view.h
index 24ce159..6371ced9 100644
--- a/ash/system/audio/audio_detailed_view.h
+++ b/ash/system/audio/audio_detailed_view.h
@@ -16,6 +16,7 @@
 #include "ash/system/tray/tray_detailed_view.h"
 #include "base/memory/raw_ptr.h"
 #include "chromeos/ash/components/audio/audio_device.h"
+#include "chromeos/ash/components/audio/cras_audio_handler.h"
 #include "components/soda/soda_installer.h"
 #include "ui/base/metadata/metadata_header_macros.h"
 #include "ui/views/controls/button/button.h"
@@ -33,7 +34,8 @@
 
 class ASH_EXPORT AudioDetailedView : public TrayDetailedView,
                                      public AccessibilityObserver,
-                                     public speech::SodaInstaller::Observer {
+                                     public speech::SodaInstaller::Observer,
+                                     public CrasAudioHandler::AudioObserver {
  public:
   METADATA_HEADER(AudioDetailedView);
 
@@ -113,6 +115,15 @@
   // Updates the child views in `scroll_content()`.
   void UpdateScrollableList();
 
+  // Updates the label and checkmark color of `device_name_container` based on
+  // whether this device is muted or not.
+  void UpdateDeviceContainerColor(HoverHighlightView* device_name_container,
+                                  bool is_muted);
+
+  // Callback to change the active node's color based on the mute state. Gets
+  // called when the input/output node's mute state changes.
+  void UpdateActiveDeviceColor(bool is_input, bool is_muted);
+
   // TrayDetailedView:
   void HandleViewClicked(views::View* view) override;
   void CreateExtraTitleRowButtons() override;
@@ -124,6 +135,13 @@
   void OnSodaProgress(speech::LanguageCode language_code,
                       int combined_progress) override;
 
+  // CrasAudioHandler::AudioObserver:
+  void OnOutputMuteChanged(bool mute_on) override;
+  void OnInputMuteChanged(
+      bool mute_on,
+      CrasAudioHandler::InputMuteChangeMethod method) override;
+  void OnInputMutedByMicrophoneMuteSwitchChanged(bool muted) override;
+
   typedef std::map<views::View*, AudioDevice> AudioDeviceMap;
 
   std::unique_ptr<MicGainSliderController> mic_gain_controller_;
diff --git a/ash/system/audio/mic_gain_slider_controller.cc b/ash/system/audio/mic_gain_slider_controller.cc
index 40df79b..762a7e1 100644
--- a/ash/system/audio/mic_gain_slider_controller.cc
+++ b/ash/system/audio/mic_gain_slider_controller.cc
@@ -69,7 +69,8 @@
   // Unmute if muted.
   if (CrasAudioHandler::Get()->IsInputMuted()) {
     CrasAudioHandler::Get()->SetMuteForDevice(
-        CrasAudioHandler::Get()->GetPrimaryActiveInputNode(), false);
+        CrasAudioHandler::Get()->GetPrimaryActiveInputNode(),
+        /*mute_on=*/false);
   }
 
   const int level = value * 100;
@@ -82,8 +83,9 @@
   // and level is 0 state in QsRevamp.
   if (features::IsQsRevampEnabled() && level == 0) {
     CrasAudioHandler::Get()->SetMuteForDevice(
-        CrasAudioHandler::Get()->GetPrimaryActiveInputNode(), true);
+        CrasAudioHandler::Get()->GetPrimaryActiveInputNode(), /*mute_on=*/true);
   }
+
   CrasAudioHandler::Get()->SetInputGainPercent(level);
 
   input_gain_metric_delay_timer_.Reset();
@@ -93,6 +95,13 @@
   auto* const audio_handler = CrasAudioHandler::Get();
   const bool mute = !audio_handler->IsInputMuted();
 
+  // If the level is 0, this slider is still muted, and nothing needs to be
+  // done.
+  if (features::IsQsRevampEnabled() &&
+      audio_handler->GetInputGainPercent() == 0) {
+    return;
+  }
+
   TrackToggleUMA(/*target_toggle_state=*/mute);
 
   audio_handler->SetMuteForDevice(
diff --git a/ash/system/audio/mic_gain_slider_view.cc b/ash/system/audio/mic_gain_slider_view.cc
index 31ac9c6..95baaeb 100644
--- a/ash/system/audio/mic_gain_slider_view.cc
+++ b/ash/system/audio/mic_gain_slider_view.cc
@@ -23,6 +23,8 @@
 
 namespace ash {
 
+using Style = QuickSettingsSlider::Style;
+
 namespace {
 // Gets resource ID for the string that should be used for mute state portion of
 // the microphone toggle button tooltip.
@@ -68,7 +70,7 @@
           kImeMenuMicrophoneIcon,
           IDS_ASH_STATUS_TRAY_VOLUME_SLIDER_LABEL,
           /*read_only=*/false,
-          QuickSettingsSlider::Style::kRadioActive),
+          Style::kRadioActive),
       device_id_(device_id),
       internal_(internal) {
   CrasAudioHandler::Get()->AddAudioObserver(this);
@@ -79,8 +81,9 @@
         kSliderChildrenViewSpacing));
     slider()->SetBorder(views::CreateEmptyBorder(kRadioSliderPadding));
     slider()->SetPreferredSize(kRadioSliderPreferredSize);
-    slider_icon()->SetBorder(views::CreateEmptyBorder(kRadioSliderIconPadding));
-    layout->SetFlexForView(slider()->parent(), /*flex=*/1);
+    slider_button()->SetBorder(
+        views::CreateEmptyBorder(kRadioSliderIconPadding));
+    layout->SetFlexForView(slider(), /*flex=*/1);
     layout->set_cross_axis_alignment(
         views::BoxLayout::CrossAxisAlignment::kCenter);
     announcement_view_ = AddChildView(std::make_unique<views::View>());
@@ -162,13 +165,10 @@
           audio_handler->GetDeviceByType(AudioDeviceType::kFrontMic)->id;
     }
 
-    // Checks if the device is muted. If so, sets its level to be 0 to render
-    // the muted state for slider. Otherwise, gets its volume level.
-    level =
-        audio_handler->IsInputMutedForDevice(device_id)
-            ? 0
-            : audio_handler->GetInputGainPercentForDevice(device_id) / 100.f;
-    is_muted = level == 0;
+    level = audio_handler->GetInputGainPercentForDevice(device_id) / 100.f;
+    // Still needs to check if `level` is 0 because toggling the mic mute by
+    // keyboard will not set the input to be muted in `MicGainSliderController`.
+    is_muted = audio_handler->IsInputMutedForDevice(device_id) || level == 0;
   }
 
   if (toast_label()) {
@@ -200,14 +200,15 @@
     // showing and `device_id_` doesn't match with `active_device_id`.
     const bool is_active = show_internal_stub || active_device_id == device_id_;
     static_cast<QuickSettingsSlider*>(slider())->SetSliderStyle(
-        is_active ? QuickSettingsSlider::Style::kRadioActive
-                  : QuickSettingsSlider::Style::kRadioInactive);
+        is_active ? (is_muted ? Style::kRadioActiveMuted : Style::kRadioActive)
+                  : Style::kRadioInactive);
 
-    slider_icon()->SetImage(ui::ImageModel::FromVectorIcon(
-        is_muted ? kMutedMicrophoneIcon : kImeMenuMicrophoneIcon,
-        is_active ? cros_tokens::kCrosSysSystemOnPrimaryContainer
-                  : cros_tokens::kCrosSysOnSurfaceVariant,
-        kQsSliderIconSize));
+    slider_button()->SetVectorIcon(is_muted ? kMutedMicrophoneIcon
+                                            : kImeMenuMicrophoneIcon);
+    slider_button()->SetIconColorId(
+        is_active ? (is_muted ? cros_tokens::kCrosSysOnSurface
+                              : cros_tokens::kCrosSysSystemOnPrimaryContainer)
+                  : cros_tokens::kCrosSysOnSurfaceVariant);
   }
 
   // Slider's value is in finer granularity than audio volume level(0.01),
diff --git a/ash/system/audio/unified_volume_slider_controller.cc b/ash/system/audio/unified_volume_slider_controller.cc
index 7a18153..ff66515 100644
--- a/ash/system/audio/unified_volume_slider_controller.cc
+++ b/ash/system/audio/unified_volume_slider_controller.cc
@@ -4,6 +4,7 @@
 
 #include "ash/system/audio/unified_volume_slider_controller.h"
 
+#include "ash/constants/ash_features.h"
 #include "ash/constants/quick_settings_catalogs.h"
 #include "ash/system/audio/unified_volume_view.h"
 #include "base/metrics/histogram_functions.h"
@@ -30,7 +31,7 @@
           CrasAudioHandler::kMetricsDelayTimerInterval,
           /*receiver=*/this,
           &UnifiedVolumeSliderController::RecordVolumeSourceMetric) {
-  DCHECK(delegate);
+  CHECK(delegate);
 }
 
 UnifiedVolumeSliderController::UnifiedVolumeSliderController()
@@ -80,18 +81,27 @@
   }
 
   const int level = value * 100;
+  auto* const audio_handler = CrasAudioHandler::Get();
 
-  if (level != CrasAudioHandler::Get()->GetOutputVolumePercent()) {
-    TrackValueChangeUMA(/*going_up=*/level >
-                        CrasAudioHandler::Get()->GetOutputVolumePercent());
+  // If the `level` doesn't change, don't do anything.
+  if (level == audio_handler->GetOutputVolumePercent()) {
+    return;
   }
 
-  CrasAudioHandler::Get()->SetOutputVolumePercent(level);
+  TrackValueChangeUMA(/*going_up=*/level >
+                      audio_handler->GetOutputVolumePercent());
+  audio_handler->SetOutputVolumePercent(level);
+
+  // For QsRevamp: Manually sets the mute state since we don't distinguish muted
+  // and level is 0 state in QsRevamp.
+  if (features::IsQsRevampEnabled() && level == 0) {
+    audio_handler->SetOutputMute(/*mute_on=*/true);
+  }
 
   // If the volume is above certain level and it's muted, it should be unmuted.
-  if (CrasAudioHandler::Get()->IsOutputMuted() &&
-      level > CrasAudioHandler::Get()->GetOutputDefaultVolumeMuteThreshold()) {
-    CrasAudioHandler::Get()->SetOutputMute(false);
+  if (audio_handler->IsOutputMuted() &&
+      level > audio_handler->GetOutputDefaultVolumeMuteThreshold()) {
+    audio_handler->SetOutputMute(/*mute_on=*/false);
   }
 
   output_volume_metric_delay_timer_.Reset();
@@ -101,6 +111,12 @@
   auto* const audio_handler = CrasAudioHandler::Get();
   const bool mute = !audio_handler->IsOutputMuted();
 
+  // If the level is 0, the slider is still muted, and nothing needs to be done.
+  if (features::IsQsRevampEnabled() &&
+      audio_handler->GetOutputVolumePercent() == 0) {
+    return;
+  }
+
   TrackToggleUMA(/*target_toggle_state=*/mute);
 
   audio_handler->SetOutputMute(
diff --git a/ash/system/audio/unified_volume_view.cc b/ash/system/audio/unified_volume_view.cc
index 037eb92..6da6a9f 100644
--- a/ash/system/audio/unified_volume_view.cc
+++ b/ash/system/audio/unified_volume_view.cc
@@ -15,6 +15,7 @@
 #include "ash/style/icon_button.h"
 #include "ash/system/audio/unified_volume_slider_controller.h"
 #include "ash/system/tray/tray_constants.h"
+#include "ash/system/unified/quick_settings_slider.h"
 #include "ash/wm/screen_pinning_controller.h"
 #include "ash/wm/window_state.h"
 #include "chromeos/ash/components/audio/cras_audio_handler.h"
@@ -26,6 +27,8 @@
 
 namespace ash {
 
+using Style = QuickSettingsSlider::Style;
+
 namespace {
 
 // References to the icons that correspond to different volume levels.
@@ -39,24 +42,6 @@
 // The maximum index of `kVolumeLevelIcons`.
 constexpr int kVolumeLevels = std::size(kVolumeLevelIcons) - 1;
 
-// The maximum index of `kQsVolumeLevelIcons`.
-constexpr int kQsVolumeLevels =
-    std::size(UnifiedVolumeView::kQsVolumeLevelIcons) - 1;
-
-// Get vector icon reference that corresponds to the given volume level. `level`
-// is between 0.0 to 1.0 inclusive.
-const gfx::VectorIcon& GetVolumeIconForLevel(float level) {
-  if (!features::IsQsRevampEnabled()) {
-    int index = static_cast<int>(std::ceil(level * kVolumeLevels));
-    DCHECK(index >= 0 && index <= kVolumeLevels);
-    return *kVolumeLevelIcons[index];
-  }
-
-  int index = static_cast<int>(std::ceil(level * kQsVolumeLevels));
-  DCHECK(index >= 0 && index <= kQsVolumeLevels);
-  return *UnifiedVolumeView::kQsVolumeLevelIcons[index];
-}
-
 }  // namespace
 
 UnifiedVolumeView::UnifiedVolumeView(
@@ -78,7 +63,6 @@
           &kQuickSettingsRightArrowIcon,
           IDS_ASH_STATUS_TRAY_AUDIO))),
       is_active_output_node_(is_active_output_node),
-      slider_style_(QuickSettingsSlider::Style::kDefault),
       a11y_controller_(Shell::Get()->accessibility_controller()),
       device_id_(CrasAudioHandler::Get()->GetPrimaryActiveOutputNode()) {
   CrasAudioHandler::Get()->AddAudioObserver(this);
@@ -143,10 +127,9 @@
                         kSystemMenuVolumeHighIcon,
                         IDS_ASH_STATUS_TRAY_VOLUME_SLIDER_LABEL,
                         false,
-                        QuickSettingsSlider::Style::kRadioActive),
+                        Style::kRadioActive),
       more_button_(nullptr),
       is_active_output_node_(is_active_output_node),
-      slider_style_(QuickSettingsSlider::Style::kRadioActive),
       a11y_controller_(Shell::Get()->accessibility_controller()),
       device_id_(device_id) {
   CrasAudioHandler::Get()->AddAudioObserver(this);
@@ -156,11 +139,10 @@
       kSliderChildrenViewSpacing));
   slider()->SetBorder(views::CreateEmptyBorder(kRadioSliderPadding));
   slider()->SetPreferredSize(kRadioSliderPreferredSize);
-  slider_icon()->SetBorder(views::CreateEmptyBorder(kRadioSliderIconPadding));
-  layout->SetFlexForView(slider()->parent(), /*flex=*/1);
+  slider_button()->SetBorder(views::CreateEmptyBorder(kRadioSliderIconPadding));
+  layout->SetFlexForView(slider(), /*flex=*/1);
   layout->set_cross_axis_alignment(
       views::BoxLayout::CrossAxisAlignment::kCenter);
-  SetPreferredSize(kRadioSliderPreferredSize);
 
   Update(/*by_user=*/false);
 }
@@ -196,35 +178,55 @@
         IDS_ASH_STATUS_TRAY_VOLUME, state_tooltip_text));
   } else {
     level = audio_handler->GetOutputVolumePercentForDevice(device_id_) / 100.f;
-    // When muted by keyboard, the level stored in `audio_handler` should be
-    // preserved but the slider should appear as muted. `level` is set to 0
-    // manually to update the slider and icon.
-    if (audio_handler->IsOutputMutedForDevice(device_id_)) {
-      level = 0;
-    }
+    // Still needs to check if `level` is 0 because toggling the output mute by
+    // keyboard will not set it to be muted in `UnifiedVolumeSliderController`.
+    const bool is_muted =
+        audio_handler->IsOutputMutedForDevice(device_id_) || level == 0;
+
     auto active_device_id = audio_handler->GetPrimaryActiveOutputNode();
 
-    switch (slider_style_) {
-      case QuickSettingsSlider::Style::kDefault: {
+    // Updates the style before updating the slider icon.
+    auto* qs_slider = static_cast<QuickSettingsSlider*>(slider());
+    const Style slider_style = qs_slider->slider_style();
+    // The default style is for the slider in the main page and in the toast.
+    const bool is_default_style = (slider_style == Style::kDefault ||
+                                   slider_style == Style::kDefaultMuted);
+    if (is_default_style) {
+      qs_slider->SetSliderStyle(is_muted ? Style::kDefaultMuted
+                                         : Style::kDefault);
+    } else if (active_device_id == device_id_) {
+      qs_slider->SetSliderStyle(is_muted ? Style::kRadioActiveMuted
+                                         : Style::kRadioActive);
+    }
+
+    // Updates the slider icon based on mute state and the style for radio
+    // sliders.
+    switch (slider_style) {
+      case Style::kDefault:
+      case Style::kDefaultMuted: {
         // TODO(b/257151067): Adds tooltip.
-        slider_icon()->SetImage(ui::ImageModel::FromVectorIcon(
-            GetVolumeIconForLevel(level),
-            cros_tokens::kCrosSysSystemOnPrimaryContainer, kQsSliderIconSize));
+        slider_button()->SetVectorIcon(is_muted ? kUnifiedMenuVolumeMuteIcon
+                                                : GetVolumeIconForLevel(level));
+        slider_button()->SetIconColorId(
+            is_muted ? cros_tokens::kCrosSysOnSurfaceVariant
+                     : cros_tokens::kCrosSysSystemOnPrimaryContainer);
+
         break;
       }
-      case QuickSettingsSlider::Style::kRadioActive:
-      case QuickSettingsSlider::Style::kRadioInactive: {
-        static_cast<QuickSettingsSlider*>(slider())->SetSliderStyle(
+      case Style::kRadioActive:
+      case Style::kRadioActiveMuted:
+      case Style::kRadioInactive: {
+        qs_slider->SetSliderStyle(
             active_device_id == device_id_
-                ? QuickSettingsSlider::Style::kRadioActive
-                : QuickSettingsSlider::Style::kRadioInactive);
-
-        slider_icon()->SetImage(ui::ImageModel::FromVectorIcon(
-            GetVolumeIconForLevel(level),
+                ? (is_muted ? Style::kRadioActiveMuted : Style::kRadioActive)
+                : Style::kRadioInactive);
+        slider_button()->SetVectorIcon(is_muted ? kUnifiedMenuVolumeMuteIcon
+                                                : GetVolumeIconForLevel(level));
+        slider_button()->SetIconColorId(
             active_device_id == device_id_
-                ? cros_tokens::kCrosSysSystemOnPrimaryContainer
-                : cros_tokens::kCrosSysSecondary,
-            kQsSliderIconSize));
+                ? (is_muted ? cros_tokens::kCrosSysOnSurface
+                            : cros_tokens::kCrosSysSystemOnPrimaryContainer)
+                : cros_tokens::kCrosSysOnSurfaceVariant);
         break;
       }
       default:
@@ -245,6 +247,18 @@
   SetSliderValue(level, by_user);
 }
 
+const gfx::VectorIcon& UnifiedVolumeView::GetVolumeIconForLevel(float level) {
+  if (!features::IsQsRevampEnabled()) {
+    int index = static_cast<int>(std::ceil(level * kVolumeLevels));
+    CHECK(index >= 0 && index <= kVolumeLevels);
+    return *kVolumeLevelIcons[index];
+  }
+
+  int index = static_cast<int>(std::ceil(level * kQsVolumeLevels));
+  CHECK(index >= 0 && index <= kQsVolumeLevels);
+  return *kQsVolumeLevelIcons[index];
+}
+
 void UnifiedVolumeView::OnLiveCaptionButtonPressed() {
   a11y_controller_->live_caption().SetEnabled(
       !a11y_controller_->live_caption().enabled());
diff --git a/ash/system/audio/unified_volume_view.h b/ash/system/audio/unified_volume_view.h
index ddf3dd8..74ef813 100644
--- a/ash/system/audio/unified_volume_view.h
+++ b/ash/system/audio/unified_volume_view.h
@@ -50,16 +50,25 @@
   // References to the icons that correspond to different volume levels used in
   // the `QuickSettingsSlider`. Defined as a public member to be used in tests.
   static constexpr const gfx::VectorIcon* kQsVolumeLevelIcons[] = {
-      &kUnifiedMenuVolumeMuteIcon,    // Mute volume.
+      &kUnifiedMenuVolumeMuteIcon,    // Muted.
       &kUnifiedMenuVolumeMediumIcon,  // Medium volume.
       &kUnifiedMenuVolumeHighIcon,    // High volume.
   };
 
+  // The maximum index of `kQsVolumeLevelIcons`.
+  static constexpr int kQsVolumeLevels = std::size(kQsVolumeLevelIcons) - 1;
+
   IconButton* more_button() { return more_button_; }
 
  private:
+  friend class UnifiedVolumeViewTest;
+
   void Update(bool by_user);
 
+  // Get `VectorIcon` reference that corresponds to the given volume level.
+  // `level` is between 0.0 to 1.0 inclusive.
+  const gfx::VectorIcon& GetVolumeIconForLevel(float level);
+
   // Callback called when the `live_caption_button_` is pressed.
   void OnLiveCaptionButtonPressed();
 
@@ -82,7 +91,6 @@
   // Whether this `UnifiedVolumeView` is the view for the active output node.
   bool const is_active_output_node_;
 
-  QuickSettingsSlider::Style const slider_style_;
   const raw_ptr<AccessibilityControllerImpl, ExperimentalAsh> a11y_controller_;
   uint64_t device_id_ = 0;
   // Owned by the views hierarchy.
diff --git a/ash/system/audio/unified_volume_view_unittest.cc b/ash/system/audio/unified_volume_view_unittest.cc
index 6b38977b..5a3795a 100644
--- a/ash/system/audio/unified_volume_view_unittest.cc
+++ b/ash/system/audio/unified_volume_view_unittest.cc
@@ -39,15 +39,14 @@
 
   // Checks `level` corresponds to the expected icon.
   void CheckSliderIcon(float level) {
-    const gfx::VectorIcon* icon =
-        slider_icon()->GetImageModel().GetVectorIcon().vector_icon();
+    const gfx::VectorIcon& icon = GetIcon(level);
 
     if (level <= 0.0) {
-      EXPECT_STREQ(icon->name, UnifiedVolumeView::kQsVolumeLevelIcons[0]->name);
+      EXPECT_STREQ(icon.name, UnifiedVolumeView::kQsVolumeLevelIcons[0]->name);
     } else if (level <= 0.5) {
-      EXPECT_STREQ(icon->name, UnifiedVolumeView::kQsVolumeLevelIcons[1]->name);
+      EXPECT_STREQ(icon.name, UnifiedVolumeView::kQsVolumeLevelIcons[1]->name);
     } else {
-      EXPECT_STREQ(icon->name, UnifiedVolumeView::kQsVolumeLevelIcons[2]->name);
+      EXPECT_STREQ(icon.name, UnifiedVolumeView::kQsVolumeLevelIcons[2]->name);
     }
   }
 
@@ -61,8 +60,8 @@
 
   views::Slider* slider() { return unified_volume_view()->slider(); }
 
-  views::ImageView* slider_icon() {
-    return unified_volume_view()->slider_icon();
+  const gfx::VectorIcon& GetIcon(float level) {
+    return unified_volume_view()->GetVolumeIconForLevel(level);
   }
 
   UnifiedSystemTrayController* controller() {
@@ -81,9 +80,8 @@
 // `LiveCaption` button, and a drill-in button that leads to
 // `AudioDetailedView`.
 TEST_F(UnifiedVolumeViewTest, SliderButtonComponents) {
-  EXPECT_STREQ(
-      unified_volume_view()->children()[0]->children()[0]->GetClassName(),
-      "QuickSettingsSlider");
+  EXPECT_STREQ(unified_volume_view()->children()[0]->GetClassName(),
+               "QuickSettingsSlider");
 
   // TODO(b/257151067): Updates the a11y name id and tooltip text.
   auto* live_caption_button =
@@ -169,9 +167,9 @@
   CheckSliderIcon(level);
 
   PressAndReleaseKey(ui::KeyboardCode::VKEY_VOLUME_MUTE);
-  // The slider level should be 0 and icon appears as muted.
-  EXPECT_EQ(slider()->GetValue(), 0);
-  CheckSliderIcon(0);
+  // The slider level should remain as `level`.
+  EXPECT_EQ(slider()->GetValue(), level);
+  CheckSliderIcon(level);
 
   PressAndReleaseKey(ui::KeyboardCode::VKEY_VOLUME_UP);
   // The slider level and icon should be restored.
@@ -192,14 +190,14 @@
   CheckSliderIcon(level);
 
   PressAndReleaseKey(ui::KeyboardCode::VKEY_VOLUME_MUTE);
-  // The slider level should be 0 and icon appears as muted.
-  EXPECT_EQ(slider()->GetValue(), 0);
-  CheckSliderIcon(0);
+  // The slider level should remain as `level`.
+  EXPECT_EQ(slider()->GetValue(), level);
+  CheckSliderIcon(level);
 
   PressAndReleaseKey(ui::KeyboardCode::VKEY_VOLUME_DOWN);
   // The slider level and icon should remain muted.
   EXPECT_EQ(slider()->GetValue(), 0);
-  CheckSliderIcon(0);
+  CheckSliderIcon(/*level=*/0);
 }
 
 }  // namespace ash
diff --git a/ash/system/brightness/display_detailed_view.cc b/ash/system/brightness/display_detailed_view.cc
index 4982bf8..6983d11 100644
--- a/ash/system/brightness/display_detailed_view.cc
+++ b/ash/system/brightness/display_detailed_view.cc
@@ -34,9 +34,9 @@
 namespace {
 
 constexpr auto kScrollViewMargin = gfx::Insets::TLBR(0, 12, 16, 12);
-constexpr auto kTileMargin = gfx::Insets::TLBR(4, 4, 8, 4);
-constexpr auto kSliderPadding = gfx::Insets::TLBR(4, 0, 0, 0);
-constexpr auto kSliderBorder = gfx::Insets::VH(0, 4);
+constexpr auto kTileMargin = gfx::Insets::TLBR(4, 4, 12, 4);
+constexpr auto kSliderPadding = gfx::Insets::TLBR(0, 0, 4, 0);
+constexpr auto kSliderBorder = gfx::Insets(4);
 
 }  // namespace
 
@@ -90,7 +90,7 @@
       std::make_unique<views::BoxLayout>(
           views::BoxLayout::Orientation::kHorizontal, kSliderPadding,
           /*between_child_spacing=*/0));
-  slider_layout->SetFlexForView(unified_brightness_view->slider()->parent(),
+  slider_layout->SetFlexForView(unified_brightness_view->slider(),
                                 /*flex=*/1);
   scroll_content()->AddChildView(std::move(unified_brightness_view));
   // Sets the ID for testing.
@@ -105,7 +105,7 @@
 }
 
 void DisplayDetailedView::CreateExtraTitleRowButtons() {
-  DCHECK(!settings_button_);
+  CHECK(!settings_button_);
 
   tri_view()->SetContainerVisible(TriView::Container::END, /*visible=*/true);
 
diff --git a/ash/system/brightness/unified_brightness_view.cc b/ash/system/brightness/unified_brightness_view.cc
index 09e4f6c9..832337b 100644
--- a/ash/system/brightness/unified_brightness_view.cc
+++ b/ash/system/brightness/unified_brightness_view.cc
@@ -24,22 +24,6 @@
 
 namespace ash {
 
-namespace {
-
-// The maximum index of `kBrightnessLevelIcons`.
-const int kBrightnessLevels =
-    std::size(UnifiedBrightnessView::kBrightnessLevelIcons) - 1;
-
-// Get vector icon reference that corresponds to the given brightness level.
-// `level` is between 0.0 to 1.0.
-const gfx::VectorIcon& GetBrightnessIconForLevel(float level) {
-  int index = static_cast<int>(std::ceil(level * kBrightnessLevels));
-  DCHECK(index >= 0 && index <= kBrightnessLevels);
-  return *UnifiedBrightnessView::kBrightnessLevelIcons[index];
-}
-
-}  // namespace
-
 UnifiedBrightnessView::UnifiedBrightnessView(
     UnifiedBrightnessSliderController* controller,
     scoped_refptr<UnifiedSystemTrayModel> model,
@@ -116,13 +100,20 @@
   float level = model_->display_brightness();
 
   if (features::IsQsRevampEnabled()) {
-    slider_icon()->SetImage(ui::ImageModel::FromVectorIcon(
-        GetBrightnessIconForLevel(level),
-        cros_tokens::kCrosSysSystemOnPrimaryContainer, kQsSliderIconSize));
+    slider_button()->SetVectorIcon(GetBrightnessIconForLevel(level));
+    slider_button()->SetIconColorId(
+        cros_tokens::kCrosSysSystemOnPrimaryContainer);
   }
   SetSliderValue(level, by_user);
 }
 
+const gfx::VectorIcon& UnifiedBrightnessView::GetBrightnessIconForLevel(
+    float level) {
+  int index = static_cast<int>(std::ceil(level * kBrightnessLevels));
+  CHECK(index >= 0 && index <= kBrightnessLevels);
+  return *kBrightnessLevelIcons[index];
+}
+
 void UnifiedBrightnessView::OnNightLightButtonPressed() {
   night_light_controller_->Toggle();
 
diff --git a/ash/system/brightness/unified_brightness_view.h b/ash/system/brightness/unified_brightness_view.h
index 9bdb723..61315b1 100644
--- a/ash/system/brightness/unified_brightness_view.h
+++ b/ash/system/brightness/unified_brightness_view.h
@@ -44,7 +44,16 @@
       &kUnifiedMenuBrightnessHighIcon,    // High brightness.
   };
 
+  // The maximum index of `kBrightnessLevelIcons`.
+  static constexpr int kBrightnessLevels = std::size(kBrightnessLevelIcons) - 1;
+
  private:
+  friend class UnifiedBrightnessViewTest;
+
+  // Get vector icon reference that corresponds to the given brightness level.
+  // `level` is between 0.0 to 1.0.
+  const gfx::VectorIcon& GetBrightnessIconForLevel(float level);
+
   // Callback called when `night_light_button_` is pressed.
   void OnNightLightButtonPressed();
 
diff --git a/ash/system/brightness/unified_brightness_view_unittest.cc b/ash/system/brightness/unified_brightness_view_unittest.cc
index c5f8984..97f2381 100644
--- a/ash/system/brightness/unified_brightness_view_unittest.cc
+++ b/ash/system/brightness/unified_brightness_view_unittest.cc
@@ -20,6 +20,12 @@
 
 namespace ash {
 
+namespace {
+
+constexpr float kMinBrightnessLevel = 0.05;
+
+}  // namespace
+
 class UnifiedBrightnessViewTest : public AshTestBase {
  public:
   UnifiedBrightnessViewTest()
@@ -69,8 +75,8 @@
 
   views::Slider* slider() { return unified_brightness_view_->slider(); }
 
-  views::ImageView* slider_icon() {
-    return unified_brightness_view_->slider_icon();
+  const gfx::VectorIcon& GetIcon(float level) {
+    return unified_brightness_view_->GetBrightnessIconForLevel(level);
   }
 
   UnifiedSystemTrayController* controller() {
@@ -97,9 +103,8 @@
 // `NightLight` button, and a drill-in button that leads to the display subpage.
 TEST_F(UnifiedBrightnessViewTest, SliderButtonComponents) {
   EXPECT_EQ(unified_brightness_view()->children().size(), 3u);
-  EXPECT_STREQ(
-      unified_brightness_view()->children()[0]->children()[0]->GetClassName(),
-      "QuickSettingsSlider");
+  EXPECT_STREQ(unified_brightness_view()->children()[0]->GetClassName(),
+               "QuickSettingsSlider");
 
   // TODO(b/257151067): Updates the a11y name id and tooltip text.
   auto* night_light_button =
@@ -122,17 +127,18 @@
   EXPECT_EQ(display_subpage_drill_in_button->GetTooltipText(),
             u"Show display settings");
 
-  // TODO(b/259989534): Add a test after adding the display subpage to test the
-  // drill-in button.
+  // Clicks on the drill-in button and checks `DisplayDetailedView` is shown.
+  EXPECT_FALSE(controller()->IsDetailedViewShown());
+  LeftClickOn(unified_brightness_view()->children()[2]);
+  EXPECT_TRUE(controller()->showing_display_detailed_view());
 }
 
 // Tests that `UnifiedBrightnessView` in the display subpage is made up of a
 // `QuickSettingsSlider`.
 TEST_F(UnifiedBrightnessViewTest, SliderComponent) {
   EXPECT_EQ(brightness_slider()->children().size(), 1u);
-  EXPECT_STREQ(
-      brightness_slider()->children()[0]->children()[0]->GetClassName(),
-      "QuickSettingsSlider");
+  EXPECT_STREQ(brightness_slider()->children()[0]->GetClassName(),
+               "QuickSettingsSlider");
 }
 
 // Tests the slider icon matches the slider level.
@@ -151,19 +157,18 @@
 
     WaitUntilUpdated();
 
-    const gfx::VectorIcon* icon =
-        slider_icon()->GetImageModel().GetVectorIcon().vector_icon();
+    // The minimum level for brightness is 0.05, since `SliderValueChanged()`
+    // will adjust the brightness level and set the icon accordingly.
+    const gfx::VectorIcon& icon = GetIcon(std::max(level, kMinBrightnessLevel));
 
     if (level <= 0.0) {
-      // The minimum level for brightness is 0.05, since `SliderValueChanged()`
-      // will adjust the brightness level and set the icon accordingly.
-      EXPECT_STREQ(icon->name,
+      EXPECT_STREQ(icon.name,
                    UnifiedBrightnessView::kBrightnessLevelIcons[1]->name);
     } else if (level <= 0.5) {
-      EXPECT_STREQ(icon->name,
+      EXPECT_STREQ(icon.name,
                    UnifiedBrightnessView::kBrightnessLevelIcons[1]->name);
     } else {
-      EXPECT_STREQ(icon->name,
+      EXPECT_STREQ(icon.name,
                    UnifiedBrightnessView::kBrightnessLevelIcons[2]->name);
     }
   }
diff --git a/ash/system/media/quick_settings_media_view.cc b/ash/system/media/quick_settings_media_view.cc
index a771e4f..454b952 100644
--- a/ash/system/media/quick_settings_media_view.cc
+++ b/ash/system/media/quick_settings_media_view.cc
@@ -193,4 +193,4 @@
   }
 }
 
-}  // namespace ash
\ No newline at end of file
+}  // namespace ash
diff --git a/ash/system/media/quick_settings_media_view.h b/ash/system/media/quick_settings_media_view.h
index 821a1cc..2528993 100644
--- a/ash/system/media/quick_settings_media_view.h
+++ b/ash/system/media/quick_settings_media_view.h
@@ -49,6 +49,15 @@
   // Updates the media item order given the id order in the list.
   void UpdateItemOrder(std::list<std::string> ids);
 
+  // Helper functions for testing.
+  PaginationModel* pagination_model_for_testing() {
+    return pagination_model_.get();
+  }
+  std::map<const std::string, global_media_controls::MediaItemUIView*>
+  items_for_testing() {
+    return items_;
+  }
+
  private:
   raw_ptr<QuickSettingsMediaViewController> controller_ = nullptr;
 
diff --git a/ash/system/media/quick_settings_media_view_container_unittest.cc b/ash/system/media/quick_settings_media_view_container_unittest.cc
new file mode 100644
index 0000000..9b361a5
--- /dev/null
+++ b/ash/system/media/quick_settings_media_view_container_unittest.cc
@@ -0,0 +1,71 @@
+// Copyright 2023 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "ash/system/media/quick_settings_media_view_container.h"
+
+#include "ash/constants/ash_features.h"
+#include "ash/system/media/media_tray.h"
+#include "ash/system/unified/unified_system_tray.h"
+#include "ash/system/unified/unified_system_tray_bubble.h"
+#include "ash/test/ash_test_base.h"
+#include "base/test/scoped_feature_list.h"
+#include "media/base/media_switches.h"
+
+namespace ash {
+
+class QuickSettingsMediaViewContainerTest : public NoSessionAshTestBase {
+ public:
+  QuickSettingsMediaViewContainerTest() = default;
+  QuickSettingsMediaViewContainerTest(
+      const QuickSettingsMediaViewContainerTest&) = delete;
+  QuickSettingsMediaViewContainerTest& operator=(
+      const QuickSettingsMediaViewContainerTest&) = delete;
+  ~QuickSettingsMediaViewContainerTest() override = default;
+
+  void SetUp() override {
+    feature_list_.InitWithFeatures(
+        {features::kQsRevamp, media::kGlobalMediaControlsCrOSUpdatedUI}, {});
+    NoSessionAshTestBase::SetUp();
+
+    MediaTray::SetPinnedToShelf(false);
+    GetPrimaryUnifiedSystemTray()->ShowBubble();
+  }
+
+  QuickSettingsView* quick_settings_view() {
+    return GetPrimaryUnifiedSystemTray()->bubble()->quick_settings_view();
+  }
+
+  QuickSettingsMediaViewContainer* media_view_container() {
+    return quick_settings_view()->media_view_container_for_testing();
+  }
+
+ private:
+  base::test::ScopedFeatureList feature_list_;
+};
+
+TEST_F(QuickSettingsMediaViewContainerTest, ChangeMediaViewVisibility) {
+  EXPECT_FALSE(media_view_container()->GetVisible());
+
+  quick_settings_view()->SetShowMediaView(true);
+  EXPECT_TRUE(media_view_container()->GetVisible());
+
+  quick_settings_view()->SetShowMediaView(false);
+  EXPECT_FALSE(media_view_container()->GetVisible());
+}
+
+TEST_F(QuickSettingsMediaViewContainerTest,
+       SwitchBetweenMediaViewAndDetailedView) {
+  quick_settings_view()->SetShowMediaView(true);
+  EXPECT_TRUE(media_view_container()->GetVisible());
+
+  // Make the quick settings view navigate to a dummy detailed view.
+  quick_settings_view()->SetDetailedView(std::make_unique<views::View>());
+  EXPECT_FALSE(media_view_container()->GetVisible());
+
+  // Make the quick settings view navigate back to the main view.
+  quick_settings_view()->ResetDetailedView();
+  EXPECT_TRUE(media_view_container()->GetVisible());
+}
+
+}  // namespace ash
diff --git a/ash/system/media/quick_settings_media_view_controller.cc b/ash/system/media/quick_settings_media_view_controller.cc
index 5f09c8d..4334d128 100644
--- a/ash/system/media/quick_settings_media_view_controller.cc
+++ b/ash/system/media/quick_settings_media_view_controller.cc
@@ -24,6 +24,11 @@
   media_session::MediaSessionService* service =
       Shell::Get()->shell_delegate()->GetMediaSessionService();
 
+  // Return if this is called from tests and there is no media service.
+  if (!service) {
+    return;
+  }
+
   mojo::Remote<media_session::mojom::AudioFocusManager> audio_focus_remote;
   mojo::Remote<media_session::mojom::MediaControllerManager>
       controller_manager_remote;
@@ -105,4 +110,4 @@
   media_view_->UpdateItemOrder(media_item_manager_->GetActiveItemIds());
 }
 
-}  // namespace ash
\ No newline at end of file
+}  // namespace ash
diff --git a/ash/system/media/quick_settings_media_view_controller.h b/ash/system/media/quick_settings_media_view_controller.h
index 4f481ff1..508bb8e 100644
--- a/ash/system/media/quick_settings_media_view_controller.h
+++ b/ash/system/media/quick_settings_media_view_controller.h
@@ -68,6 +68,9 @@
   // Updates the order of media items in the quick settings media view.
   void UpdateMediaItemOrder();
 
+  // Helper functions for testing.
+  QuickSettingsMediaView* media_view_for_testing() { return media_view_; }
+
  private:
   raw_ptr<UnifiedSystemTrayController> tray_controller_ = nullptr;
 
diff --git a/ash/system/media/quick_settings_media_view_controller_unittest.cc b/ash/system/media/quick_settings_media_view_controller_unittest.cc
new file mode 100644
index 0000000..89cc2a1
--- /dev/null
+++ b/ash/system/media/quick_settings_media_view_controller_unittest.cc
@@ -0,0 +1,71 @@
+// Copyright 2023 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "ash/system/media/quick_settings_media_view_controller.h"
+
+#include "ash/constants/ash_features.h"
+#include "ash/system/media/media_tray.h"
+#include "ash/system/media/quick_settings_media_view.h"
+#include "ash/system/unified/unified_system_tray.h"
+#include "ash/system/unified/unified_system_tray_bubble.h"
+#include "ash/test/ash_test_base.h"
+#include "base/test/scoped_feature_list.h"
+#include "components/media_message_center/mock_media_notification_item.h"
+#include "media/base/media_switches.h"
+
+namespace ash {
+
+class QuickSettingsMediaViewControllerTest : public NoSessionAshTestBase {
+ public:
+  QuickSettingsMediaViewControllerTest() = default;
+  QuickSettingsMediaViewControllerTest(
+      const QuickSettingsMediaViewControllerTest&) = delete;
+  QuickSettingsMediaViewControllerTest& operator=(
+      const QuickSettingsMediaViewControllerTest&) = delete;
+  ~QuickSettingsMediaViewControllerTest() override = default;
+
+  void SetUp() override {
+    feature_list_.InitWithFeatures(
+        {features::kQsRevamp, media::kGlobalMediaControlsCrOSUpdatedUI}, {});
+    NoSessionAshTestBase::SetUp();
+
+    MediaTray::SetPinnedToShelf(false);
+    GetPrimaryUnifiedSystemTray()->ShowBubble();
+    item_ = std::make_unique<testing::NiceMock<
+        media_message_center::test::MockMediaNotificationItem>>();
+  }
+
+  QuickSettingsMediaViewController* controller() {
+    return GetPrimaryUnifiedSystemTray()
+        ->bubble()
+        ->unified_system_tray_controller()
+        ->media_view_controller();
+  }
+
+  QuickSettingsMediaView* view() {
+    return controller()->media_view_for_testing();
+  }
+
+  base::WeakPtr<media_message_center::test::MockMediaNotificationItem> item() {
+    return item_->GetWeakPtr();
+  }
+
+ private:
+  base::test::ScopedFeatureList feature_list_;
+  std::unique_ptr<media_message_center::test::MockMediaNotificationItem> item_;
+};
+
+TEST_F(QuickSettingsMediaViewControllerTest, ShowOrHideMediaItem) {
+  const std::string item_id = "item_id";
+  EXPECT_EQ(0, static_cast<int>(view()->items_for_testing().size()));
+
+  controller()->ShowMediaItem(item_id, item());
+  EXPECT_EQ(1, static_cast<int>(view()->items_for_testing().size()));
+  EXPECT_TRUE(view()->items_for_testing().contains(item_id));
+
+  controller()->HideMediaItem(item_id);
+  EXPECT_EQ(0, static_cast<int>(view()->items_for_testing().size()));
+}
+
+}  // namespace ash
diff --git a/ash/system/media/quick_settings_media_view_unittest.cc b/ash/system/media/quick_settings_media_view_unittest.cc
new file mode 100644
index 0000000..c4eb0183
--- /dev/null
+++ b/ash/system/media/quick_settings_media_view_unittest.cc
@@ -0,0 +1,75 @@
+// Copyright 2023 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "ash/system/media/quick_settings_media_view.h"
+
+#include "ash/constants/ash_features.h"
+#include "ash/system/media/media_tray.h"
+#include "ash/system/media/quick_settings_media_view_controller.h"
+#include "ash/system/unified/unified_system_tray.h"
+#include "ash/system/unified/unified_system_tray_bubble.h"
+#include "ash/test/ash_test_base.h"
+#include "base/test/scoped_feature_list.h"
+#include "components/global_media_controls/public/views/media_item_ui_view.h"
+#include "components/media_message_center/mock_media_notification_item.h"
+#include "media/base/media_switches.h"
+
+namespace ash {
+
+class QuickSettingsMediaViewTest : public NoSessionAshTestBase {
+ public:
+  QuickSettingsMediaViewTest() = default;
+  QuickSettingsMediaViewTest(const QuickSettingsMediaViewTest&) = delete;
+  QuickSettingsMediaViewTest& operator=(const QuickSettingsMediaViewTest&) =
+      delete;
+  ~QuickSettingsMediaViewTest() override = default;
+
+  void SetUp() override {
+    feature_list_.InitWithFeatures(
+        {features::kQsRevamp, media::kGlobalMediaControlsCrOSUpdatedUI}, {});
+    NoSessionAshTestBase::SetUp();
+
+    MediaTray::SetPinnedToShelf(false);
+    GetPrimaryUnifiedSystemTray()->ShowBubble();
+    item_ = std::make_unique<testing::NiceMock<
+        media_message_center::test::MockMediaNotificationItem>>();
+  }
+
+  QuickSettingsMediaView* view() {
+    return GetPrimaryUnifiedSystemTray()
+        ->bubble()
+        ->unified_system_tray_controller()
+        ->media_view_controller()
+        ->media_view_for_testing();
+  }
+
+  base::WeakPtr<media_message_center::test::MockMediaNotificationItem> item() {
+    return item_->GetWeakPtr();
+  }
+
+ private:
+  base::test::ScopedFeatureList feature_list_;
+  std::unique_ptr<media_message_center::test::MockMediaNotificationItem> item_;
+};
+
+TEST_F(QuickSettingsMediaViewTest, ShowOrHideItem) {
+  const std::string item_id = "item_id";
+  std::unique_ptr<global_media_controls::MediaItemUIView> item_ui =
+      std::make_unique<global_media_controls::MediaItemUIView>(
+          item_id, item(), nullptr, nullptr);
+
+  EXPECT_EQ(0, static_cast<int>(view()->items_for_testing().size()));
+  EXPECT_EQ(-1, view()->pagination_model_for_testing()->total_pages());
+
+  view()->ShowItem(item_id, std::move(item_ui));
+  EXPECT_EQ(1, static_cast<int>(view()->items_for_testing().size()));
+  EXPECT_TRUE(view()->items_for_testing().contains(item_id));
+  EXPECT_EQ(1, view()->pagination_model_for_testing()->total_pages());
+
+  view()->HideItem(item_id);
+  EXPECT_EQ(0, static_cast<int>(view()->items_for_testing().size()));
+  EXPECT_EQ(0, view()->pagination_model_for_testing()->total_pages());
+}
+
+}  // namespace ash
diff --git a/ash/system/message_center/ash_notification_view.cc b/ash/system/message_center/ash_notification_view.cc
index e7e74264..7e09652 100644
--- a/ash/system/message_center/ash_notification_view.cc
+++ b/ash/system/message_center/ash_notification_view.cc
@@ -1723,6 +1723,9 @@
 
   SkColor icon_color = AshColorProvider::Get()->GetContentLayerColor(
       AshColorProvider::ContentLayerType::kInvertedButtonLabelColor);
+  if (chromeos::features::IsJellyEnabled() && GetWidget()) {
+    icon_color = GetColorProvider()->GetColor(cros_tokens::kCrosSysOnPrimary);
+  }
   SkColor icon_background_color = CalculateIconAndButtonsColor(notification);
 
   // TODO(crbug.com/768748): figure out if this has a performance impact and
diff --git a/ash/system/tray/tray_constants.h b/ash/system/tray/tray_constants.h
index a50711c3..f5d3f20 100644
--- a/ash/system/tray/tray_constants.h
+++ b/ash/system/tray/tray_constants.h
@@ -130,7 +130,7 @@
 // Constants used in the QuickSettingsSlider of the `QuickSettingsView`.
 constexpr int kQsSliderIconSize = 20;
 constexpr auto kRadioSliderIconPadding = gfx::Insets::VH(0, 2);
-constexpr auto kRadioSliderPadding = gfx::Insets::TLBR(0, 4, 0, 24);
+constexpr auto kRadioSliderPadding = gfx::Insets::TLBR(4, 4, 4, 24);
 constexpr auto kRadioSliderPreferredSize = gfx::Size(0, 44);
 constexpr auto kRadioSliderViewPadding = gfx::Insets::TLBR(0, 20, 0, 0);
 
diff --git a/ash/system/tray/tray_popup_utils.cc b/ash/system/tray/tray_popup_utils.cc
index f9aad71..c98abb2 100644
--- a/ash/system/tray/tray_popup_utils.cc
+++ b/ash/system/tray/tray_popup_utils.cc
@@ -44,6 +44,7 @@
 #include "ui/views/layout/box_layout.h"
 #include "ui/views/layout/fill_layout.h"
 #include "ui/views/painter.h"
+#include "ui/views/view_utils.h"
 
 namespace ash {
 
@@ -298,15 +299,13 @@
                                               bool checked,
                                               bool enterprise_managed) {
   const int dip_size = GetDefaultSizeOfVectorIcon(kCheckCircleIcon);
-  ui::ImageModel check_mark = ui::ImageModel::FromVectorIcon(
-      kHollowCheckCircleIcon,
-      // The mapping of `cros_tokens::kCrosSysSystemOnPrimaryContainer` cannot
-      // accommodate `check_mark` and other components, so we still want to
-      // guard with Jelly flag here.
+  // The mapping of `cros_tokens::kCrosSysSystemOnPrimaryContainer` cannot
+  // accommodate `check_mark` and other components, so we still want to
+  // guard with Jelly flag here.
+  ui::ImageModel check_mark = CreateCheckMark(
       chromeos::features::IsJellyEnabled()
           ? cros_tokens::kCrosSysSystemOnPrimaryContainer
-          : static_cast<ui::ColorId>(kColorAshIconColorProminent),
-      dip_size);
+          : static_cast<ui::ColorId>(kColorAshIconColorProminent));
   if (enterprise_managed) {
     ui::ImageModel enterprise_managed_icon = ui::ImageModel::FromVectorIcon(
         chromeos::kEnterpriseIcon, kColorAshIconColorBlocked, dip_size);
@@ -319,14 +318,35 @@
 
 void TrayPopupUtils::UpdateCheckMarkVisibility(HoverHighlightView* container,
                                                bool visible) {
-  if (!container)
+  if (!container) {
     return;
+  }
+
   container->SetRightViewVisible(visible);
   container->SetAccessibilityState(
       visible ? HoverHighlightView::AccessibilityState::CHECKED_CHECKBOX
               : HoverHighlightView::AccessibilityState::UNCHECKED_CHECKBOX);
 }
 
+void TrayPopupUtils::UpdateCheckMarkColor(HoverHighlightView* container,
+                                          ui::ColorId color_id) {
+  if (!container || !container->right_view()) {
+    return;
+  }
+
+  auto check_mark = CreateCheckMark(color_id);
+  if (views::IsViewClass<views::ImageView>(container->right_view())) {
+    static_cast<views::ImageView*>(container->right_view())
+        ->SetImage(check_mark);
+  }
+}
+
+ui::ImageModel TrayPopupUtils::CreateCheckMark(ui::ColorId color_id) {
+  return ui::ImageModel::FromVectorIcon(
+      kHollowCheckCircleIcon, color_id,
+      GetDefaultSizeOfVectorIcon(kCheckCircleIcon));
+}
+
 void TrayPopupUtils::SetLabelFontList(views::Label* label, FontStyle style) {
   label->SetAutoColorReadabilityEnabled(false);
   const gfx::FontList google_sans_font_list({"Google Sans"}, gfx::Font::NORMAL,
diff --git a/ash/system/tray/tray_popup_utils.h b/ash/system/tray/tray_popup_utils.h
index 4554ec83..f24af4b8 100644
--- a/ash/system/tray/tray_popup_utils.h
+++ b/ash/system/tray/tray_popup_utils.h
@@ -24,6 +24,10 @@
 class Separator;
 }  // namespace views
 
+namespace ui {
+class ImageModel;
+}  // namespace ui
+
 namespace ash {
 class HoverHighlightView;
 class UnfocusableLabel;
@@ -175,6 +179,13 @@
   static void UpdateCheckMarkVisibility(HoverHighlightView* container,
                                         bool visible);
 
+  // Updates the color of the checkable row |container|.
+  static void UpdateCheckMarkColor(HoverHighlightView* container,
+                                   ui::ColorId color_id);
+
+  // Creates the check mark.
+  static ui::ImageModel CreateCheckMark(ui::ColorId color_id);
+
   // Sets the font list for |label| based on |style|.
   static void SetLabelFontList(views::Label* label, FontStyle style);
 };
diff --git a/ash/system/unified/quick_settings_slider.cc b/ash/system/unified/quick_settings_slider.cc
index a35ed21e3..f96d6a5 100644
--- a/ash/system/unified/quick_settings_slider.cc
+++ b/ash/system/unified/quick_settings_slider.cc
@@ -20,6 +20,8 @@
 
 namespace ash {
 
+using Style = QuickSettingsSlider::Style;
+
 namespace {
 
 // The radius used to draw the rounded empty slider ends.
@@ -36,31 +38,37 @@
 constexpr float kInactiveRadioSliderRoundedRadius = 8.f;
 constexpr float kRadioSliderWidth = 2 * kActiveRadioSliderRoundedRadius;
 
-// TODO(b/256705775): Replace the value once the spec is updated.
 // The thickness of the focus ring border.
 constexpr int kLineThickness = 2;
 // The gap between the focus ring and the slider.
 constexpr int kFocusOffset = 2;
 
-float GetSliderRoundedCornerRadius(QuickSettingsSlider::Style slider_style) {
+// The offset for the slider top padding.
+constexpr int kTopPaddingOffset = 4;
+
+float GetSliderRoundedCornerRadius(Style slider_style) {
   switch (slider_style) {
-    case QuickSettingsSlider::Style::kDefault:
+    case Style::kDefault:
+    case Style::kDefaultMuted:
       return kFullSliderRoundedRadius;
-    case QuickSettingsSlider::Style::kRadioActive:
+    case Style::kRadioActive:
+    case Style::kRadioActiveMuted:
       return kActiveRadioSliderRoundedRadius;
-    case QuickSettingsSlider::Style::kRadioInactive:
+    case Style::kRadioInactive:
       return kInactiveRadioSliderRoundedRadius;
     default:
       NOTREACHED();
   }
 }
 
-float GetSliderWidth(QuickSettingsSlider::Style slider_style) {
+float GetSliderWidth(Style slider_style) {
   switch (slider_style) {
-    case QuickSettingsSlider::Style::kDefault:
+    case Style::kDefault:
+    case Style::kDefaultMuted:
       return kFullSliderWidth;
-    case QuickSettingsSlider::Style::kRadioActive:
-    case QuickSettingsSlider::Style::kRadioInactive:
+    case Style::kRadioActive:
+    case Style::kRadioActiveMuted:
+    case Style::kRadioInactive:
       return kRadioSliderWidth;
     default:
       NOTREACHED();
@@ -95,7 +103,8 @@
 gfx::Rect QuickSettingsSlider::GetInactiveRadioSliderRect() {
   const gfx::Rect content = GetContentsBounds();
   return gfx::Rect(content.x() - kFocusOffset,
-                   content.height() / 2 - kRadioSliderWidth / 2 - kFocusOffset,
+                   content.height() / 2 - kRadioSliderWidth / 2 - kFocusOffset +
+                       kTopPaddingOffset,
                    content.width() + 2 * kFocusOffset,
                    kRadioSliderWidth + 2 * kFocusOffset);
 }
@@ -119,6 +128,10 @@
     case Style::kRadioActive:
       return GetColorProvider()->GetColor(static_cast<ui::ColorId>(
           cros_tokens::kCrosSysSystemPrimaryContainer));
+    case Style::kDefaultMuted:
+      return GetColorProvider()->GetColor(
+          static_cast<ui::ColorId>(cros_tokens::kCrosSysDisabledOpaque));
+    case Style::kRadioActiveMuted:
     case Style::kRadioInactive:
       return GetColorProvider()->GetColor(
           static_cast<ui::ColorId>(cros_tokens::kCrosSysDisabledContainer));
@@ -140,6 +153,8 @@
     case Style::kRadioActive:
       return GetColorProvider()->GetColor(
           static_cast<ui::ColorId>(cros_tokens::kCrosSysHighlightShape));
+    case Style::kDefaultMuted:
+    case Style::kRadioActiveMuted:
     case Style::kRadioInactive:
       return GetColorProvider()->GetColor(
           static_cast<ui::ColorId>(cros_tokens::kCrosSysDisabledContainer));
@@ -162,16 +177,18 @@
   const int width = content.width() - slider_width;
   const int full_width = GetAnimatingValue() * width + slider_width;
   const int x = content.x();
-  const int y = content.height() / 2 - slider_width / 2;
+  const int y = content.height() / 2 - slider_width / 2 + kTopPaddingOffset;
 
   gfx::Rect empty_slider_rect;
   float empty_slider_radius;
   switch (slider_style_) {
-    case Style::kDefault: {
+    case Style::kDefault:
+    case Style::kDefaultMuted: {
       const int empty_width =
           width + kFullSliderRoundedRadius - full_width + kEmptySliderWidth;
       const int x_empty = x + full_width - kEmptySliderRoundedRadius;
-      const int y_empty = content.height() / 2 - kEmptySliderWidth / 2;
+      const int y_empty =
+          content.height() / 2 - kEmptySliderWidth / 2 + kTopPaddingOffset;
 
       empty_slider_rect =
           gfx::Rect(x_empty, y_empty, empty_width, kEmptySliderWidth);
@@ -179,6 +196,7 @@
       break;
     }
     case Style::kRadioActive:
+    case Style::kRadioActiveMuted:
     case Style::kRadioInactive: {
       empty_slider_rect = gfx::Rect(x, y, content.width(), kRadioSliderWidth);
       empty_slider_radius = slider_radius;
diff --git a/ash/system/unified/quick_settings_slider.h b/ash/system/unified/quick_settings_slider.h
index 692bc42..36bdb5cb 100644
--- a/ash/system/unified/quick_settings_slider.h
+++ b/ash/system/unified/quick_settings_slider.h
@@ -40,10 +40,17 @@
     // parts are center-aligned horizontally. The ends of both parts have fully
     // rounded corners.
     kDefault,
+    // Same style as `kDefault`, except for the thumb and trough are in gray for
+    // the muted default sliders.
+    kDefaultMuted,
     // Represents the style where both the full part and the empty part of the
     // slider have a height of `kFullSliderThickness`. The ends are fully
     // rounded.
     kRadioActive,
+    // Same style as `kRadioActive`, except for the thumb and trough are in
+    // gray for the muted radio sliders. Only the active radio sliders will have
+    // the muted state.
+    kRadioActiveMuted,
     // Represents the style where the full part and the empty part also have the
     // same height of `kFullSliderThickness`, except that the ends are not fully
     // rounded but have a radius of `kInactiveRadioSliderRoundedRadius`.
diff --git a/ash/system/unified/quick_settings_view.h b/ash/system/unified/quick_settings_view.h
index bccb03c1d..7647c5c 100644
--- a/ash/system/unified/quick_settings_view.h
+++ b/ash/system/unified/quick_settings_view.h
@@ -116,6 +116,9 @@
   UnifiedMediaControlsContainer* media_controls_container_for_testing() {
     return media_controls_container_;
   }
+  QuickSettingsMediaViewContainer* media_view_container_for_testing() {
+    return media_view_container_;
+  }
   QuickSettingsFooter* footer_for_testing() { return footer_; }
 
  private:
diff --git a/ash/system/unified/unified_slider_bubble_controller.cc b/ash/system/unified/unified_slider_bubble_controller.cc
index 8d0dd64..733358b 100644
--- a/ash/system/unified/unified_slider_bubble_controller.cc
+++ b/ash/system/unified/unified_slider_bubble_controller.cc
@@ -54,7 +54,7 @@
         slider_view->SetLayoutManager(std::make_unique<views::BoxLayout>(
             views::BoxLayout::Orientation::kHorizontal,
             kQsToastSliderViewPadding, kSliderChildrenViewSpacing));
-    layout->SetFlexForView(slider_view->slider()->parent(), /*flex=*/1);
+    layout->SetFlexForView(slider_view->slider(), /*flex=*/1);
     layout->set_cross_axis_alignment(
         views::BoxLayout::CrossAxisAlignment::kCenter);
     return;
@@ -262,7 +262,7 @@
   // If the bubble already exists, update the content of the bubble and extend
   // the autoclose timer.
   if (bubble_widget_) {
-    DCHECK(bubble_view_);
+    CHECK(bubble_view_);
 
     if (slider_type_ != slider_type) {
       bubble_view_->RemoveAllChildViews();
@@ -284,7 +284,7 @@
 
   tray_->CloseSecondaryBubbles();
 
-  DCHECK(!bubble_view_);
+  CHECK(!bubble_view_);
 
   slider_type_ = slider_type;
   CreateSliderController();
diff --git a/ash/system/unified/unified_slider_view.cc b/ash/system/unified/unified_slider_view.cc
index 66c877ac..35ce617 100644
--- a/ash/system/unified/unified_slider_view.cc
+++ b/ash/system/unified/unified_slider_view.cc
@@ -12,6 +12,7 @@
 #include "ash/system/tray/tray_constants.h"
 #include "ash/system/tray/tray_popup_utils.h"
 #include "ash/system/unified/quick_settings_metrics_util.h"
+#include "ash/system/unified/quick_settings_slider.h"
 #include "base/check_op.h"
 #include "ui/base/l10n/l10n_util.h"
 #include "ui/base/metadata/metadata_impl_macros.h"
@@ -35,8 +36,7 @@
 namespace {
 
 constexpr auto kQsSliderRowPadding = gfx::Insets::TLBR(0, 12, 4, 16);
-constexpr auto kQsSliderIconInsets = gfx::Insets::VH(0, 10);
-constexpr auto kQsSliderBorder = gfx::Insets::TLBR(0, 4, 0, 16);
+constexpr auto kQsSliderBorder = gfx::Insets::TLBR(4, 4, 4, 16);
 
 std::unique_ptr<views::Slider> CreateSlider(
     UnifiedSliderListener* listener,
@@ -50,13 +50,13 @@
 }  // namespace
 
 void UnifiedSliderListener::TrackToggleUMA(bool target_toggle_state) {
-  DCHECK_NE(GetCatalogName(), QsSliderCatalogName::kUnknown);
+  CHECK_NE(GetCatalogName(), QsSliderCatalogName::kUnknown);
   quick_settings_metrics_util::RecordQsSliderToggle(
       GetCatalogName(), /*enable=*/target_toggle_state);
 }
 
 void UnifiedSliderListener::TrackValueChangeUMA(bool going_up) {
-  DCHECK_NE(GetCatalogName(), QsSliderCatalogName::kUnknown);
+  CHECK_NE(GetCatalogName(), QsSliderCatalogName::kUnknown);
   quick_settings_metrics_util::RecordQsSliderValueChange(GetCatalogName(),
                                                          /*going_up=*/going_up);
 }
@@ -103,44 +103,39 @@
     return;
   }
 
-  auto container = std::make_unique<views::View>();
-  slider_ =
-      container->AddChildView(CreateSlider(listener, read_only, slider_style));
-  // Uses `icon_container` to hold `slider_icon_` and makes it left align.
-  auto icon_container = std::make_unique<views::View>();
-  icon_container->SetCanProcessEventsWithinSubtree(false);
-
-  slider_icon_ =
-      icon_container->AddChildView(std::make_unique<views::ImageView>());
-  slider_icon_->SetImage(ui::ImageModel::FromVectorIcon(
-      icon, cros_tokens::kCrosSysSystemOnPrimaryContainer, kQsSliderIconSize));
-  // Sets up the `slider_icon_` for RTL since `ImageView` doesn't handle it.
-  slider_icon_->SetFlipCanvasOnPaintForRTLUI(true);
-
-  auto* icon_container_layout =
-      icon_container->SetLayoutManager(std::make_unique<views::BoxLayout>(
-          views::BoxLayout::Orientation::kHorizontal, kQsSliderIconInsets,
-          /*between_child_spacing=*/0));
-  icon_container_layout->set_main_axis_alignment(
+  slider_ = AddChildView(CreateSlider(listener, read_only, slider_style));
+  slider_->SetBorder(views::CreateEmptyBorder(kQsSliderBorder));
+  // Sets `slider_` to have a `BoxLayout` to align the child view
+  // `slider_button_` to the left.
+  auto* slider_layout =
+      slider_->SetLayoutManager(std::make_unique<views::BoxLayout>(
+          views::BoxLayout::Orientation::kHorizontal));
+  slider_layout->set_main_axis_alignment(
       views::BoxLayout::MainAxisAlignment::kStart);
 
-  container->AddChildView(std::move(icon_container));
-  container->SetLayoutManager(std::make_unique<views::FillLayout>());
+  const bool is_default_style =
+      (slider_style == QuickSettingsSlider::Style::kDefault ||
+       slider_style == QuickSettingsSlider::Style::kDefaultMuted);
+  slider_button_ = slider_->AddChildView(std::make_unique<IconButton>(
+      std::move(callback),
+      is_default_style ? IconButton::Type::kMediumFloating
+                       : IconButton::Type::kLargeFloating,
+      &icon, accessible_name_id,
+      /*is_togglable=*/true,
+      /*has_border=*/true));
+  slider_button_->SetIconColorId(cros_tokens::kCrosSysSystemOnPrimaryContainer);
 
   // Prevent an accessibility event while initiallizing this view.
   // Typically the first update of the slider value is conducted by the
   // caller function to reflect the current value.
   slider_->SetEnableAccessibilityEvents(false);
-
   slider_->GetViewAccessibility().OverrideName(
       l10n_util::GetStringUTF16(accessible_name_id));
-  slider_->SetBorder(views::CreateEmptyBorder(kQsSliderBorder));
 
   auto* layout = SetLayoutManager(std::make_unique<views::BoxLayout>(
       views::BoxLayout::Orientation::kHorizontal, kQsSliderRowPadding,
       kSliderChildrenViewSpacing));
-  container_ = AddChildView(std::move(container));
-  layout->SetFlexForView(container_, /*flex=*/1);
+  layout->SetFlexForView(slider_, /*flex=*/1);
   layout->set_cross_axis_alignment(
       views::BoxLayout::CrossAxisAlignment::kCenter);
 
@@ -175,7 +170,7 @@
         accessible_name_id_,
         /*is_togglable=*/true,
         /*has_border=*/true));
-    container_->SetVisible(false);
+    slider_->SetVisible(false);
   }
   toast_label_ = AddChildView(std::make_unique<views::Label>());
   TrayPopupUtils::SetLabelFontList(toast_label_,
diff --git a/ash/system/unified/unified_slider_view.h b/ash/system/unified/unified_slider_view.h
index 62d3c10..90d4628143 100644
--- a/ash/system/unified/unified_slider_view.h
+++ b/ash/system/unified/unified_slider_view.h
@@ -74,7 +74,7 @@
   IconButton* button() { return button_; }
   views::Slider* slider() { return slider_; }
   views::Label* toast_label() { return toast_label_; }
-  views::ImageView* slider_icon() { return slider_icon_; }
+  IconButton* slider_button() { return slider_button_; }
 
   // Sets a slider value. If `by_user` is false, accessibility events will not
   // be triggered.
@@ -95,7 +95,7 @@
   raw_ptr<IconButton, ExperimentalAsh> button_ = nullptr;
   raw_ptr<views::Slider, ExperimentalAsh> slider_ = nullptr;
   raw_ptr<views::Label, ExperimentalAsh> toast_label_ = nullptr;
-  raw_ptr<views::ImageView, ExperimentalAsh> slider_icon_ = nullptr;
+  raw_ptr<IconButton, ExperimentalAsh> slider_button_ = nullptr;
   raw_ptr<views::View, ExperimentalAsh> container_ = nullptr;
 };
 
diff --git a/ash/user_education/user_education_controller.h b/ash/user_education/user_education_controller.h
index 0d715e3e..a204fd8a 100644
--- a/ash/user_education/user_education_controller.h
+++ b/ash/user_education/user_education_controller.h
@@ -10,6 +10,7 @@
 
 #include "ash/ash_export.h"
 #include "ash/public/cpp/session/session_observer.h"
+#include "ash/user_education/user_education_ping_controller.h"
 #include "ash/user_education/user_education_private_api_key.h"
 #include "base/functional/callback_forward.h"
 #include "base/scoped_observation.h"
@@ -57,6 +58,9 @@
   // education services in the browser.
   std::unique_ptr<UserEducationDelegate> delegate_;
 
+  // The controller responsible for creation/management of pings.
+  UserEducationPingController ping_controller_;
+
   // The set of controllers responsible for specific user education features.
   std::set<std::unique_ptr<UserEducationFeatureController>>
       feature_controllers_;
diff --git a/ash/user_education/user_education_controller_unittest.cc b/ash/user_education/user_education_controller_unittest.cc
index be3f078..66b0675 100644
--- a/ash/user_education/user_education_controller_unittest.cc
+++ b/ash/user_education/user_education_controller_unittest.cc
@@ -14,6 +14,7 @@
 #include "ash/user_education/mock_user_education_delegate.h"
 #include "ash/user_education/user_education_ash_test_base.h"
 #include "ash/user_education/user_education_feature_controller.h"
+#include "ash/user_education/user_education_ping_controller.h"
 #include "ash/user_education/welcome_tour/welcome_tour_controller.h"
 #include "base/test/bind.h"
 #include "base/test/scoped_feature_list.h"
@@ -100,6 +101,13 @@
   EXPECT_EQ(!!HoldingSpaceTourController::Get(), IsHoldingSpaceTourEnabled());
 }
 
+// Verifies that the user education ping controller exists iff user education
+// features are enabled.
+TEST_P(UserEducationControllerTest, UserEducationPingControllerExists) {
+  EXPECT_EQ(!!UserEducationPingController::Get(),
+            !!UserEducationController::Get());
+}
+
 // Verifies that the Welcome Tour controller exists iff the feature is enabled.
 TEST_P(UserEducationControllerTest, WelcomeTourControllerExists) {
   EXPECT_EQ(!!WelcomeTourController::Get(), IsWelcomeTourEnabled());
diff --git a/ash/user_education/user_education_ping_controller.cc b/ash/user_education/user_education_ping_controller.cc
new file mode 100644
index 0000000..36f2d06
--- /dev/null
+++ b/ash/user_education/user_education_ping_controller.cc
@@ -0,0 +1,90 @@
+// Copyright 2023 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "ash/user_education/user_education_ping_controller.h"
+
+#include "base/check_op.h"
+#include "base/memory/raw_ptr.h"
+#include "ui/compositor/layer.h"
+#include "ui/compositor/layer_owner.h"
+#include "ui/views/view.h"
+
+namespace ash {
+namespace {
+
+// The singleton instance owned by the `UserEducationController`.
+UserEducationPingController* g_instance = nullptr;
+
+}  // namespace
+
+// UserEducationPingController::Ping -------------------------------------------
+
+class UserEducationPingController::Ping {
+ public:
+  explicit Ping(views::View* view)
+      : view_(view),
+        layer_owner_(std::make_unique<ui::Layer>(ui::LAYER_NOT_DRAWN)) {
+    CHECK(view_->layer());
+
+    // Name ping layers so that they are easy to identify in debugging/testing.
+    layer_owner_.layer()->SetName(kPingLayerName);
+
+    // Add ping layers to the layer tree below `view_` layers so that the ping
+    // appears beneath the `view_`.
+    view_->AddLayerToRegion(layer_owner_.layer(), views::LayerRegion::kBelow);
+  }
+
+  Ping(const Ping&) = delete;
+  Ping& operator=(const Ping&) = delete;
+  ~Ping() = default;
+
+  // Returns the `view_` associated with this ping.
+  const views::View* view() const { return view_; }
+
+ private:
+  // Pointer to the view associated with this ping.
+  const raw_ptr<views::View> view_;
+
+  // The owner for the ping layer which is added to the layer tree below `view_`
+  // layers so that the ping appears beneath the `view_`.
+  ui::LayerOwner layer_owner_;
+};
+
+// UserEducationPingController -------------------------------------------------
+
+UserEducationPingController::UserEducationPingController() {
+  CHECK_EQ(g_instance, nullptr);
+  g_instance = this;
+}
+
+UserEducationPingController::~UserEducationPingController() {
+  CHECK_EQ(g_instance, this);
+  g_instance = nullptr;
+}
+
+// static
+UserEducationPingController* UserEducationPingController::Get() {
+  return g_instance;
+}
+
+bool UserEducationPingController::CreatePing(PingId ping_id,
+                                             views::View* view) {
+  // A ping is not created if a ping already exists for `ping_id` or `view`.
+  for (const auto& [id, ping] : pings_by_id_) {
+    if (id == ping_id || ping->view() == view) {
+      return false;
+    }
+  }
+
+  // A ping isn't created if `view` is not drawn.
+  if (!view->IsDrawn()) {
+    return false;
+  }
+
+  // Create a ping and indicate success.
+  pings_by_id_[ping_id] = std::make_unique<Ping>(view);
+  return true;
+}
+
+}  // namespace ash
diff --git a/ash/user_education/user_education_ping_controller.h b/ash/user_education/user_education_ping_controller.h
new file mode 100644
index 0000000..9d5cfa3
--- /dev/null
+++ b/ash/user_education/user_education_ping_controller.h
@@ -0,0 +1,55 @@
+// 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 ASH_USER_EDUCATION_USER_EDUCATION_PING_CONTROLLER_H_
+#define ASH_USER_EDUCATION_USER_EDUCATION_PING_CONTROLLER_H_
+
+#include <map>
+#include <memory>
+
+#include "ash/ash_export.h"
+
+namespace views {
+class View;
+}  // namespace views
+
+namespace ash {
+
+enum class PingId;
+
+// The singleton controller, owned by the `UserEducationController`, responsible
+// for creation/management of pings.
+class ASH_EXPORT UserEducationPingController {
+ public:
+  // Names for ping layers so they are easy to distinguish in debugging/testing.
+  static constexpr char kPingLayerName[] = "Ping";
+
+  UserEducationPingController();
+  UserEducationPingController(const UserEducationPingController&) = delete;
+  UserEducationPingController& operator=(const UserEducationPingController&) =
+      delete;
+  ~UserEducationPingController();
+
+  // Returns the singleton instance owned by the `UserEducationController`.
+  // NOTE: Exists if and only if user education features are enabled.
+  static UserEducationPingController* Get();
+
+  // Attempts to creates a ping, identified by `ping_id`, for the specified
+  // `view`, returning whether a ping was in fact created. Note that a ping will
+  // *not* be created if:
+  // (a) a ping already exists for the specified `ping_id`;
+  // (b) a ping already exists for the specified `view`;
+  // (c) the specified `view` is not drawn.
+  bool CreatePing(PingId ping_id, views::View* view);
+
+ private:
+  class Ping;
+
+  // Owns pings, mapping them to their associated IDs.
+  std::map<PingId, std::unique_ptr<Ping>> pings_by_id_;
+};
+
+}  // namespace ash
+
+#endif  // ASH_USER_EDUCATION_USER_EDUCATION_PING_CONTROLLER_H_
diff --git a/ash/user_education/user_education_ping_controller_unittest.cc b/ash/user_education/user_education_ping_controller_unittest.cc
new file mode 100644
index 0000000..0486d7f
--- /dev/null
+++ b/ash/user_education/user_education_ping_controller_unittest.cc
@@ -0,0 +1,146 @@
+// Copyright 2023 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "ash/user_education/user_education_ping_controller.h"
+
+#include <memory>
+#include <vector>
+
+#include "ash/constants/ash_features.h"
+#include "ash/user_education/user_education_ash_test_base.h"
+#include "ash/user_education/user_education_types.h"
+#include "base/test/scoped_feature_list.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "ui/compositor/layer.h"
+#include "ui/views/metadata/view_factory.h"
+#include "ui/views/view.h"
+#include "ui/views/widget/widget.h"
+
+namespace ash {
+namespace {
+
+// Aliases.
+using ::testing::Conditional;
+using ::testing::ElementsAre;
+using ::testing::Eq;
+using ::testing::IsEmpty;
+using ::testing::Mock;
+using ::testing::Property;
+
+}  // namespace
+
+// UserEducationPingControllerTest ---------------------------------------------
+
+// Base class for tests of the `UserEducationPingController`.
+class UserEducationPingControllerTest : public UserEducationAshTestBase {
+ public:
+  UserEducationPingControllerTest() {
+    // NOTE: The `UserEducationPingController` exists only when a user education
+    // feature is enabled. Controller existence is verified in test coverage for
+    // the controller's owner.
+    std::vector<base::test::FeatureRef> enabled_features;
+    enabled_features.emplace_back(features::kCaptureModeTour);
+    enabled_features.emplace_back(features::kHoldingSpaceTour);
+    enabled_features.emplace_back(features::kWelcomeTour);
+    scoped_feature_list_.InitWithFeatures(enabled_features, {});
+  }
+
+  // Returns the singleton instance owned by the `UserEducationController`.
+  UserEducationPingController* controller() {
+    return UserEducationPingController::Get();
+  }
+
+  // Returns the view to ping.
+  views::View* view() { return widget_->GetContentsView(); }
+
+  // Creates a ping for the specified view, returning `true` if successful.
+  bool CreatePing(PingId ping_id = PingId::kTest1,
+                  const absl::optional<views::View*>& v = absl::nullopt) {
+    return controller()->CreatePing(ping_id, v.value_or(this->view()));
+  }
+
+  // Expects that no ping exists for the specified view. No ping exists for a
+  // view if the view does not have any associated ping layers.
+  void ExpectNoPing(const absl::optional<views::View*>& v = absl::nullopt) {
+    views::View* const view = v.value_or(this->view());
+    EXPECT_THAT(
+        view->GetLayersInOrder(),
+        Conditional(view->layer(), ElementsAre(Eq(view->layer())), IsEmpty()));
+  }
+
+  // Asserts that a ping exists for the specified view. A ping exists for a view
+  // if the view has ping layers configured with expected properties.
+  void AssertPing(const absl::optional<views::View*>& v = absl::nullopt) {
+    views::View* const view = v.value_or(this->view());
+    ASSERT_THAT(
+        view->GetLayersInOrder(),
+        ElementsAre(Property(&ui::Layer::name,
+                             Eq(UserEducationPingController::kPingLayerName)),
+                    Eq(view->layer())));
+  }
+
+ private:
+  // UserEducationPingControllerTest:
+  void SetUp() override {
+    UserEducationAshTestBase::SetUp();
+
+    // Create a `widget_` whose contents `view()` will be pinged.
+    widget_ = CreateFramelessTestWidget();
+    widget_->SetContentsView(
+        views::Builder<views::View>().SetPaintToLayer().Build());
+    widget_->CenterWindow(gfx::Size(100, 100));
+  }
+
+  // Used to enable user education features which are required for existence of
+  // the `controller()` under test.
+  base::test::ScopedFeatureList scoped_feature_list_;
+
+  // Owns the `view()` to ping.
+  std::unique_ptr<views::Widget> widget_;
+};
+
+// Tests -----------------------------------------------------------------------
+
+// Verifies that a single view cannot be pinged multiple times concurrently but
+// that multiple concurrent pings may exist for distinct views.
+TEST_F(UserEducationPingControllerTest, MultipleConcurrent) {
+  ExpectNoPing();
+
+  // Ping `view()`.
+  EXPECT_TRUE(CreatePing(PingId::kTest1));
+  ASSERT_NO_FATAL_FAILURE(AssertPing());
+
+  // Attempts to ping `view()` multiple times concurrently should fail, even if
+  // no ping yet exists for the specified ping ID.
+  EXPECT_FALSE(CreatePing(PingId::kTest1));
+  ASSERT_NO_FATAL_FAILURE(AssertPing());
+  EXPECT_FALSE(CreatePing(PingId::kTest2));
+  ASSERT_NO_FATAL_FAILURE(AssertPing());
+
+  // Add `another_view` to the view hierarchy.
+  views::View* another_view = nullptr;
+  view()->AddChildView(views::Builder<views::View>()
+                           .CopyAddressTo(&another_view)
+                           .SetPaintToLayer()
+                           .Build());
+
+  // Attempts to ping `another_view` should succeed if and only if no ping yet
+  // exists for the specified ping ID.
+  EXPECT_FALSE(CreatePing(PingId::kTest1, another_view));
+  ExpectNoPing(another_view);
+  EXPECT_TRUE(CreatePing(PingId::kTest2, another_view));
+  ASSERT_NO_FATAL_FAILURE(AssertPing(another_view));
+}
+
+// Verifies that a `view()` cannot be pinged if not drawn.
+TEST_F(UserEducationPingControllerTest, NotIsDrawn) {
+  view()->SetVisible(false);
+  ExpectNoPing();
+
+  EXPECT_FALSE(CreatePing());
+  ExpectNoPing();
+}
+
+}  // namespace ash
diff --git a/ash/user_education/user_education_types.h b/ash/user_education/user_education_types.h
index d3e1f03..c1d767e 100644
--- a/ash/user_education/user_education_types.h
+++ b/ash/user_education/user_education_types.h
@@ -7,7 +7,17 @@
 
 namespace ash {
 
-// Each value uniquely identifies an Ash feature tutorial.
+// Each value uniquely identifies a ping. Used to gate creation of new pings to
+// avoid spamming the user.
+enum class PingId {
+  kMinValue,
+  kTest1 = kMinValue,
+  kTest2,
+  kMaxValue = kTest2,
+};
+
+// Each value uniquely identifies a feature tutorial. Used to gate creation of
+// new feature tutorials to avoid spamming the user.
 enum class TutorialId {
   kMinValue,
   kCaptureModeTourPrototype1 = kMinValue,
diff --git a/ash/wallpaper/wallpaper_controller_impl.cc b/ash/wallpaper/wallpaper_controller_impl.cc
index 467ed9858..9a789f0f 100644
--- a/ash/wallpaper/wallpaper_controller_impl.cc
+++ b/ash/wallpaper/wallpaper_controller_impl.cc
@@ -2149,8 +2149,9 @@
 
 void WallpaperControllerImpl::ShowOobeWallpaper() {
   if (ash::features::IsOobeSimonEnabled()) {
-    const base::FilePath simon_file_path = base::FilePath(FILE_PATH_LITERAL(
-        "/usr/share/chromeos-assets/simon/simon_wallpaper.jpg"));
+    const base::FilePath simon_file_path = base::FilePath(
+        FILE_PATH_LITERAL("/usr/share/chromeos-assets/animated_splash_screen/"
+                          "oobe_wallpaper.jpg"));
     if (!cached_oobe_wallpaper_.image.isNull() &&
         cached_oobe_wallpaper_.file_path == simon_file_path) {
       OnOobeWallpaperDecoded(simon_file_path, cached_oobe_wallpaper_.image);
diff --git a/ash/webui/diagnostics_ui/backend/input/input_data_provider_unittest.cc b/ash/webui/diagnostics_ui/backend/input/input_data_provider_unittest.cc
index 275b80e..098e4ef4 100644
--- a/ash/webui/diagnostics_ui/backend/input/input_data_provider_unittest.cc
+++ b/ash/webui/diagnostics_ui/backend/input/input_data_provider_unittest.cc
@@ -584,6 +584,9 @@
   bool NotifyDeprecatedSixPackKeyRewrite(ui::KeyboardCode key_code) override {
     return false;
   }
+  void RecordEventRemappedToRightClick() override {}
+  void RecordSixPackEventRewrite(ui::KeyboardCode key_code,
+                                 bool alt_based) override {}
 
  protected:
   bool suppress_modifier_key_rewrites_ = false;
diff --git a/ash/wm/desks/desks_unittests.cc b/ash/wm/desks/desks_unittests.cc
index 77ee31f..a52a762 100644
--- a/ash/wm/desks/desks_unittests.cc
+++ b/ash/wm/desks/desks_unittests.cc
@@ -4896,6 +4896,9 @@
   bool NotifyDeprecatedSixPackKeyRewrite(ui::KeyboardCode key_code) override {
     return false;
   }
+  void RecordEventRemappedToRightClick() override {}
+  void RecordSixPackEventRewrite(ui::KeyboardCode key_code,
+                                 bool alt_based) override {}
 
   void SendAccelerator(ui::KeyboardCode key_code, int flags) {
     ui::test::EventGenerator* generator = GetEventGenerator();
diff --git a/ash/wm/overview/overview_grid.cc b/ash/wm/overview/overview_grid.cc
index f76fc3b..bfac56a 100644
--- a/ash/wm/overview/overview_grid.cc
+++ b/ash/wm/overview/overview_grid.cc
@@ -2390,7 +2390,9 @@
   // |window_list_|. However, if a window turns out to be an ignored item,
   // |window_position| remains where the item was as to then reposition the
   // other window's bounds in place of that item.
-  const int height = total_bounds.height() / kTabletLayoutRow;
+  const int height = (total_bounds.height() -
+                      ((kTabletLayoutRow - 1) * kSpaceBetweenItemsDp)) /
+                     kTabletLayoutRow;
   int window_position = 0;
   std::vector<gfx::RectF> rects;
   for (const auto& window : window_list_) {
@@ -2403,7 +2405,8 @@
     // Calculate the width and y position of the item.
     const int width = CalculateWidthAndMaybeSetUnclippedBounds(item, height);
     const int y =
-        height * (window_position % kTabletLayoutRow) + total_bounds.y();
+        (height + kSpaceBetweenItemsDp) * (window_position % kTabletLayoutRow) +
+        total_bounds.y();
 
     // Use the right bounds of the item next to in the row as the x position, if
     // that item exists.
diff --git a/ash/wm/overview/overview_session_unittest.cc b/ash/wm/overview/overview_session_unittest.cc
index fa168675..d5cb30d 100644
--- a/ash/wm/overview/overview_session_unittest.cc
+++ b/ash/wm/overview/overview_session_unittest.cc
@@ -3693,31 +3693,32 @@
   EXPECT_LT(item3_bounds.y(), item4_bounds.y());
 }
 
+// Tests that with the tablet mode layout, some of the windows are offscreen.
 TEST_F(TabletModeOverviewSessionTest, CheckOffscreenWindows) {
-  auto windows = CreateTestWindows(8);
+  auto windows = CreateTestWindows(10);
   ToggleOverview();
   ASSERT_TRUE(InOverviewSession());
 
   OverviewItem* item0 = GetOverviewItemForWindow(windows[0].get());
   OverviewItem* item1 = GetOverviewItemForWindow(windows[1].get());
-  OverviewItem* item6 = GetOverviewItemForWindow(windows[6].get());
-  OverviewItem* item7 = GetOverviewItemForWindow(windows[7].get());
+  OverviewItem* item8 = GetOverviewItemForWindow(windows[8].get());
+  OverviewItem* item9 = GetOverviewItemForWindow(windows[9].get());
 
   const gfx::RectF screen_bounds(GetGridBounds());
   const gfx::RectF item0_bounds = item0->target_bounds();
   const gfx::RectF item1_bounds = item1->target_bounds();
-  const gfx::RectF item6_bounds = item6->target_bounds();
-  const gfx::RectF item7_bounds = item7->target_bounds();
+  const gfx::RectF item8_bounds = item8->target_bounds();
+  const gfx::RectF item9_bounds = item9->target_bounds();
 
   // |item6| should be in the same row of windows as |item0|, but offscreen
   // (one screen length away).
-  EXPECT_FALSE(screen_bounds.Contains(item6_bounds));
-  EXPECT_EQ(item0_bounds.y(), item6_bounds.y());
+  EXPECT_FALSE(screen_bounds.Contains(item8_bounds));
+  EXPECT_EQ(item0_bounds.y(), item8_bounds.y());
   // |item7| should be in the same row of windows as |item1|, but offscreen
   // and below |item6|.
-  EXPECT_FALSE(screen_bounds.Contains(item7_bounds));
-  EXPECT_EQ(item1_bounds.y(), item7_bounds.y());
-  EXPECT_LT(item6_bounds.y(), item7_bounds.y());
+  EXPECT_FALSE(screen_bounds.Contains(item9_bounds));
+  EXPECT_EQ(item1_bounds.y(), item9_bounds.y());
+  EXPECT_LT(item8_bounds.y(), item9_bounds.y());
 }
 
 // Tests to see if windows are not shifted if all already available windows
diff --git a/base/threading/thread_restrictions.h b/base/threading/thread_restrictions.h
index 4c9ff445..536ee97 100644
--- a/base/threading/thread_restrictions.h
+++ b/base/threading/thread_restrictions.h
@@ -395,6 +395,7 @@
 
 namespace remoting {
 class AutoThread;
+class ScopedAllowBlockingForCrashReporting;
 class ScopedBypassIOThreadRestrictions;
 namespace protocol {
 class ScopedAllowSyncPrimitivesForWebRtcDataStreamAdapter;
@@ -640,6 +641,7 @@
   friend class remote_cocoa::SelectFileDialogBridge;
   friend class remoting::
       ScopedBypassIOThreadRestrictions;  // http://crbug.com/1144161
+  friend class remoting::ScopedAllowBlockingForCrashReporting;
   friend class ui::DrmDisplayHostManager;
   friend class ui::ScopedAllowBlockingForGbmSurface;
   friend class ui::SelectFileDialogLinux;
diff --git a/base/types/expected.h b/base/types/expected.h
index 357b170..28eae0c 100644
--- a/base/types/expected.h
+++ b/base/types/expected.h
@@ -116,7 +116,7 @@
 //     return base::ok(std::move(result));
 //   }
 template <typename T>
-class ok<T, /* is_void_v<T> = */ false> {
+class ok<T, /* is_void_v<T> = */ false> final {
  public:
   template <typename U = T, internal::EnableIfOkValueConstruction<T, U> = 0>
   constexpr explicit ok(U&& val) noexcept : value_(std::forward<U>(val)) {}
@@ -148,7 +148,7 @@
 };
 
 template <typename T>
-class ok<T, /* is_void_v<T> = */ true> {
+class ok<T, /* is_void_v<T> = */ true> final {
  public:
   constexpr explicit ok() noexcept = default;
 };
@@ -171,7 +171,7 @@
 // [expected.un.object], class template unexpected
 // https://eel.is/c++draft/expected#un.object
 template <typename E>
-class unexpected {
+class unexpected final {
  public:
   // [expected.un.ctor] Constructors
   template <typename Err = E,
@@ -228,7 +228,7 @@
 // [expected.expected], class template expected
 // https://eel.is/c++draft/expected#expected
 template <typename T, typename E>
-class [[nodiscard]] expected<T, E, /* is_void_v<T> = */ false> {
+class [[nodiscard]] expected<T, E, /* is_void_v<T> = */ false> final {
   // Note: A partial specialization for void value types follows below.
   static_assert(!std::is_void_v<T>, "Error: T must not be void");
 
@@ -637,7 +637,7 @@
 
 // [expected.void], partial specialization of expected for void types
 template <typename T, typename E>
-class [[nodiscard]] expected<T, E, /* is_void_v<T> = */ true> {
+class [[nodiscard]] expected<T, E, /* is_void_v<T> = */ true> final {
   // Note: A partial specialization for non-void value types can be found above.
   static_assert(std::is_void_v<T>, "Error: T must be void");
 
diff --git a/build/fuchsia/test/common.py b/build/fuchsia/test/common.py
index 16d7b92..810868a6 100644
--- a/build/fuchsia/test/common.py
+++ b/build/fuchsia/test/common.py
@@ -179,6 +179,14 @@
     return 'Running' in _get_daemon_status()
 
 
+def check_ssh_config_file() -> None:
+    """Checks for ssh keys and generates them if they are missing."""
+
+    script_path = os.path.join(SDK_ROOT, 'bin', 'fuchsia-common.sh')
+    check_cmd = ['bash', '-c', f'. {script_path}; check-fuchsia-ssh-config']
+    subprocess.run(check_cmd, check=True)
+
+
 def _wait_for_daemon(start=True, timeout_seconds=100):
     """Waits for daemon to reach desired state in a polling loop.
 
diff --git a/build/fuchsia/test/compatible_utils.py b/build/fuchsia/test/compatible_utils.py
index d20b419e..b917a65 100644
--- a/build/fuchsia/test/compatible_utils.py
+++ b/build/fuchsia/test/compatible_utils.py
@@ -11,55 +11,6 @@
 
 from typing import Iterable, List, Optional, Tuple
 
-_SSH_CONFIG = """
-# Configure port 8022 for connecting to a device with the local address.
-# This makes it possible to forward 8022 to a device connected remotely.
-# The fuchsia private key is used for the identity.
-Host 127.0.0.1
-  Port 8022
-
-Host ::1
-  Port 8022
-
-Host *
-# Turn off refusing to connect to hosts whose key has changed
-StrictHostKeyChecking no
-CheckHostIP no
-
-# Disable recording the known hosts
-UserKnownHostsFile=/dev/null
-
-# Do not forward auth agent connection to remote, no X11
-ForwardAgent no
-ForwardX11 no
-
-# Connection timeout in seconds
-ConnectTimeout=10
-
-# Check for server alive in seconds, max count before disconnecting
-ServerAliveInterval 1
-ServerAliveCountMax 10
-
-# Try to keep the master connection open to speed reconnecting.
-ControlMaster auto
-ControlPersist yes
-
-# When expanded, the ControlPath below cannot have more than 90 characters
-# (total of 108 minus 18 used by a random suffix added by ssh).
-# '%C' expands to 40 chars and there are 9 fixed chars, so '~' can expand to
-# up to 41 chars, which is a reasonable limit for a user's home in most
-# situations. If '~' expands to more than 41 chars, the ssh connection
-# will fail with an error like:
-#     unix_listener: path "..." too long for Unix domain socket
-# A possible solution is to use /tmp instead of ~, but it has
-# its own security concerns.
-ControlPath=~/.ssh/fx-%C
-
-# Connect with user, use the identity specified.
-User fuchsia
-IdentitiesOnly yes
-IdentityFile ~/.ssh/fuchsia_ed25519
-GSSAPIDelegateCredentials no"""
 
 # File indicating version of an image downloaded to the host
 _BUILD_ARGS = "buildargs.gn"
@@ -171,11 +122,11 @@
     """Get the prefix of a barebone ssh command."""
 
     ssh_addr, ssh_port = parse_host_port(host_port_pair)
-    ssh_config = os.path.expanduser('~/.fuchsia/sshconfig')
-    if not os.path.isfile(ssh_config):
-        with open(ssh_config, "w") as f:
-            f.write(_SSH_CONFIG)
-    return ['ssh', '-F', ssh_config, ssh_addr, '-p', str(ssh_port)]
+    return [
+        'ssh', '-F',
+        os.path.expanduser('~/.fuchsia/sshconfig'), ssh_addr, '-p',
+        str(ssh_port)
+    ]
 
 
 def install_symbols(package_paths: Iterable[str],
diff --git a/build/fuchsia/test/ffx_emulator.py b/build/fuchsia/test/ffx_emulator.py
index bfa1c7d..be473ccb 100644
--- a/build/fuchsia/test/ffx_emulator.py
+++ b/build/fuchsia/test/ffx_emulator.py
@@ -13,7 +13,7 @@
 
 from contextlib import AbstractContextManager
 
-from common import find_image_in_sdk, get_system_info, \
+from common import check_ssh_config_file, find_image_in_sdk, get_system_info, \
                    run_ffx_command, SDK_ROOT
 from compatible_utils import get_host_arch, get_sdk_hash
 
@@ -55,6 +55,7 @@
     def _start_emulator(self) -> None:
         """Start the emulator."""
         logging.info('Starting emulator %s', self._node_name)
+        check_ssh_config_file()
         emu_command = [
             'emu', 'start', self._product_bundle, '--name', self._node_name
         ]
diff --git a/build/fuchsia/test/flash_device.py b/build/fuchsia/test/flash_device.py
index e58ae403..807b4940 100755
--- a/build/fuchsia/test/flash_device.py
+++ b/build/fuchsia/test/flash_device.py
@@ -14,8 +14,8 @@
 from typing import Optional, Tuple
 
 import common
-from common import BootMode, boot_device, get_system_info, \
-    find_image_in_sdk, register_device_args
+from common import BootMode, boot_device, check_ssh_config_file, \
+    get_system_info, find_image_in_sdk, register_device_args
 from compatible_utils import get_sdk_hash, pave, running_unattended
 from lockfile import lock
 
@@ -144,6 +144,7 @@
 
     system_image_dir = actual_image_dir
     if needs_update:
+        check_ssh_config_file()
         if should_pave:
             if running_unattended():
                 assert target, ('Target ID must be specified on swarming when'
diff --git a/build/fuchsia/test/flash_device_unittests.py b/build/fuchsia/test/flash_device_unittests.py
index 252eacf..afacc6b 100755
--- a/build/fuchsia/test/flash_device_unittests.py
+++ b/build/fuchsia/test/flash_device_unittests.py
@@ -34,13 +34,16 @@
                                                     _TEST_VERSION))
         swarming_patcher = mock.patch('flash_device.running_unattended',
                                       return_value=False)
+        check_patcher = mock.patch('flash_device.check_ssh_config_file')
         time_sleep = mock.patch('time.sleep')
         self._ffx_mock = ffx_patcher.start()
         self._sdk_hash_mock = sdk_hash_patcher.start()
+        self._check_patcher_mock = check_patcher.start()
         self._swarming_mock = swarming_patcher.start()
         self._time_sleep = time_sleep.start()
         self.addCleanup(self._ffx_mock.stop)
         self.addCleanup(self._sdk_hash_mock.stop)
+        self.addCleanup(self._check_patcher_mock.stop)
         self.addCleanup(self._swarming_mock.stop)
         self.addCleanup(self._time_sleep.stop)
 
diff --git a/build/fuchsia/test/test_server_unittests.py b/build/fuchsia/test/test_server_unittests.py
index 4874da6..f601884 100755
--- a/build/fuchsia/test/test_server_unittests.py
+++ b/build/fuchsia/test/test_server_unittests.py
@@ -18,10 +18,10 @@
     """Unittests for test_server.py."""
 
     def setUp(self) -> None:
-        self._log_patcher = mock.patch('test_server.logging.debug')
         self._subprocess_patcher = mock.patch('test_server.subprocess.run')
-        self._log_mock = self._log_patcher.start()
+        self._log_patcher = mock.patch('test_server.logging.debug')
         self._subprocess_mock = self._subprocess_patcher.start()
+        self._log_mock = self._log_patcher.start()
         self.addCleanup(self._log_mock.stop)
         self.addCleanup(self._subprocess_mock.stop)
 
@@ -34,40 +34,36 @@
         cmd_mock.stdout = str(port_pair[0])
         self._subprocess_mock.return_value = cmd_mock
 
-        with mock.patch('compatible_utils.open', mock.mock_open()):
-            forwarder = test_server.SSHPortForwarder(_HOST_PORT_PAIR)
+        forwarder = test_server.SSHPortForwarder(_HOST_PORT_PAIR)
 
-            # Unmap should raise an exception if no ports are mapped.
-            with self.assertRaises(Exception):
-                forwarder.Unmap(port_pair[0])
-
-            forwarder.Map([port_pair])
-            self.assertEqual(self._subprocess_mock.call_count, 2)
-            self.assertEqual(forwarder.GetDevicePortForHostPort(port_pair[1]),
-                             port_pair[0])
-
-            # Unmap should also raise an exception if the unmap command fails.
-            self._subprocess_mock.reset_mock()
-            cmd_mock.returncode = 1
-            with self.assertRaises(Exception):
-                forwarder.Unmap(port_pair[0])
-            self.assertEqual(self._subprocess_mock.call_count, 1)
-
-            self._subprocess_mock.reset_mock()
-            cmd_mock.returncode = 0
+        # Unmap should raise an exception if no ports are mapped.
+        with self.assertRaises(Exception):
             forwarder.Unmap(port_pair[0])
-            self.assertEqual(self._subprocess_mock.call_count, 1)
 
-    @mock.patch('os.path.isfile')
-    def test_port_forward_exception(self, isfile_mock) -> None:
+        forwarder.Map([port_pair])
+        self.assertEqual(self._subprocess_mock.call_count, 2)
+        self.assertEqual(forwarder.GetDevicePortForHostPort(port_pair[1]),
+                         port_pair[0])
+
+        # Unmap should also raise an exception if the unmap command fails.
+        self._subprocess_mock.reset_mock()
+        cmd_mock.returncode = 1
+        with self.assertRaises(Exception):
+            forwarder.Unmap(port_pair[0])
+        self.assertEqual(self._subprocess_mock.call_count, 1)
+
+        self._subprocess_mock.reset_mock()
+        cmd_mock.returncode = 0
+        forwarder.Unmap(port_pair[0])
+        self.assertEqual(self._subprocess_mock.call_count, 1)
+
+    def test_port_forward_exception(self) -> None:
         """Tests that exception is raised if |port_forward| command fails."""
 
         cmd_mock = mock.Mock()
         cmd_mock.returncode = 1
-        isfile_mock.return_value = False
         self._subprocess_mock.return_value = cmd_mock
-        with mock.patch('compatible_utils.open', mock.mock_open()), \
-             self.assertRaises(Exception):
+        with self.assertRaises(Exception):
             test_server.port_forward(_HOST_PORT_PAIR, _HOST_PORT)
 
     @mock.patch('test_server.chrome_test_server_spawner.SpawningServer')
diff --git a/build/install-build-deps.py b/build/install-build-deps.py
index bbe3093..0244775 100755
--- a/build/install-build-deps.py
+++ b/build/install-build-deps.py
@@ -17,7 +17,7 @@
 import sys
 
 
-@functools.cache
+@functools.lru_cache
 def build_apt_package_list():
   print("Building apt package list.", file=sys.stderr)
   output = subprocess.check_output(["apt-cache", "dumpavail"], text=True)
@@ -135,7 +135,7 @@
     sys.exit(1)
 
 
-@functools.cache
+@functools.lru_cache
 def distro_codename():
   return subprocess.check_output(["lsb_release", "--codename", "--short"],
                                  text=True).strip()
diff --git a/build/skia_gold_common/skia_gold_properties.py b/build/skia_gold_common/skia_gold_properties.py
index 91a24cbc..68a175f 100644
--- a/build/skia_gold_common/skia_gold_properties.py
+++ b/build/skia_gold_common/skia_gold_properties.py
@@ -13,11 +13,20 @@
 import logging
 import optparse
 import os
-from typing import Union
+import subprocess
+import sys
+from typing import Optional, Union
+
+CHROMIUM_SRC_DIR = os.path.realpath(
+    os.path.join(os.path.dirname(__file__), '..', '..'))
 
 ParsedCmdArgs = Union[argparse.Namespace, optparse.Values]
 
 
+def _IsWin() -> bool:
+  return sys.platform == 'win32'
+
+
 class SkiaGoldProperties():
   def __init__(self, args: ParsedCmdArgs):
     """Abstract class to validate and store properties related to Skia Gold.
@@ -86,9 +95,17 @@
   def bypass_skia_gold_functionality(self) -> bool:
     return self._bypass_skia_gold_functionality
 
-  @staticmethod
-  def _GetGitOriginMainHeadSha1() -> str:
-    raise NotImplementedError()
+  def _GetGitOriginMainHeadSha1(self) -> Optional[str]:
+    try:
+      return subprocess.check_output(
+          ['git', 'rev-parse', 'origin/main'],
+          shell=_IsWin(),
+          cwd=self._GetGitRepoDirectory()).decode('utf-8').strip()
+    except subprocess.CalledProcessError:
+      return None
+
+  def _GetGitRepoDirectory(self) -> str:
+    return CHROMIUM_SRC_DIR
 
   def _GetGitRevision(self) -> str:
     if not self._git_revision:
diff --git a/cc/mojom/render_frame_metadata.mojom b/cc/mojom/render_frame_metadata.mojom
index bd2a069..5d33118e 100644
--- a/cc/mojom/render_frame_metadata.mojom
+++ b/cc/mojom/render_frame_metadata.mojom
@@ -4,7 +4,6 @@
 
 module cc.mojom;
 
-import "mojo/public/mojom/base/time.mojom";
 import "services/viz/public/mojom/compositing/local_surface_id.mojom";
 import "services/viz/public/mojom/compositing/selection.mojom";
 import "services/viz/public/mojom/compositing/vertical_scroll_direction.mojom";
@@ -81,14 +80,6 @@
 
   viz.mojom.VerticalScrollDirection new_vertical_scroll_direction;
 
-  // The cumulative time spent performing visual updates since the last commit.
-  // Tracked for all `local_surface_id` before this one.
-  mojo_base.mojom.TimeDelta previous_surfaces_visual_update_duration;
-
-  // The cumulative time spend performing visual updates for the current
-  // `local_surface_id` since the last commit.
-  mojo_base.mojom.TimeDelta current_surface_visual_update_duration;
-
   // Used to position Android bottom bar, whose position is computed by the
   // renderer compositor.
   [EnableIf=is_android]
diff --git a/cc/mojom/render_frame_metadata_mojom_traits.cc b/cc/mojom/render_frame_metadata_mojom_traits.cc
index 8204a2bc..826bca8a 100644
--- a/cc/mojom/render_frame_metadata_mojom_traits.cc
+++ b/cc/mojom/render_frame_metadata_mojom_traits.cc
@@ -5,7 +5,6 @@
 #include "cc/mojom/render_frame_metadata_mojom_traits.h"
 
 #include "build/build_config.h"
-#include "mojo/public/cpp/base/time_mojom_traits.h"
 #include "services/viz/public/cpp/compositing/selection_mojom_traits.h"
 #include "services/viz/public/cpp/compositing/vertical_scroll_direction_mojom_traits.h"
 #include "third_party/skia/include/core/SkColor.h"
@@ -57,10 +56,6 @@
          data.ReadLocalSurfaceId(&out->local_surface_id) &&
          data.ReadNewVerticalScrollDirection(
              &out->new_vertical_scroll_direction) &&
-         data.ReadPreviousSurfacesVisualUpdateDuration(
-             &out->previous_surfaces_visual_update_duration) &&
-         data.ReadCurrentSurfaceVisualUpdateDuration(
-             &out->current_surface_visual_update_duration) &&
          data.ReadRootBackgroundColor(&out->root_background_color);
 }
 
diff --git a/cc/mojom/render_frame_metadata_mojom_traits.h b/cc/mojom/render_frame_metadata_mojom_traits.h
index 8423313..5056240e 100644
--- a/cc/mojom/render_frame_metadata_mojom_traits.h
+++ b/cc/mojom/render_frame_metadata_mojom_traits.h
@@ -98,16 +98,6 @@
     return metadata.new_vertical_scroll_direction;
   }
 
-  static base::TimeDelta previous_surfaces_visual_update_duration(
-      const cc::RenderFrameMetadata& metadata) {
-    return metadata.previous_surfaces_visual_update_duration;
-  }
-
-  static base::TimeDelta current_surface_visual_update_duration(
-      const cc::RenderFrameMetadata& metadata) {
-    return metadata.current_surface_visual_update_duration;
-  }
-
 #if BUILDFLAG(IS_ANDROID)
   static float bottom_controls_height(const cc::RenderFrameMetadata& metadata) {
     return metadata.bottom_controls_height;
diff --git a/cc/trees/commit_state.cc b/cc/trees/commit_state.cc
index e74dec2f..19210a0 100644
--- a/cc/trees/commit_state.cc
+++ b/cc/trees/commit_state.cc
@@ -38,10 +38,7 @@
       overscroll_behavior(prev.overscroll_behavior),
       background_color(prev.background_color),
       viewport_property_ids(prev.viewport_property_ids),
-      local_surface_id_from_parent(prev.local_surface_id_from_parent),
-      previous_surfaces_visual_update_duration(
-          prev.previous_surfaces_visual_update_duration),
-      visual_update_duration(prev.visual_update_duration) {
+      local_surface_id_from_parent(prev.local_surface_id_from_parent) {
   memcpy(event_listener_properties, prev.event_listener_properties,
          sizeof(event_listener_properties));
 }
diff --git a/cc/trees/commit_state.h b/cc/trees/commit_state.h
index b854ded..030daf8 100644
--- a/cc/trees/commit_state.h
+++ b/cc/trees/commit_state.h
@@ -105,8 +105,6 @@
   SkColor4f background_color = SkColors::kWhite;
   ViewportPropertyIds viewport_property_ids;
   viz::LocalSurfaceId local_surface_id_from_parent;
-  base::TimeDelta previous_surfaces_visual_update_duration;
-  base::TimeDelta visual_update_duration;
 
   // -------------------------------------------------------------------------
   // Take/reset: these values are reset on the LayerTreeHost between commits.
diff --git a/cc/trees/layer_tree_host.cc b/cc/trees/layer_tree_host.cc
index a659fcfc..1f00bc2eb 100644
--- a/cc/trees/layer_tree_host.cc
+++ b/cc/trees/layer_tree_host.cc
@@ -409,8 +409,6 @@
   client_->WillCommit(has_updates ? *result : *pending_commit_state());
   pending_commit_state()->source_frame_number++;
   commit_completion_event_ = std::move(completion);
-  pending_commit_state()->previous_surfaces_visual_update_duration =
-      base::TimeDelta();
   return result;
 }
 
@@ -1590,13 +1588,6 @@
   pending_commit_state()->local_surface_id_from_parent =
       local_surface_id_from_parent;
 
-  // When we are switching to a new viz::LocalSurfaceId add our current visual
-  // update duration to that of previous surfaces, and clear out the total. So
-  // that we can begin to track the updates for this new Surface.
-  pending_commit_state()->previous_surfaces_visual_update_duration +=
-      pending_commit_state()->visual_update_duration;
-  pending_commit_state()->visual_update_duration = base::TimeDelta();
-
   // If the parent sequence number has not advanced, then there is no need to
   // commit anything. This can occur when the child sequence number has
   // advanced. Which means that child has changed visual properites, and the
@@ -2048,9 +2039,4 @@
   return proxy_->GetPercentDroppedFrames();
 }
 
-void LayerTreeHost::IncrementVisualUpdateDuration(
-    base::TimeDelta visual_update_duration) {
-  pending_commit_state()->visual_update_duration += visual_update_duration;
-}
-
 }  // namespace cc
diff --git a/cc/trees/layer_tree_host.h b/cc/trees/layer_tree_host.h
index 9a732cb..bc04de2 100644
--- a/cc/trees/layer_tree_host.h
+++ b/cc/trees/layer_tree_host.h
@@ -896,8 +896,6 @@
     return base::AutoReset<bool>(&syncing_deltas_for_test_, true);
   }
 
-  void IncrementVisualUpdateDuration(base::TimeDelta visual_update_duration);
-
  protected:
   LayerTreeHost(InitParams params, CompositorMode mode);
 
diff --git a/cc/trees/layer_tree_host_impl.cc b/cc/trees/layer_tree_host_impl.cc
index 16117d0b..6128771 100644
--- a/cc/trees/layer_tree_host_impl.cc
+++ b/cc/trees/layer_tree_host_impl.cc
@@ -2487,14 +2487,6 @@
         child_local_surface_id_allocator_.GetCurrentLocalSurfaceId();
   }
 
-  metadata.previous_surfaces_visual_update_duration =
-      active_tree()->previous_surfaces_visual_update_duration();
-  metadata.current_surface_visual_update_duration =
-      active_tree()->visual_update_duration();
-  // We only want to report the durations from a Commit the first time. Not for
-  // subsequent Impl-only frames.
-  active_tree()->ClearVisualUpdateDurations();
-
   return metadata;
 }
 
diff --git a/cc/trees/layer_tree_impl.cc b/cc/trees/layer_tree_impl.cc
index 5471458e..f84a7b7 100644
--- a/cc/trees/layer_tree_impl.cc
+++ b/cc/trees/layer_tree_impl.cc
@@ -748,10 +748,6 @@
   // Transfer page transition directives.
   for (auto& request : commit_state.view_transition_requests)
     AddViewTransitionRequest(std::move(request));
-
-  SetVisualUpdateDurations(
-      commit_state.previous_surfaces_visual_update_duration,
-      commit_state.visual_update_duration);
 }
 
 void LayerTreeImpl::PushPropertyTreesTo(LayerTreeImpl* target_tree) {
@@ -882,9 +878,6 @@
 
   for (auto& request : TakeViewTransitionRequests())
     target_tree->AddViewTransitionRequest(std::move(request));
-
-  target_tree->SetVisualUpdateDurations(
-      previous_surfaces_visual_update_duration_, visual_update_duration_);
 }
 
 void LayerTreeImpl::HandleTickmarksVisibilityChange() {
@@ -2958,19 +2951,6 @@
   return host_impl_->IsReadyToActivate();
 }
 
-void LayerTreeImpl::ClearVisualUpdateDurations() {
-  previous_surfaces_visual_update_duration_ = base::TimeDelta();
-  visual_update_duration_ = base::TimeDelta();
-}
-
-void LayerTreeImpl::SetVisualUpdateDurations(
-    base::TimeDelta previous_surfaces_visual_update_duration,
-    base::TimeDelta visual_update_duration) {
-  previous_surfaces_visual_update_duration_ =
-      previous_surfaces_visual_update_duration;
-  visual_update_duration_ = visual_update_duration;
-}
-
 void LayerTreeImpl::RequestImplSideInvalidationForRerasterTiling() {
   host_impl_->RequestImplSideInvalidationForRerasterTiling();
 }
diff --git a/cc/trees/layer_tree_impl.h b/cc/trees/layer_tree_impl.h
index a3e3e91..18deef8 100644
--- a/cc/trees/layer_tree_impl.h
+++ b/cc/trees/layer_tree_impl.h
@@ -808,17 +808,6 @@
   // output of the current frame.
   bool HasViewTransitionSaveRequest() const;
 
-  void ClearVisualUpdateDurations();
-  void SetVisualUpdateDurations(
-      base::TimeDelta previous_surfaces_visual_update_duration,
-      base::TimeDelta visual_update_duration);
-  base::TimeDelta previous_surfaces_visual_update_duration() const {
-    return previous_surfaces_visual_update_duration_;
-  }
-  base::TimeDelta visual_update_duration() const {
-    return visual_update_duration_;
-  }
-
  protected:
   float ClampPageScaleFactorToLimits(float page_scale_factor) const;
   void PushPageScaleFactorAndLimits(const float* page_scale_factor,
diff --git a/cc/trees/render_frame_metadata.cc b/cc/trees/render_frame_metadata.cc
index a2a7004..0dbd442 100644
--- a/cc/trees/render_frame_metadata.cc
+++ b/cc/trees/render_frame_metadata.cc
@@ -36,10 +36,6 @@
          external_page_scale_factor == other.external_page_scale_factor &&
          top_controls_height == other.top_controls_height &&
          top_controls_shown_ratio == other.top_controls_shown_ratio &&
-         previous_surfaces_visual_update_duration ==
-             other.previous_surfaces_visual_update_duration &&
-         current_surface_visual_update_duration ==
-             other.current_surface_visual_update_duration &&
 #if BUILDFLAG(IS_ANDROID)
          bottom_controls_height == other.bottom_controls_height &&
          bottom_controls_shown_ratio == other.bottom_controls_shown_ratio &&
diff --git a/cc/trees/render_frame_metadata.h b/cc/trees/render_frame_metadata.h
index f2b7335..6a8a428 100644
--- a/cc/trees/render_frame_metadata.h
+++ b/cc/trees/render_frame_metadata.h
@@ -5,7 +5,6 @@
 #ifndef CC_TREES_RENDER_FRAME_METADATA_H_
 #define CC_TREES_RENDER_FRAME_METADATA_H_
 
-#include "base/time/time.h"
 #include "build/build_config.h"
 #include "cc/cc_export.h"
 #include "components/viz/common/quads/selection.h"
@@ -115,14 +114,6 @@
   viz::VerticalScrollDirection new_vertical_scroll_direction =
       viz::VerticalScrollDirection::kNull;
 
-  // The cumulative time spent performing visual updates for all
-  // `local_surface_id` before this one.
-  base::TimeDelta previous_surfaces_visual_update_duration;
-
-  // The cumulative time spent performing visual updates for the current
-  // `local_surface_id`.
-  base::TimeDelta current_surface_visual_update_duration;
-
 #if BUILDFLAG(IS_ANDROID)
   // Used to position Android bottom bar, whose position is computed by the
   // renderer compositor.
diff --git a/chrome/android/chrome_java_resources.gni b/chrome/android/chrome_java_resources.gni
index f245de7d..d577a7fb 100644
--- a/chrome/android/chrome_java_resources.gni
+++ b/chrome/android/chrome_java_resources.gni
@@ -474,6 +474,7 @@
   "java/res/layout/autofill_server_data_label.xml",
   "java/res/layout/autofill_server_data_text_label.xml",
   "java/res/layout/autofill_update_address_profile_prompt.xml",
+  "java/res/layout/automotive_back_button_toolbar.xml",
   "java/res/layout/bookmark_edit.xml",
   "java/res/layout/bookmark_folder_select_activity.xml",
   "java/res/layout/bookmark_main.xml",
diff --git a/chrome/android/chrome_test_java_sources.gni b/chrome/android/chrome_test_java_sources.gni
index 7a287546..e5da9b03 100644
--- a/chrome/android/chrome_test_java_sources.gni
+++ b/chrome/android/chrome_test_java_sources.gni
@@ -74,6 +74,7 @@
   "javatests/src/org/chromium/chrome/browser/autofill/settings/AutofillProfilesFragmentTest.java",
   "javatests/src/org/chromium/chrome/browser/autofill/settings/AutofillServerCardEditorTest.java",
   "javatests/src/org/chromium/chrome/browser/autofill/settings/AutofillTestRule.java",
+  "javatests/src/org/chromium/chrome/browser/automotive/backbuttontoolbar/BackButtonToolbarTest.java",
   "javatests/src/org/chromium/chrome/browser/background_sync/BackgroundSyncTest.java",
   "javatests/src/org/chromium/chrome/browser/background_sync/PeriodicBackgroundSyncTest.java",
   "javatests/src/org/chromium/chrome/browser/bookmarks/BookmarkBridgeTest.java",
diff --git a/chrome/android/features/start_surface/java/src/org/chromium/chrome/features/start_surface/TabSwitcherAndStartSurfaceLayout.java b/chrome/android/features/start_surface/java/src/org/chromium/chrome/features/start_surface/TabSwitcherAndStartSurfaceLayout.java
index c1c9263..b55ca18 100644
--- a/chrome/android/features/start_surface/java/src/org/chromium/chrome/features/start_surface/TabSwitcherAndStartSurfaceLayout.java
+++ b/chrome/android/features/start_surface/java/src/org/chromium/chrome/features/start_surface/TabSwitcherAndStartSurfaceLayout.java
@@ -782,7 +782,7 @@
     }
 
     @VisibleForTesting
-    void setPerfListenerForTesting(PerfListener perfListener) {
+    public void setPerfListenerForTesting(PerfListener perfListener) {
         mPerfListenerForTesting = perfListener;
     }
 
diff --git a/chrome/android/features/start_surface/javatests/start_surface_test_java_sources.gni b/chrome/android/features/start_surface/javatests/start_surface_test_java_sources.gni
index dcc5ff21..e3ae8c03 100644
--- a/chrome/android/features/start_surface/javatests/start_surface_test_java_sources.gni
+++ b/chrome/android/features/start_surface/javatests/start_surface_test_java_sources.gni
@@ -16,6 +16,4 @@
   "//chrome/android/features/start_surface/javatests/src/org/chromium/chrome/features/start_surface/StartSurfaceTabSwitcherTest.java",
   "//chrome/android/features/start_surface/javatests/src/org/chromium/chrome/features/start_surface/StartSurfaceTest.java",
   "//chrome/android/features/start_surface/javatests/src/org/chromium/chrome/features/start_surface/StartSurfaceTestUtils.java",
-  "//chrome/android/features/start_surface/javatests/src/org/chromium/chrome/features/start_surface/TabSwitcherAndStartSurfaceLayoutPerfTest.java",
-  "//chrome/android/features/start_surface/javatests/src/org/chromium/chrome/features/start_surface/TabSwitcherAndStartSurfaceLayoutTest.java",
 ]
diff --git a/chrome/android/features/start_surface/javatests/src/org/chromium/chrome/features/start_surface/TabSwitcherAndStartSurfaceLayoutPerfTest.java b/chrome/android/features/tab_ui/javatests/src/org/chromium/chrome/browser/tasks/tab_management/TabSwitcherAndStartSurfaceLayoutPerfTest.java
similarity index 98%
rename from chrome/android/features/start_surface/javatests/src/org/chromium/chrome/features/start_surface/TabSwitcherAndStartSurfaceLayoutPerfTest.java
rename to chrome/android/features/tab_ui/javatests/src/org/chromium/chrome/browser/tasks/tab_management/TabSwitcherAndStartSurfaceLayoutPerfTest.java
index 2e519be..09202585 100644
--- a/chrome/android/features/start_surface/javatests/src/org/chromium/chrome/features/start_surface/TabSwitcherAndStartSurfaceLayoutPerfTest.java
+++ b/chrome/android/features/tab_ui/javatests/src/org/chromium/chrome/browser/tasks/tab_management/TabSwitcherAndStartSurfaceLayoutPerfTest.java
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-package org.chromium.chrome.features.start_surface;
+package org.chromium.chrome.browser.tasks.tab_management;
 
 import static androidx.test.espresso.matcher.ViewMatchers.withId;
 import static androidx.test.espresso.matcher.ViewMatchers.withParent;
@@ -54,12 +54,10 @@
 import org.chromium.chrome.browser.layouts.animation.CompositorAnimator;
 import org.chromium.chrome.browser.tab.Tab;
 import org.chromium.chrome.browser.tab.TabSelectionType;
-import org.chromium.chrome.browser.tasks.tab_management.TabSwitcherLayout;
 import org.chromium.chrome.browser.tasks.tab_management.TabSwitcherLayout.PerfListener;
-import org.chromium.chrome.browser.tasks.tab_management.TabUiFeatureUtilities;
-import org.chromium.chrome.browser.tasks.tab_management.TabUiTestHelper;
 import org.chromium.chrome.features.start_surface.StartSurfaceTestUtils.LegacyTestParams;
 import org.chromium.chrome.features.start_surface.StartSurfaceTestUtils.RefactorTestParams;
+import org.chromium.chrome.features.start_surface.TabSwitcherAndStartSurfaceLayout;
 import org.chromium.chrome.test.ChromeJUnit4RunnerDelegate;
 import org.chromium.chrome.test.ChromeTabbedActivityTestRule;
 import org.chromium.chrome.test.R;
diff --git a/chrome/android/features/start_surface/javatests/src/org/chromium/chrome/features/start_surface/TabSwitcherAndStartSurfaceLayoutTest.java b/chrome/android/features/tab_ui/javatests/src/org/chromium/chrome/browser/tasks/tab_management/TabSwitcherAndStartSurfaceLayoutTest.java
similarity index 98%
rename from chrome/android/features/start_surface/javatests/src/org/chromium/chrome/features/start_surface/TabSwitcherAndStartSurfaceLayoutTest.java
rename to chrome/android/features/tab_ui/javatests/src/org/chromium/chrome/browser/tasks/tab_management/TabSwitcherAndStartSurfaceLayoutTest.java
index b7c3007..a919c4b2 100644
--- a/chrome/android/features/start_surface/javatests/src/org/chromium/chrome/features/start_surface/TabSwitcherAndStartSurfaceLayoutTest.java
+++ b/chrome/android/features/tab_ui/javatests/src/org/chromium/chrome/browser/tasks/tab_management/TabSwitcherAndStartSurfaceLayoutTest.java
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-package org.chromium.chrome.features.start_surface;
+package org.chromium.chrome.browser.tasks.tab_management;
 
 import static android.os.Build.VERSION_CODES.O_MR1;
 import static android.os.Build.VERSION_CODES.Q;
@@ -102,22 +102,12 @@
 import org.chromium.chrome.browser.tabmodel.TabCreator;
 import org.chromium.chrome.browser.tabmodel.TabModel;
 import org.chromium.chrome.browser.tasks.tab_groups.TabGroupModelFilter;
-import org.chromium.chrome.browser.tasks.tab_management.TabGridThumbnailView;
-import org.chromium.chrome.browser.tasks.tab_management.TabProperties;
-import org.chromium.chrome.browser.tasks.tab_management.TabSelectionEditorTestingRobot;
-import org.chromium.chrome.browser.tasks.tab_management.TabSuggestionMessageService;
-import org.chromium.chrome.browser.tasks.tab_management.TabSwitcher;
-import org.chromium.chrome.browser.tasks.tab_management.TabSwitcherCoordinator;
-import org.chromium.chrome.browser.tasks.tab_management.TabSwitcherLayout;
-import org.chromium.chrome.browser.tasks.tab_management.TabUiFeatureUtilities;
-import org.chromium.chrome.browser.tasks.tab_management.TabUiTestHelper;
-import org.chromium.chrome.browser.tasks.tab_management.TabUiThemeProvider;
-import org.chromium.chrome.browser.tasks.tab_management.UndoGroupSnackbarController;
 import org.chromium.chrome.browser.ui.messages.snackbar.SnackbarManager;
 import org.chromium.chrome.browser.undo_tab_close_snackbar.UndoBarController;
 import org.chromium.chrome.browser.util.ChromeAccessibilityUtil;
 import org.chromium.chrome.features.start_surface.StartSurfaceTestUtils.LegacyTestParams;
 import org.chromium.chrome.features.start_surface.StartSurfaceTestUtils.RefactorTestParams;
+import org.chromium.chrome.features.start_surface.TabSwitcherAndStartSurfaceLayout;
 import org.chromium.chrome.test.ChromeJUnit4RunnerDelegate;
 import org.chromium.chrome.test.ChromeTabbedActivityTestRule;
 import org.chromium.chrome.test.R;
@@ -428,7 +418,8 @@
     @CommandLineFlags.Add({BASE_PARAMS + "/soft-cleanup-delay/2000/cleanup-delay/10000"})
     @DisableIf.Build(message = "https://crbug.com/1365708", supported_abis_includes = "x86",
             sdk_is_greater_than = O_MR1, sdk_is_less_than = Q)
-    public void testTabToGridFromLiveTabWarm(boolean isStartSurfaceRefactorEnabled)
+    public void
+    testTabToGridFromLiveTabWarm(boolean isStartSurfaceRefactorEnabled)
             throws InterruptedException {
         assertFalse(
                 TabUiFeatureUtilities.isTabToGtsAnimationEnabled(mActivityTestRule.getActivity()));
diff --git a/chrome/android/features/tab_ui/tab_management_java_sources.gni b/chrome/android/features/tab_ui/tab_management_java_sources.gni
index a493cab..afbd60c9 100644
--- a/chrome/android/features/tab_ui/tab_management_java_sources.gni
+++ b/chrome/android/features/tab_ui/tab_management_java_sources.gni
@@ -127,6 +127,8 @@
   "//chrome/android/features/tab_ui/javatests/src/org/chromium/chrome/browser/tasks/tab_management/TabSelectionEditorTest.java",
   "//chrome/android/features/tab_ui/javatests/src/org/chromium/chrome/browser/tasks/tab_management/TabSelectionEditorTestingRobot.java",
   "//chrome/android/features/tab_ui/javatests/src/org/chromium/chrome/browser/tasks/tab_management/TabSuggestionMessageCardTest.java",
+  "//chrome/android/features/tab_ui/javatests/src/org/chromium/chrome/browser/tasks/tab_management/TabSwitcherAndStartSurfaceLayoutPerfTest.java",
+  "//chrome/android/features/tab_ui/javatests/src/org/chromium/chrome/browser/tasks/tab_management/TabSwitcherAndStartSurfaceLayoutTest.java",
   "//chrome/android/features/tab_ui/javatests/src/org/chromium/chrome/browser/tasks/tab_management/TabSwitcherMultiWindowTest.java",
   "//chrome/android/features/tab_ui/javatests/src/org/chromium/chrome/browser/tasks/tab_management/TabSwitcherTabletTest.java",
   "//chrome/android/features/tab_ui/javatests/src/org/chromium/chrome/browser/tasks/tab_management/TabSwitcherThumbnailTest.java",
diff --git a/chrome/android/java/res/layout/automotive_back_button_toolbar.xml b/chrome/android/java/res/layout/automotive_back_button_toolbar.xml
new file mode 100644
index 0000000..8f0f017
--- /dev/null
+++ b/chrome/android/java/res/layout/automotive_back_button_toolbar.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+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.
+-->
+<merge xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto">
+
+  <androidx.appcompat.widget.Toolbar
+      android:id="@+id/automotive_back_button_toolbar"
+      android:layout_width="match_parent"
+      android:layout_height="?attr/actionBarSize"
+      android:background="@android:color/black"
+      app:navigationIcon="@drawable/ic_arrow_back_white_24dp"
+      app:theme="@style/DarkModeRippleColor"
+      app:navigationContentDescription="@string/back"
+      android:visibility="gone"/>
+
+</merge>
diff --git a/chrome/android/java/res/layout/settings_activity.xml b/chrome/android/java/res/layout/settings_activity.xml
index 2f1ede8..ef8be6e 100644
--- a/chrome/android/java/res/layout/settings_activity.xml
+++ b/chrome/android/java/res/layout/settings_activity.xml
@@ -20,6 +20,8 @@
         android:theme="@style/ThemeOverlay.Settings.DisableElevationOverlay"
         app:liftOnScroll="true">
 
+        <include layout="@layout/automotive_back_button_toolbar"/>
+
         <com.google.android.material.appbar.MaterialToolbar
             android:id="@+id/action_bar"
             android:layout_width="match_parent"
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ChromeBaseAppCompatActivity.java b/chrome/android/java/src/org/chromium/chrome/browser/ChromeBaseAppCompatActivity.java
index 90f68e9..73670387 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/ChromeBaseAppCompatActivity.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/ChromeBaseAppCompatActivity.java
@@ -12,15 +12,20 @@
 import android.content.res.Configuration;
 import android.os.Build;
 import android.os.Bundle;
+import android.view.MenuItem;
+import android.view.View;
 
 import androidx.annotation.CallSuper;
+import androidx.annotation.LayoutRes;
 import androidx.annotation.Nullable;
 import androidx.annotation.RequiresApi;
 import androidx.annotation.StyleRes;
 import androidx.appcompat.app.AppCompatActivity;
+import androidx.appcompat.widget.Toolbar;
 
 import com.google.android.material.color.DynamicColors;
 
+import org.chromium.base.BuildInfo;
 import org.chromium.base.BundleUtils;
 import org.chromium.base.ContextUtils;
 import org.chromium.base.metrics.RecordHistogram;
@@ -43,6 +48,12 @@
 /**
  * A subclass of {@link AppCompatActivity} that maintains states and objects applied to all
  * activities in {@link ChromeApplication} (e.g. night mode).
+ *
+ * This class also adds a persistent back button toolbar for automotive by applying a ThemeOverlay.
+ * Activities that already use their own ActionBar must override
+ * {@link #shouldUseActionBarForAutomotiveToolbar} to false and add
+ * <include layout="@layout/automotive_back_button_toolbar"/>
+ * in their xml layout. See SettingsActivity and settings_activity.xml for an example.
  */
 public class ChromeBaseAppCompatActivity extends AppCompatActivity
         implements NightModeStateProvider.Observer, ModalDialogManagerHolder {
@@ -250,6 +261,10 @@
             UmaSessionStats.registerSyntheticFieldTrial(
                     "IsDynamicColorAvailable", isDynamicColorAvailable ? "Enabled" : "Disabled");
         });
+
+        if (BuildInfo.getInstance().isAutomotive && shouldUseActionBarForAutomotiveToolbar()) {
+            setTheme(R.style.ThemeOverlay_BrowserUI_Automotive_PersistentBackButtonToolbar);
+        }
     }
 
     /**
@@ -286,4 +301,53 @@
         }
         return service;
     }
+
+    /**
+     * Set the back button in the automotive toolbar to perform an Android system level back.
+     *
+     * This toolbar will be used to do things like exit fullscreen YouTube videos because AAOS/cars
+     * don't have a built in back button
+     */
+    @Override
+    public boolean onOptionsItemSelected(MenuItem item) {
+        if (item.getItemId() == android.R.id.home) {
+            getOnBackPressedDispatcher().onBackPressed();
+            return true;
+        }
+        return false;
+    }
+
+    @Override
+    public void setContentView(@LayoutRes int layoutResID) {
+        super.setContentView(layoutResID);
+
+        // Activities that use their own ActionBar can add the persistent back button toolbar for
+        // automotive using a Toolbar View.
+        if (BuildInfo.getInstance().isAutomotive && !shouldUseActionBarForAutomotiveToolbar()) {
+            Toolbar backButtonToolbarForAutomotive =
+                    findViewById(R.id.automotive_back_button_toolbar);
+            if (backButtonToolbarForAutomotive != null) {
+                backButtonToolbarForAutomotive.setVisibility(View.VISIBLE);
+                backButtonToolbarForAutomotive.setNavigationOnClickListener(
+                        view -> { getOnBackPressedDispatcher().onBackPressed(); });
+            }
+        }
+    }
+
+    @Override
+    protected void onResume() {
+        if (BuildInfo.getInstance().isAutomotive && shouldUseActionBarForAutomotiveToolbar()
+                && getSupportActionBar() != null) {
+            getSupportActionBar().setHomeActionContentDescription(R.string.back);
+        }
+        super.onResume();
+    }
+
+    /**
+     * @return true if the persistent back button toolbar for automotive will be added by default
+     * using AppCompatActivity's ActionBar, provided by a ThemeOverlay.
+     */
+    protected boolean shouldUseActionBarForAutomotiveToolbar() {
+        return true;
+    }
 }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ChromeTabbedActivity.java b/chrome/android/java/src/org/chromium/chrome/browser/ChromeTabbedActivity.java
index d174bc53..84f653f 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/ChromeTabbedActivity.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/ChromeTabbedActivity.java
@@ -576,6 +576,11 @@
             mHistoricalTabModelObserver =
                     new HistoricalTabModelObserver(mTabModelSelector.getModel(false));
 
+            // Defer initialization of this helper so it triggers after TabModelFilter
+            // observers.
+            UndoRefocusHelper.initialize(
+                    this, mTabModelSelector, getLayoutManagerSupplier(), isTablet());
+
             if (DeviceFormFactor.isNonMultiDisplayContextOnTablet(this)) {
                 mTabModelSelector.addObserver(new TabModelSelectorObserver() {
                     @Override
@@ -1852,8 +1857,6 @@
         mInactivityTracker = new ChromeInactivityTracker(
                 ChromePreferenceKeys.TABBED_ACTIVITY_LAST_BACKGROUNDED_TIME_MS_PREF);
         TabUsageTracker.initialize(this.getLifecycleDispatcher(), tabModelSelector);
-        UndoRefocusHelper.initialize(
-                this, tabModelSelector, getLayoutManagerSupplier(), isTablet());
 
         assert getActivityTabStartupMetricsTracker() != null;
         boolean shouldShowOverviewPageOnStart = shouldShowOverviewPageOnStart();
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/app/bookmarks/BookmarkAddEditFolderActivity.java b/chrome/android/java/src/org/chromium/chrome/browser/app/bookmarks/BookmarkAddEditFolderActivity.java
index 6c359a1..e679c70 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/app/bookmarks/BookmarkAddEditFolderActivity.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/app/bookmarks/BookmarkAddEditFolderActivity.java
@@ -262,6 +262,11 @@
         mModel.removeObserver(mBookmarkModelObserver);
     }
 
+    @Override
+    protected boolean shouldUseActionBarForAutomotiveToolbar() {
+        return false;
+    }
+
     private void updateParent(BookmarkId newParent) {
         mParentId = newParent;
         mParentTextView.setText(mModel.getBookmarkTitle(mParentId));
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/app/bookmarks/BookmarkEditActivity.java b/chrome/android/java/src/org/chromium/chrome/browser/app/bookmarks/BookmarkEditActivity.java
index ac73b15..57795b7 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/app/bookmarks/BookmarkEditActivity.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/app/bookmarks/BookmarkEditActivity.java
@@ -183,6 +183,11 @@
         super.onDestroy();
     }
 
+    @Override
+    protected boolean shouldUseActionBarForAutomotiveToolbar() {
+        return false;
+    }
+
     @VisibleForTesting
     BookmarkTextInputLayout getTitleEditText() {
         return mTitleEditText;
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/app/bookmarks/BookmarkFolderSelectActivity.java b/chrome/android/java/src/org/chromium/chrome/browser/app/bookmarks/BookmarkFolderSelectActivity.java
index 8f7aa7c..fc931fd 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/app/bookmarks/BookmarkFolderSelectActivity.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/app/bookmarks/BookmarkFolderSelectActivity.java
@@ -287,6 +287,11 @@
         }
     }
 
+    @Override
+    protected boolean shouldUseActionBarForAutomotiveToolbar() {
+        return false;
+    }
+
     private void moveBookmarksAndFinish(List<BookmarkId> bookmarks, BookmarkId parent) {
         List<BookmarkId> movedBookmarks = new ArrayList<>();
         ReadingListUtils.typeSwapBookmarksIfNecessary(
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/settings/SettingsActivity.java b/chrome/android/java/src/org/chromium/chrome/browser/settings/SettingsActivity.java
index ba88b55..958740e 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/settings/SettingsActivity.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/settings/SettingsActivity.java
@@ -571,4 +571,9 @@
 
         return divider;
     }
+
+    @Override
+    protected boolean shouldUseActionBarForAutomotiveToolbar() {
+        return false;
+    }
 }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/signin/SyncConsentActivity.java b/chrome/android/java/src/org/chromium/chrome/browser/signin/SyncConsentActivity.java
index 1a99fb7..9701eca 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/signin/SyncConsentActivity.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/signin/SyncConsentActivity.java
@@ -95,4 +95,9 @@
         }
         return mWindowAndroid;
     }
+
+    @Override
+    protected boolean shouldUseActionBarForAutomotiveToolbar() {
+        return false;
+    }
 }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/tabmodel/TabModelImpl.java b/chrome/android/java/src/org/chromium/chrome/browser/tabmodel/TabModelImpl.java
index e77c2201..3f4ea872 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/tabmodel/TabModelImpl.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/tabmodel/TabModelImpl.java
@@ -117,21 +117,31 @@
                             WebContents webContents = tab.getWebContents();
                             if (webContents != null) webContents.setAudioMuted(false);
 
-                            boolean activeModel = isActiveModel();
-
-                            if (mIndex == INVALID_TAB_INDEX) {
-                                // If we're the active model call setIndex to actually select this
-                                // tab, otherwise just set mIndex but don't kick off everything that
-                                // happens when calling setIndex().
-                                if (activeModel) {
-                                    TabModelUtils.setIndex(TabModelImpl.this, insertIndex, false,
-                                            TabSelectionType.FROM_UNDO);
-                                } else {
-                                    mIndex = insertIndex;
-                                }
+                            // Start by setting a valid index to the restored tab if not already
+                            // valid. This ensures getting the current index is valid for any
+                            // observers.
+                            boolean wasInvalidIndex = mIndex == INVALID_TAB_INDEX;
+                            if (wasInvalidIndex) {
+                                mIndex = insertIndex;
                             }
 
+                            // Alert observers the tab closure was undone before calling
+                            // setIndex if necessary as
+                            // * Observers may rely on this signal to re-introduce the tab to
+                            //   their visibility if it is selected before this it may not exist
+                            //   for those observers.
+                            // * UndoRefocusHelper may update the index out-of-band.
                             for (TabModelObserver obs : mObservers) obs.tabClosureUndone(tab);
+
+                            // If the mIndex we set earlier is still in use then trigger a proper
+                            // index update and notify any observers.
+                            if (wasInvalidIndex && isActiveModel() && mIndex == insertIndex) {
+                                // Reset the index first so the event is raised properly as a index
+                                // change and not re-using the current index.
+                                mIndex = INVALID_TAB_INDEX;
+                                TabModelUtils.setIndex(TabModelImpl.this, insertIndex, false,
+                                        TabSelectionType.FROM_UNDO);
+                            }
                         }
 
                         @Override
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/automotive/backbuttontoolbar/BackButtonToolbarTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/automotive/backbuttontoolbar/BackButtonToolbarTest.java
new file mode 100644
index 0000000..cbfdf82
--- /dev/null
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/automotive/backbuttontoolbar/BackButtonToolbarTest.java
@@ -0,0 +1,133 @@
+// 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.automotive.backbuttontoolbar;
+
+import static androidx.appcompat.app.ActionBar.DISPLAY_HOME_AS_UP;
+import static androidx.test.espresso.matcher.ViewMatchers.assertThat;
+
+import static org.hamcrest.Matchers.instanceOf;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import android.view.MenuItem;
+import android.view.View;
+
+import androidx.activity.OnBackPressedCallback;
+import androidx.appcompat.app.AppCompatActivity;
+import androidx.appcompat.widget.AppCompatImageButton;
+import androidx.appcompat.widget.Toolbar;
+import androidx.test.filters.SmallTest;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import org.chromium.base.test.util.CallbackHelper;
+import org.chromium.base.test.util.CommandLineFlags.Add;
+import org.chromium.base.test.util.DoNotBatch;
+import org.chromium.base.test.util.Feature;
+import org.chromium.base.test.util.Restriction;
+import org.chromium.chrome.browser.ChromeTabbedActivity;
+import org.chromium.chrome.browser.flags.ChromeSwitches;
+import org.chromium.chrome.browser.settings.MainSettings;
+import org.chromium.chrome.browser.settings.SettingsActivity;
+import org.chromium.chrome.browser.settings.SettingsActivityTestRule;
+import org.chromium.chrome.test.AutomotiveContextWrapperTestRule;
+import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
+import org.chromium.chrome.test.ChromeTabbedActivityTestRule;
+import org.chromium.chrome.test.R;
+import org.chromium.content_public.browser.test.util.TestThreadUtils;
+import org.chromium.ui.test.util.DeviceRestriction;
+
+/**
+ * Instrumentation tests for the persistent back button toolbar in automotive.
+ */
+@RunWith(ChromeJUnit4ClassRunner.class)
+@DoNotBatch(reason = "The two tests in this suite each launch different Activity's.")
+@Add({ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE})
+public class BackButtonToolbarTest {
+    @Rule
+    public final ChromeTabbedActivityTestRule mChromeTabbedActivityTestRule =
+            new ChromeTabbedActivityTestRule();
+
+    @Rule
+    public SettingsActivityTestRule<MainSettings> mSettingsActivityTestRule =
+            new SettingsActivityTestRule<>(MainSettings.class);
+
+    @Rule
+    public AutomotiveContextWrapperTestRule mAutomotiveContextWrapperTestRule =
+            new AutomotiveContextWrapperTestRule();
+
+    private CallbackHelper mBackPressCallbackHelper;
+
+    @Before
+    public void setUp() {
+        mAutomotiveContextWrapperTestRule.setIsAutomotive(true);
+        mBackPressCallbackHelper = new CallbackHelper();
+    }
+
+    @Test
+    @SmallTest
+    @Restriction(DeviceRestriction.RESTRICTION_TYPE_AUTO)
+    @Feature({"Automotive Toolbar"})
+    public void testAutomotiveToolbar_ActionBar2() throws Exception {
+        mChromeTabbedActivityTestRule.startMainActivityOnBlankPage();
+        ChromeTabbedActivity chromeTabbedActivity = mChromeTabbedActivityTestRule.getActivity();
+
+        // Check that the automotive toolbar is present with only a back button.
+        assertTrue(chromeTabbedActivity.getSupportActionBar().isShowing());
+        assertEquals("Automotive toolbar should only contain a back button",
+                chromeTabbedActivity.getSupportActionBar().getDisplayOptions(), DISPLAY_HOME_AS_UP);
+
+        // Simulate a back button press on the automotive toolbar.
+        addBackPressedCallback(chromeTabbedActivity, mBackPressCallbackHelper);
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            MenuItem backButton = mock(MenuItem.class);
+            when(backButton.getItemId()).thenReturn(android.R.id.home);
+            chromeTabbedActivity.onOptionsItemSelected(backButton);
+        });
+
+        // Verify that #onBackPressed was called.
+        mBackPressCallbackHelper.waitForFirst();
+    }
+
+    @Test
+    @SmallTest
+    @Restriction(DeviceRestriction.RESTRICTION_TYPE_AUTO)
+    @Feature({"Automotive Toolbar"})
+    public void testAutomotiveToolbar_ToolbarView() throws Exception {
+        // Launch Settings Activity, which uses a Toolbar View to implement the automotive toolbar.
+        mSettingsActivityTestRule.startSettingsActivity();
+        SettingsActivity settingsActivity = mSettingsActivityTestRule.getActivity();
+
+        // Check that the automotive toolbar is present with only a back button.
+        Toolbar toolbar = settingsActivity.findViewById(R.id.automotive_back_button_toolbar);
+        assertNotNull(toolbar);
+        assertEquals("Toolbar not visible", toolbar.getVisibility(), View.VISIBLE);
+        assertEquals("Toolbar should only contain a back button", toolbar.getChildCount(), 1);
+        assertThat(toolbar.getChildAt(0), instanceOf(AppCompatImageButton.class));
+
+        // Click the back button in the automotive toolbar.
+        addBackPressedCallback(settingsActivity, mBackPressCallbackHelper);
+        TestThreadUtils.runOnUiThreadBlocking(() -> { toolbar.getChildAt(0).performClick(); });
+
+        // Verify that #onBackPressed was called.
+        mBackPressCallbackHelper.waitForFirst();
+    }
+
+    private void addBackPressedCallback(
+            AppCompatActivity activity, CallbackHelper backPressCallback) {
+        activity.getOnBackPressedDispatcher().addCallback(new OnBackPressedCallback(true) {
+            @Override
+            public void handleOnBackPressed() {
+                backPressCallback.notifyCalled();
+            }
+        });
+    }
+}
diff --git a/chrome/app/chromeos_strings.grdp b/chrome/app/chromeos_strings.grdp
index df79435..84741367 100644
--- a/chrome/app/chromeos_strings.grdp
+++ b/chrome/app/chromeos_strings.grdp
@@ -6703,4 +6703,13 @@
     Your administrator has logged in to look into an issue. You can continue to use the device after the administrator gives the control back to you.
   </message>
 
+  <!-- Gaia Info -->
+  <message name="IDS_GAIA_INFO_TITLE" desc="Title of the Gaia Info screen.">
+    Use your Google Account on your <ph name="DEVICE_TYPE">$1<ex>Chromebook</ex></ph>
+  </message>
+  <message name="IDS_GAIA_INFO_DESCRIPTION" desc="Description of the Gaia Info screen.">
+    <ph name="BEGIN_PARAGRAPH1">&lt;p&gt;</ph>On the next screen, sign in with your Google Account.<ph name="END_PARAGRAPH1">&lt;/p&gt;</ph>
+    <ph name="BEGIN_PARAGRAPH2">&lt;p&gt;</ph>This is the same account you use for Gmail, YouTube, Chrome, and other Google services. Use your account for a personalized experience and easy access to all of your information.<ph name="END_PARAGRAPH2">&lt;/p&gt;</ph>
+    <ph name="BEGIN_PARAGRAPH3">&lt;p&gt;</ph>If you don't have a Google Account, you'll be able to create one on the next screen.<ph name="END_PARAGRAPH3">&lt;/p&gt;</ph>
+  </message>
 </grit-part>
diff --git a/chrome/app/chromeos_strings_grdp/IDS_GAIA_INFO_DESCRIPTION.png.sha1 b/chrome/app/chromeos_strings_grdp/IDS_GAIA_INFO_DESCRIPTION.png.sha1
new file mode 100644
index 0000000..7cbc60c
--- /dev/null
+++ b/chrome/app/chromeos_strings_grdp/IDS_GAIA_INFO_DESCRIPTION.png.sha1
@@ -0,0 +1 @@
+c04277da80bef6c5e86dfb10142edd163e9b1f42
\ No newline at end of file
diff --git a/chrome/app/chromeos_strings_grdp/IDS_GAIA_INFO_TITLE.png.sha1 b/chrome/app/chromeos_strings_grdp/IDS_GAIA_INFO_TITLE.png.sha1
new file mode 100644
index 0000000..7cbc60c
--- /dev/null
+++ b/chrome/app/chromeos_strings_grdp/IDS_GAIA_INFO_TITLE.png.sha1
@@ -0,0 +1 @@
+c04277da80bef6c5e86dfb10142edd163e9b1f42
\ No newline at end of file
diff --git a/chrome/app/generated_resources.grd b/chrome/app/generated_resources.grd
index b87b68f..752c668 100644
--- a/chrome/app/generated_resources.grd
+++ b/chrome/app/generated_resources.grd
@@ -7059,7 +7059,7 @@
         Hide <ph name="MODULE_TITLE">$1<ex>Office Chairs</ex></ph>
       </message>
       <message name="IDS_NTP_MODULES_DISABLE_BUTTON_TEXT" desc="Text shown on the dismiss button of an NTP module.">
-        Don't show <ph name="MODULE_NAME">$1<ex>shopping suggestions</ex></ph>
+        Never show <ph name="MODULE_NAME">$1<ex>shopping suggestions</ex></ph>
       </message>
       <message name="IDS_NTP_MODULES_CUSTOMIZE_BUTTON_TEXT" desc="Text shown on the customize button of an NTP module.">
         Customize cards
diff --git a/chrome/app/generated_resources_grd/IDS_NTP_MODULES_DISABLE_BUTTON_TEXT.png.sha1 b/chrome/app/generated_resources_grd/IDS_NTP_MODULES_DISABLE_BUTTON_TEXT.png.sha1
index dbb7b1ee..5ce5072 100644
--- a/chrome/app/generated_resources_grd/IDS_NTP_MODULES_DISABLE_BUTTON_TEXT.png.sha1
+++ b/chrome/app/generated_resources_grd/IDS_NTP_MODULES_DISABLE_BUTTON_TEXT.png.sha1
@@ -1 +1 @@
-d85c5384bb9f50172571b0a0dabc6f0118ac2449
\ No newline at end of file
+ff6fea27271cbb3e8bb7d57eb4f6a61aa2ad13d7
\ No newline at end of file
diff --git a/chrome/browser/ash/BUILD.gn b/chrome/browser/ash/BUILD.gn
index 56ad90c2..3203dd4 100644
--- a/chrome/browser/ash/BUILD.gn
+++ b/chrome/browser/ash/BUILD.gn
@@ -1758,6 +1758,8 @@
     "login/screens/family_link_notice_screen.h",
     "login/screens/fingerprint_setup_screen.cc",
     "login/screens/fingerprint_setup_screen.h",
+    "login/screens/gaia_info_screen.cc",
+    "login/screens/gaia_info_screen.h",
     "login/screens/gaia_password_changed_screen.cc",
     "login/screens/gaia_password_changed_screen.h",
     "login/screens/gaia_password_changed_screen_legacy.cc",
@@ -2530,6 +2532,8 @@
     "policy/reporting/metrics_reporting/device_activity/device_activity_sampler.h",
     "policy/reporting/metrics_reporting/metric_reporting_manager.cc",
     "policy/reporting/metrics_reporting/metric_reporting_manager.h",
+    "policy/reporting/metrics_reporting/metric_reporting_prefs.cc",
+    "policy/reporting/metrics_reporting/metric_reporting_prefs.h",
     "policy/reporting/metrics_reporting/mojo_service_events_observer_base.h",
     "policy/reporting/metrics_reporting/network/fake_network_diagnostics_util.cc",
     "policy/reporting/metrics_reporting/network/fake_network_diagnostics_util.h",
diff --git a/chrome/browser/ash/crostini/crostini_export_import_notification_controller.cc b/chrome/browser/ash/crostini/crostini_export_import_notification_controller.cc
index 86d9e9f8..b4103c1 100644
--- a/chrome/browser/ash/crostini/crostini_export_import_notification_controller.cc
+++ b/chrome/browser/ash/crostini/crostini_export_import_notification_controller.cc
@@ -64,7 +64,7 @@
   rich_notification_data.vector_small_image = &ash::kNotificationLinuxIcon;
 
   if (chromeos::features::IsJellyEnabled()) {
-    rich_notification_data.accent_color_id = cros_tokens::kCrosSysOnPrimary;
+    rich_notification_data.accent_color_id = cros_tokens::kCrosSysPrimary;
   } else {
     rich_notification_data.accent_color = ash::kSystemNotificationColorNormal;
   }
@@ -114,7 +114,7 @@
 
   notification_->set_type(message_center::NOTIFICATION_TYPE_PROGRESS);
   if (chromeos::features::IsJellyEnabled()) {
-    notification_->set_accent_color_id(cros_tokens::kCrosSysOnPrimary);
+    notification_->set_accent_color_id(cros_tokens::kCrosSysPrimary);
   } else {
     notification_->set_accent_color(ash::kSystemNotificationColorNormal);
   }
@@ -143,7 +143,7 @@
 
   notification_->set_type(message_center::NOTIFICATION_TYPE_PROGRESS);
   if (chromeos::features::IsJellyEnabled()) {
-    notification_->set_accent_color_id(cros_tokens::kCrosSysOnPrimary);
+    notification_->set_accent_color_id(cros_tokens::kCrosSysPrimary);
   } else {
     notification_->set_accent_color(ash::kSystemNotificationColorNormal);
   }
@@ -174,7 +174,7 @@
 
   notification_->set_type(message_center::NOTIFICATION_TYPE_SIMPLE);
   if (chromeos::features::IsJellyEnabled()) {
-    notification_->set_accent_color_id(cros_tokens::kCrosSysOnPrimary);
+    notification_->set_accent_color_id(cros_tokens::kCrosSysPrimary);
   } else {
     notification_->set_accent_color(ash::kSystemNotificationColorNormal);
   }
diff --git a/chrome/browser/ash/crostini/crostini_package_notification.cc b/chrome/browser/ash/crostini/crostini_package_notification.cc
index 44385e4..34eb78a8 100644
--- a/chrome/browser/ash/crostini/crostini_package_notification.cc
+++ b/chrome/browser/ash/crostini/crostini_package_notification.cc
@@ -72,7 +72,7 @@
   rich_notification_data.vector_small_image = &ash::kNotificationLinuxIcon;
   rich_notification_data.never_timeout = true;
   if (chromeos::features::IsJellyEnabled()) {
-    rich_notification_data.accent_color_id = cros_tokens::kCrosSysOnPrimary;
+    rich_notification_data.accent_color_id = cros_tokens::kCrosSysPrimary;
   } else {
     rich_notification_data.accent_color = ash::kSystemNotificationColorNormal;
   }
diff --git a/chrome/browser/ash/crostini/crostini_upgrade_available_notification.cc b/chrome/browser/ash/crostini/crostini_upgrade_available_notification.cc
index cc4bbe5..aa8e5421 100644
--- a/chrome/browser/ash/crostini/crostini_upgrade_available_notification.cc
+++ b/chrome/browser/ash/crostini/crostini_upgrade_available_notification.cc
@@ -110,7 +110,7 @@
   rich_notification_data.small_image = gfx::Image(gfx::CreateVectorIcon(
       vector_icons::kFileDownloadIcon, 64, gfx::kGoogleBlue800));
   if (chromeos::features::IsJellyEnabled()) {
-    rich_notification_data.accent_color_id = cros_tokens::kCrosSysOnPrimary;
+    rich_notification_data.accent_color_id = cros_tokens::kCrosSysPrimary;
   } else {
     rich_notification_data.accent_color = ash::kSystemNotificationColorNormal;
   }
diff --git a/chrome/browser/ash/events/event_rewriter_delegate_impl.cc b/chrome/browser/ash/events/event_rewriter_delegate_impl.cc
index eca55c96..5393914 100644
--- a/chrome/browser/ash/events/event_rewriter_delegate_impl.cc
+++ b/chrome/browser/ash/events/event_rewriter_delegate_impl.cc
@@ -9,6 +9,7 @@
 #include "ash/public/cpp/input_device_settings_controller.h"
 #include "ash/public/cpp/window_properties.h"
 #include "ash/public/mojom/input_device_settings.mojom.h"
+#include "base/containers/fixed_flat_map.h"
 #include "chrome/browser/ash/login/ui/login_display_host.h"
 #include "chrome/browser/ash/notifications/deprecation_notification_controller.h"
 #include "chrome/browser/extensions/extension_commands_global_registry.h"
@@ -170,6 +171,46 @@
   suppress_meta_top_row_key_rewrites_ = should_suppress;
 }
 
+void EventRewriterDelegateImpl::RecordEventRemappedToRightClick() {
+  PrefService* const pref_service = GetPrefService();
+  if (!pref_service) {
+    return;
+  }
+  pref_service->SetBoolean(prefs::kEventRemappedToRightClick, true);
+}
+
+void EventRewriterDelegateImpl::RecordSixPackEventRewrite(
+    ui::KeyboardCode key_code,
+    bool alt_based) {
+  PrefService* const pref_service = GetPrefService();
+  if (!pref_service) {
+    return;
+  }
+  // A map between "six pack" keys to prefs which track how often a user uses
+  // either the alt or search based shortcut variant to emit a "six pack" event.
+  // The "Insert" key is omitted since the (Search+Shift+Backspace) rewrite is
+  // the only way to emit an "Insert" key event.
+  static constexpr auto kSixPackKeyToPrefMap =
+      base::MakeFixedFlatMap<ui::KeyboardCode, const char*>({
+          {ui::KeyboardCode::VKEY_DELETE,
+           prefs::kKeyEventRemappedToSixPackDelete},
+          {ui::KeyboardCode::VKEY_HOME, prefs::kKeyEventRemappedToSixPackHome},
+          {ui::KeyboardCode::VKEY_PRIOR,
+           prefs::kKeyEventRemappedToSixPackPageDown},
+          {ui::KeyboardCode::VKEY_END, prefs::kKeyEventRemappedToSixPackEnd},
+          {ui::KeyboardCode::VKEY_NEXT,
+           prefs::kKeyEventRemappedToSixPackPageUp},
+      });
+  auto* it = kSixPackKeyToPrefMap.find(key_code);
+  CHECK(it != kSixPackKeyToPrefMap.end());
+  int count = pref_service->GetInteger(it->second);
+  // `alt_based` tells us whether this "six pack" event was produced by an
+  // Alt or Search/Launcher based keyboard shortcut. Update our pref to track
+  // which method the user uses more frequently.
+  count += alt_based ? 1 : -1;
+  pref_service->SetInteger(it->second, count);
+}
+
 bool EventRewriterDelegateImpl::NotifyDeprecatedRightClickRewrite() {
   return deprecation_controller_->NotifyDeprecatedRightClickRewrite();
 }
@@ -179,7 +220,7 @@
   return deprecation_controller_->NotifyDeprecatedSixPackKeyRewrite(key_code);
 }
 
-const PrefService* EventRewriterDelegateImpl::GetPrefService() const {
+PrefService* EventRewriterDelegateImpl::GetPrefService() const {
   if (pref_service_for_testing_)
     return pref_service_for_testing_;
   Profile* profile = ProfileManager::GetActiveUserProfile();
diff --git a/chrome/browser/ash/events/event_rewriter_delegate_impl.h b/chrome/browser/ash/events/event_rewriter_delegate_impl.h
index 0d59714..9d15e7f 100644
--- a/chrome/browser/ash/events/event_rewriter_delegate_impl.h
+++ b/chrome/browser/ash/events/event_rewriter_delegate_impl.h
@@ -30,7 +30,7 @@
 
   ~EventRewriterDelegateImpl() override;
 
-  void set_pref_service_for_testing(const PrefService* pref_service) {
+  void set_pref_service_for_testing(PrefService* pref_service) {
     pref_service_for_testing_ = pref_service;
   }
 
@@ -49,11 +49,14 @@
   bool NotifyDeprecatedSixPackKeyRewrite(ui::KeyboardCode key_code) override;
   void SuppressModifierKeyRewrites(bool should_suppress) override;
   void SuppressMetaTopRowKeyComboRewrites(bool should_suppress) override;
+  void RecordEventRemappedToRightClick() override;
+  void RecordSixPackEventRewrite(ui::KeyboardCode key_code,
+                                 bool alt_based) override;
 
  private:
-  const PrefService* GetPrefService() const;
+  PrefService* GetPrefService() const;
 
-  raw_ptr<const PrefService, ExperimentalAsh> pref_service_for_testing_;
+  raw_ptr<PrefService, ExperimentalAsh> pref_service_for_testing_;
 
   raw_ptr<wm::ActivationClient, DanglingUntriaged | ExperimentalAsh>
       activation_client_;
diff --git a/chrome/browser/ash/events/event_rewriter_unittest.cc b/chrome/browser/ash/events/event_rewriter_unittest.cc
index 4344681..8724315 100644
--- a/chrome/browser/ash/events/event_rewriter_unittest.cc
+++ b/chrome/browser/ash/events/event_rewriter_unittest.cc
@@ -3188,6 +3188,15 @@
          ui::DomKey::F12}}});
 }
 
+TEST_F(EventRewriterTest, RecordEventRemappedToRightClick) {
+  Preferences::RegisterProfilePrefs(prefs()->registry());
+  BooleanPrefMember remap_to_right_click;
+  remap_to_right_click.Init(prefs::kEventRemappedToRightClick, prefs());
+  remap_to_right_click.SetValue(false);
+  delegate_->RecordEventRemappedToRightClick();
+  EXPECT_EQ(true, prefs()->GetBoolean(prefs::kEventRemappedToRightClick));
+}
+
 TEST_F(
     EventRewriterTest,
     TestFunctionKeysLayout2SuppressMetaTopRowKeyRewritesWithTreatTopRowAsFKeys) {
@@ -4584,6 +4593,7 @@
 // Alt+Click or Search+Click. After a transition period this will
 // default to Search+Click and the Alt+Click logic will be removed.
 void EventRewriterTest::DontRewriteIfNotRewritten(int right_click_flags) {
+  Preferences::RegisterProfilePrefs(prefs()->registry());
   ui::DeviceDataManager* device_data_manager =
       ui::DeviceDataManager::GetInstance();
   std::vector<ui::TouchpadDevice> touchpad_devices(2);
@@ -5400,6 +5410,9 @@
   }
   void SuppressModifierKeyRewrites(bool should_suppress) override {}
   void SuppressMetaTopRowKeyComboRewrites(bool should_suppress) override {}
+  void RecordEventRemappedToRightClick() override {}
+  void RecordSixPackEventRewrite(ui::KeyboardCode key_code,
+                                 bool alt_based) override {}
 
   std::map<std::string, ui::mojom::ModifierKey> modifier_remapping_;
   base::flat_set<ui::Accelerator> registered_extension_shortcuts_;
@@ -5894,4 +5907,42 @@
         {ui::VKEY_A, ui::DomCode::US_A, ui::EF_CONTROL_DOWN,
          ui::DomKey::Constant<'a'>::Character}}});
 }
+
+class KeyEventRemappedToSixPackKeyTest
+    : public EventRewriterTest,
+      public testing::WithParamInterface<
+          std::tuple<ui::KeyboardCode, bool, int, const char*>> {
+ public:
+  void SetUp() override {
+    EventRewriterTest::SetUp();
+    std::tie(key_code_, alt_based_, expected_pref_value_, pref_name_) =
+        GetParam();
+  }
+
+ protected:
+  ui::KeyboardCode key_code_;
+  bool alt_based_;
+  int expected_pref_value_;
+  const char* pref_name_;
+};
+
+INSTANTIATE_TEST_SUITE_P(
+    All,
+    KeyEventRemappedToSixPackKeyTest,
+    testing::ValuesIn(std::vector<
+                      std::tuple<ui::KeyboardCode, bool, int, const char*>>{
+        {ui::VKEY_DELETE, false, -1, prefs::kKeyEventRemappedToSixPackDelete},
+        {ui::VKEY_HOME, true, 1, prefs::kKeyEventRemappedToSixPackHome},
+        {ui::VKEY_PRIOR, false, -1, prefs::kKeyEventRemappedToSixPackPageDown},
+        {ui::VKEY_END, true, 1, prefs::kKeyEventRemappedToSixPackEnd},
+        {ui::VKEY_NEXT, false, -1, prefs::kKeyEventRemappedToSixPackPageUp}}));
+
+TEST_P(KeyEventRemappedToSixPackKeyTest, KeyEventRemappedTest) {
+  Preferences::RegisterProfilePrefs(prefs()->registry());
+  IntegerPrefMember int_pref;
+  int_pref.Init(pref_name_, prefs());
+  int_pref.SetValue(0);
+  delegate_->RecordSixPackEventRewrite(key_code_, alt_based_);
+  EXPECT_EQ(expected_pref_value_, prefs()->GetInteger(pref_name_));
+}
 }  // namespace ash
diff --git a/chrome/browser/ash/login/debug_overlay_browsertest.cc b/chrome/browser/ash/login/debug_overlay_browsertest.cc
index 4135237..9d8d7272 100644
--- a/chrome/browser/ash/login/debug_overlay_browsertest.cc
+++ b/chrome/browser/ash/login/debug_overlay_browsertest.cc
@@ -21,8 +21,8 @@
 constexpr char kDebugOverlay[] = "debuggerOverlay";
 constexpr char kScreensPanel[] = "DebuggerPanelScreens";
 
-constexpr int kOobeScreensCount = 46;
-constexpr int kLoginScreensCount = 44;
+constexpr int kOobeScreensCount = 47;
+constexpr int kLoginScreensCount = 45;
 constexpr int kOsInstallScreensCount = 2;
 
 std::string ElementsInPanel(const std::string& panel) {
@@ -35,7 +35,9 @@
  public:
   DebugOverlayTest() {
     feature_list_.InitWithFeatures(
-        {ash::features::kOobeChoobe, ash::features::kOobeTouchpadScroll}, {});
+        {ash::features::kOobeChoobe, ash::features::kOobeTouchpadScroll,
+         features::kOobeGaiaInfoScreen},
+        {});
   }
 
   ~DebugOverlayTest() override = default;
diff --git a/chrome/browser/ash/login/oobe_quick_start/connectivity/connection.cc b/chrome/browser/ash/login/oobe_quick_start/connectivity/connection.cc
index 777c9e24..49e5570 100644
--- a/chrome/browser/ash/login/oobe_quick_start/connectivity/connection.cc
+++ b/chrome/browser/ash/login/oobe_quick_start/connectivity/connection.cc
@@ -18,6 +18,7 @@
 #include "chromeos/ash/components/quick_start/quick_start_message.h"
 #include "chromeos/ash/components/quick_start/quick_start_requests.h"
 #include "chromeos/ash/services/nearby/public/mojom/quick_start_decoder.mojom.h"
+#include "chromeos/ash/services/nearby/public/mojom/quick_start_decoder_types.mojom-forward.h"
 #include "chromeos/ash/services/nearby/public/mojom/quick_start_decoder_types.mojom-shared.h"
 #include "chromeos/ash/services/nearby/public/mojom/quick_start_decoder_types.mojom.h"
 #include "crypto/random.h"
@@ -101,11 +102,14 @@
   // Build the Wifi Credential Request payload
   std::string shared_secret_str(secondary_shared_secret_.begin(),
                                 secondary_shared_secret_.end());
-  SendMessage(
-      requests::BuildRequestWifiCredentialsMessage(session_id,
-                                                   shared_secret_str),
-      base::BindOnce(&Connection::OnRequestWifiCredentialsResponse,
-                     weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
+  ConnectionResponseCallback on_response_received =
+      base::BindOnce(&Connection::DecodeData<mojom::WifiCredentials>,
+                     weak_ptr_factory_.GetWeakPtr(),
+                     &mojom::QuickStartDecoder::DecodeWifiCredentialsResponse,
+                     std::move(callback));
+  SendMessage(requests::BuildRequestWifiCredentialsMessage(session_id,
+                                                           shared_secret_str),
+              std::move(on_response_received));
 }
 
 void Connection::NotifySourceOfUpdate(int32_t session_id,
@@ -146,34 +150,6 @@
   SendMessage(requests::BuildBootstrapOptionsRequest(), std::move(get_info));
 }
 
-void Connection::OnRequestWifiCredentialsResponse(
-    RequestWifiCredentialsCallback callback,
-    absl::optional<std::vector<uint8_t>> response_bytes) {
-  if (!response_bytes.has_value()) {
-    QS_LOG(ERROR) << "No response bytes received for wifi credentials request";
-    std::move(callback).Run(absl::nullopt);
-    return;
-  }
-
-  auto parse_mojo_response_callback =
-      base::BindOnce(&Connection::ParseWifiCredentialsResponse,
-                     weak_ptr_factory_.GetWeakPtr(), std::move(callback));
-
-  decoder_->DecodeWifiCredentialsResponse(
-      response_bytes.value(), std::move(parse_mojo_response_callback));
-}
-
-void Connection::ParseWifiCredentialsResponse(
-    RequestWifiCredentialsCallback callback,
-    ::ash::quick_start::mojom::GetWifiCredentialsResponsePtr response) {
-  if (!response->is_credentials()) {
-    std::move(callback).Run(absl::nullopt);
-    return;
-  }
-
-  std::move(callback).Run(response->get_credentials()->Clone());
-}
-
 void Connection::OnNotifySourceOfUpdateResponse(
     NotifySourceOfUpdateCallback callback,
     absl::optional<std::vector<uint8_t>> response_bytes) {
@@ -263,6 +239,40 @@
   // TODO(b/234655072): Read response from phone and run callback.
 }
 
+template <typename T>
+void Connection::DecodeData(DecoderMethod<T> decoder_method,
+                            OnDecodingCompleteCallback<T> on_decoding_complete,
+                            absl::optional<std::vector<uint8_t>> data) {
+  if (!data.has_value()) {
+    QS_LOG(ERROR) << "No data received from phone.";
+    std::move(on_decoding_complete).Run(absl::nullopt);
+    return;
+  }
+
+  // Setup a callback to handle the decoder's response. If an error was
+  // reported, return empty. If not, run the success callback with the
+  // decoded data.
+  DecoderResponseCallback<T> decoder_callback = base::BindOnce(
+      [](OnDecodingCompleteCallback<T> on_decoding_complete,
+         mojo::InlinedStructPtr<T> data,
+         absl::optional<mojom::QuickStartDecoderError> error) {
+        if (error.has_value()) {
+          // TODO(b/281052191): Log error code here
+          QS_LOG(ERROR) << "Error decoding data.";
+          std::move(on_decoding_complete).Run(absl::nullopt);
+          return;
+        }
+
+        std::move(on_decoding_complete).Run(*std::move(data));
+      },
+      std::move(on_decoding_complete));
+
+  // Run the decoder
+  base::BindOnce(decoder_method, decoder_, data.value(),
+                 std::move(decoder_callback))
+      .Run();
+}
+
 void Connection::SendPayload(const base::Value::Dict& message_payload) {
   std::string json_serialized_payload;
   CHECK(base::JSONWriter::Write(message_payload, &json_serialized_payload));
diff --git a/chrome/browser/ash/login/oobe_quick_start/connectivity/connection.h b/chrome/browser/ash/login/oobe_quick_start/connectivity/connection.h
index 0a08dcf..b26f31d1 100644
--- a/chrome/browser/ash/login/oobe_quick_start/connectivity/connection.h
+++ b/chrome/browser/ash/login/oobe_quick_start/connectivity/connection.h
@@ -17,8 +17,10 @@
 #include "chrome/browser/ash/login/oobe_quick_start/connectivity/target_device_connection_broker.h"
 #include "chrome/browser/nearby_sharing/public/cpp/nearby_connection.h"
 #include "chromeos/ash/services/nearby/public/mojom/quick_start_decoder.mojom.h"
+#include "chromeos/ash/services/nearby/public/mojom/quick_start_decoder_types.mojom-shared.h"
 #include "components/cbor/values.h"
 #include "mojo/public/cpp/bindings/shared_remote.h"
+#include "mojo/public/cpp/bindings/struct_ptr.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
 #include "url/origin.h"
 
@@ -127,15 +129,6 @@
       const std::string& challenge_b64url,
       RequestAccountTransferAssertionCallback callback) override;
 
-  // Parses a raw response and converts it to a WifiCredentialsResponse
-  void OnRequestWifiCredentialsResponse(
-      RequestWifiCredentialsCallback callback,
-      absl::optional<std::vector<uint8_t>> response_bytes);
-
-  void ParseWifiCredentialsResponse(
-      RequestWifiCredentialsCallback callback,
-      ::ash::quick_start::mojom::GetWifiCredentialsResponsePtr response);
-
   void OnNotifySourceOfUpdateResponse(
       NotifySourceOfUpdateCallback callback,
       absl::optional<std::vector<uint8_t>> response_bytes);
@@ -162,6 +155,28 @@
   void OnConnectionClosed(
       TargetDeviceConnectionBroker::ConnectionClosedReason reason);
 
+  template <typename T>
+  using DecoderResponseCallback =
+      base::OnceCallback<void(mojo::InlinedStructPtr<T>,
+                              absl::optional<mojom::QuickStartDecoderError>)>;
+
+  template <typename T>
+  using DecoderMethod =
+      void (mojom::QuickStartDecoder::*)(const std::vector<uint8_t>&,
+                                         DecoderResponseCallback<T>);
+
+  template <typename T>
+  using OnDecodingCompleteCallback =
+      base::OnceCallback<void(absl::optional<T>)>;
+
+  // Generic method to decode data using QuickStartDecoder. If a decoding error
+  // occurs, return empty data. On success, on_success will be called
+  // with the decoded data.
+  template <typename T>
+  void DecodeData(DecoderMethod<T> decoder_method,
+                  OnDecodingCompleteCallback<T> on_decoding_complete,
+                  absl::optional<std::vector<uint8_t>> data);
+
   raw_ptr<NearbyConnection, ExperimentalAsh> nearby_connection_;
   RandomSessionId random_session_id_;
   SharedSecret shared_secret_;
diff --git a/chrome/browser/ash/login/oobe_quick_start/connectivity/connection_unittest.cc b/chrome/browser/ash/login/oobe_quick_start/connectivity/connection_unittest.cc
index d779a721..23fca49 100644
--- a/chrome/browser/ash/login/oobe_quick_start/connectivity/connection_unittest.cc
+++ b/chrome/browser/ash/login/oobe_quick_start/connectivity/connection_unittest.cc
@@ -156,11 +156,11 @@
   int32_t session_id = 1;
 
   fake_quick_start_decoder_->SetWifiCredentialsResponse(
-      mojom::GetWifiCredentialsResponse::NewCredentials(
-          mojom::WifiCredentials::New("ssid", mojom::WifiSecurityType::kPSK,
-                                      true, "password")));
+      mojom::WifiCredentials::New("ssid", mojom::WifiSecurityType::kPSK, true,
+                                  "password"),
+      absl::nullopt);
 
-  base::test::TestFuture<absl::optional<mojom::WifiCredentialsPtr>> future;
+  base::test::TestFuture<absl::optional<mojom::WifiCredentials>> future;
 
   authenticated_connection_->RequestWifiCredentials(session_id,
                                                     future.GetCallback());
@@ -203,13 +203,13 @@
   EXPECT_EQ(*wifi_request_payload.FindString("shared_secret"),
             shared_secret_base64);
 
-  const absl::optional<mojom::WifiCredentialsPtr>& credentials = future.Get();
+  const absl::optional<mojom::WifiCredentials>& credentials = future.Get();
   EXPECT_TRUE(credentials.has_value());
-  EXPECT_EQ(credentials.value()->ssid, "ssid");
-  EXPECT_EQ(credentials.value()->password, "password");
-  EXPECT_EQ(credentials.value()->security_type,
+  EXPECT_EQ(credentials.value().ssid, "ssid");
+  EXPECT_EQ(credentials.value().password, "password");
+  EXPECT_EQ(credentials.value().security_type,
             ash::quick_start::mojom::WifiSecurityType::kPSK);
-  EXPECT_TRUE(credentials.value()->is_hidden);
+  EXPECT_TRUE(credentials.value().is_hidden);
 }
 
 TEST_F(ConnectionTest, RequestWifiCredentialsReturnsEmptyOnFailure) {
@@ -217,10 +217,9 @@
   // Random Session ID for testing
   int32_t session_id = 1;
   fake_quick_start_decoder_->SetWifiCredentialsResponse(
-      mojom::GetWifiCredentialsResponse::NewFailureReason(
-          mojom::GetWifiCredentialsFailureReason::kMissingWifiHiddenStatus));
+      nullptr, mojom::QuickStartDecoderError::kMessageDoesNotMatchSchema);
 
-  base::test::TestFuture<absl::optional<mojom::WifiCredentialsPtr>> future;
+  base::test::TestFuture<absl::optional<mojom::WifiCredentials>> future;
 
   authenticated_connection_->RequestWifiCredentials(session_id,
                                                     future.GetCallback());
diff --git a/chrome/browser/ash/login/oobe_quick_start/connectivity/target_device_connection_broker.h b/chrome/browser/ash/login/oobe_quick_start/connectivity/target_device_connection_broker.h
index 3e40346..cc45fdd 100644
--- a/chrome/browser/ash/login/oobe_quick_start/connectivity/target_device_connection_broker.h
+++ b/chrome/browser/ash/login/oobe_quick_start/connectivity/target_device_connection_broker.h
@@ -50,7 +50,7 @@
   class AuthenticatedConnection {
    public:
     using RequestWifiCredentialsCallback =
-        base::OnceCallback<void(absl::optional<mojom::WifiCredentialsPtr>)>;
+        base::OnceCallback<void(absl::optional<mojom::WifiCredentials>)>;
     // The ack_successful bool indicates whether the ack was successfully
     // received by the source device. If true, then the target device will
     // prepare to resume the Quick Start connection after it updates.
diff --git a/chrome/browser/ash/login/screens/gaia_info_screen.cc b/chrome/browser/ash/login/screens/gaia_info_screen.cc
new file mode 100644
index 0000000..ddb1c583
--- /dev/null
+++ b/chrome/browser/ash/login/screens/gaia_info_screen.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 "chrome/browser/ash/login/screens/gaia_info_screen.h"
+#include "chrome/browser/ash/login/wizard_controller.h"
+#include "chrome/browser/ash/policy/core/browser_policy_connector_ash.h"
+#include "chrome/browser/browser_process.h"
+#include "chrome/browser/browser_process_platform_part.h"
+#include "chrome/browser/ui/webui/ash/login/gaia_info_screen_handler.h"
+
+namespace ash {
+
+namespace {
+
+constexpr char kUserActionBack[] = "back";
+constexpr char kUserActionNext[] = "next";
+
+}  // namespace
+
+// static
+std::string GaiaInfoScreen::GetResultString(Result result) {
+  switch (result) {
+    case Result::kNext:
+      return "Next";
+    case Result::kBack:
+      return "Back";
+    case Result::kNotApplicable:
+      return BaseScreen::kNotApplicable;
+  }
+}
+
+GaiaInfoScreen::GaiaInfoScreen(base::WeakPtr<GaiaInfoScreenView> view,
+                               const ScreenExitCallback& exit_callback)
+    : BaseScreen(GaiaInfoScreenView::kScreenId, OobeScreenPriority::DEFAULT),
+      view_(std::move(view)),
+      exit_callback_(exit_callback) {}
+
+GaiaInfoScreen::~GaiaInfoScreen() = default;
+
+bool GaiaInfoScreen::MaybeSkip(WizardContext& context) {
+  if (g_browser_process->platform_part()
+          ->browser_policy_connector_ash()
+          ->IsDeviceEnterpriseManaged() ||
+      context.is_add_person_flow || context.skip_to_login_for_tests) {
+    exit_callback_.Run(Result::kNotApplicable);
+    return true;
+  }
+  return false;
+}
+
+void GaiaInfoScreen::ShowImpl() {
+  if (!view_) {
+    return;
+  }
+
+  view_->Show();
+}
+
+void GaiaInfoScreen::HideImpl() {}
+
+void GaiaInfoScreen::OnUserAction(const base::Value::List& args) {
+  const std::string& action_id = args[0].GetString();
+  if (action_id == kUserActionBack) {
+    exit_callback_.Run(Result::kBack);
+  } else if (action_id == kUserActionNext) {
+    exit_callback_.Run(Result::kNext);
+  } else {
+    BaseScreen::OnUserAction(args);
+  }
+}
+
+}  // namespace ash
diff --git a/chrome/browser/ash/login/screens/gaia_info_screen.h b/chrome/browser/ash/login/screens/gaia_info_screen.h
new file mode 100644
index 0000000..843d4dc
--- /dev/null
+++ b/chrome/browser/ash/login/screens/gaia_info_screen.h
@@ -0,0 +1,50 @@
+// Copyright 2023 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_ASH_LOGIN_SCREENS_GAIA_INFO_SCREEN_H_
+#define CHROME_BROWSER_ASH_LOGIN_SCREENS_GAIA_INFO_SCREEN_H_
+
+#include <memory>
+#include <string>
+
+#include "base/functional/callback.h"
+#include "base/memory/weak_ptr.h"
+#include "chrome/browser/ash/login/screens/base_screen.h"
+
+namespace ash {
+
+class GaiaInfoScreenView;
+
+class GaiaInfoScreen : public BaseScreen {
+ public:
+  using TView = GaiaInfoScreenView;
+
+  enum class Result { kNext = 0, kBack, kNotApplicable };
+
+  using ScreenExitCallback = base::RepeatingCallback<void(Result result)>;
+
+  GaiaInfoScreen(base::WeakPtr<GaiaInfoScreenView> view,
+                 const ScreenExitCallback& exit_callback);
+
+  GaiaInfoScreen(const GaiaInfoScreen&) = delete;
+  GaiaInfoScreen& operator=(const GaiaInfoScreen&) = delete;
+
+  ~GaiaInfoScreen() override;
+
+  static std::string GetResultString(Result result);
+
+ private:
+  // BaseScreen:
+  bool MaybeSkip(WizardContext& context) override;
+  void ShowImpl() override;
+  void HideImpl() override;
+  void OnUserAction(const base::Value::List& args) override;
+
+  base::WeakPtr<GaiaInfoScreenView> view_;
+  ScreenExitCallback exit_callback_;
+};
+
+}  // namespace ash
+
+#endif  // CHROME_BROWSER_ASH_LOGIN_SCREENS_GAIA_INFO_SCREEN_H_
diff --git a/chrome/browser/ash/login/screens/gaia_info_screen_browsertest.cc b/chrome/browser/ash/login/screens/gaia_info_screen_browsertest.cc
new file mode 100644
index 0000000..19cc4b9
--- /dev/null
+++ b/chrome/browser/ash/login/screens/gaia_info_screen_browsertest.cc
@@ -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.
+
+#include "chrome/browser/ash/login/screens/gaia_info_screen.h"
+
+#include "ash/constants/ash_features.h"
+#include "ash/constants/ash_switches.h"
+#include "chrome/browser/ash/login/oobe_screen.h"
+#include "chrome/browser/ash/login/test/js_checker.h"
+#include "chrome/browser/ash/login/test/oobe_base_test.h"
+#include "chrome/browser/ash/login/test/oobe_screen_exit_waiter.h"
+#include "chrome/browser/ash/login/test/oobe_screen_waiter.h"
+#include "chrome/browser/ash/login/wizard_controller.h"
+#include "chrome/browser/ui/webui/ash/login/gaia_info_screen_handler.h"
+#include "content/public/test/browser_test.h"
+
+namespace ash {
+namespace {
+
+class GaiaInfoScreenTest : public OobeBaseTest {
+ public:
+  GaiaInfoScreenTest() {
+    feature_list_.InitAndEnableFeature(features::kOobeGaiaInfoScreen);
+  }
+
+  ~GaiaInfoScreenTest() override = default;
+
+  void ShowGaiaInfoScreen() {
+    WizardController::default_controller()->AdvanceToScreen(
+        GaiaInfoScreenView::kScreenId);
+  }
+
+ protected:
+  base::test::ScopedFeatureList feature_list_;
+};
+
+IN_PROC_BROWSER_TEST_F(GaiaInfoScreenTest, ForwardFlow) {
+  ShowGaiaInfoScreen();
+  OobeScreenWaiter(GaiaInfoScreenView::kScreenId).Wait();
+  test::OobeJS().TapOnPath({"gaia-info", "nextButton"});
+  OobeScreenExitWaiter(GaiaInfoScreenView::kScreenId).Wait();
+}
+
+IN_PROC_BROWSER_TEST_F(GaiaInfoScreenTest, BackFlow) {
+  ShowGaiaInfoScreen();
+  OobeScreenWaiter(GaiaInfoScreenView::kScreenId).Wait();
+  test::OobeJS().TapOnPath({"gaia-info", "backButton"});
+  OobeScreenExitWaiter(GaiaInfoScreenView::kScreenId).Wait();
+}
+
+}  // namespace
+}  // namespace ash
diff --git a/chrome/browser/ash/login/screens/gaia_screen.cc b/chrome/browser/ash/login/screens/gaia_screen.cc
index 3f9b91b9..1b35634 100644
--- a/chrome/browser/ash/login/screens/gaia_screen.cc
+++ b/chrome/browser/ash/login/screens/gaia_screen.cc
@@ -59,6 +59,8 @@
   switch (result) {
     case Result::BACK:
       return "Back";
+    case Result::BACK_CHILD:
+      return "BackChild";
     case Result::CANCEL:
       return "Cancel";
     case Result::ENTERPRISE_ENROLL:
@@ -171,7 +173,13 @@
 void GaiaScreen::OnUserAction(const base::Value::List& args) {
   const std::string& action_id = args[0].GetString();
   if (action_id == kUserActionBack) {
-    exit_callback_.Run(Result::BACK);
+    GaiaView::GaiaPath gaiaPath = view_->GetGaiaPath();
+    if (gaiaPath == GaiaView::GaiaPath::kChildSignup ||
+        gaiaPath == GaiaView::GaiaPath::kChildSignin) {
+      exit_callback_.Run(Result::BACK_CHILD);
+    } else {
+      exit_callback_.Run(Result::BACK);
+    }
   } else if (action_id == kUserActionCancel) {
     exit_callback_.Run(Result::CANCEL);
   } else if (action_id == kUserActionStartEnrollment) {
diff --git a/chrome/browser/ash/login/screens/gaia_screen.h b/chrome/browser/ash/login/screens/gaia_screen.h
index 9a2c509..76821476 100644
--- a/chrome/browser/ash/login/screens/gaia_screen.h
+++ b/chrome/browser/ash/login/screens/gaia_screen.h
@@ -34,6 +34,9 @@
 
   enum class Result {
     BACK,
+    // used to distinguish clicking back on the child sign-in/sign-up screen
+    // vs. default Gaia screen
+    BACK_CHILD,
     CANCEL,
     ENTERPRISE_ENROLL,
     START_CONSUMER_KIOSK,
diff --git a/chrome/browser/ash/login/version_updater/version_updater.cc b/chrome/browser/ash/login/version_updater/version_updater.cc
index a8386c0..935511d 100644
--- a/chrome/browser/ash/login/version_updater/version_updater.cc
+++ b/chrome/browser/ash/login/version_updater/version_updater.cc
@@ -174,6 +174,11 @@
 
 void VersionUpdater::UpdateStatusChanged(
     const update_engine::StatusResult& status) {
+  // Ignore any signals that relate to DLC installs.
+  if (status.is_install()) {
+    return;
+  }
+
   update_info_.status = status;
 
   if (update_info_.is_checking_for_update &&
diff --git a/chrome/browser/ash/login/wizard_controller.cc b/chrome/browser/ash/login/wizard_controller.cc
index dff810b..27ce3c17 100644
--- a/chrome/browser/ash/login/wizard_controller.cc
+++ b/chrome/browser/ash/login/wizard_controller.cc
@@ -73,6 +73,7 @@
 #include "chrome/browser/ash/login/screens/error_screen.h"
 #include "chrome/browser/ash/login/screens/family_link_notice_screen.h"
 #include "chrome/browser/ash/login/screens/fingerprint_setup_screen.h"
+#include "chrome/browser/ash/login/screens/gaia_info_screen.h"
 #include "chrome/browser/ash/login/screens/gaia_password_changed_screen.h"
 #include "chrome/browser/ash/login/screens/gaia_password_changed_screen_legacy.h"
 #include "chrome/browser/ash/login/screens/gaia_screen.h"
@@ -154,6 +155,7 @@
 #include "chrome/browser/ui/webui/ash/login/error_screen_handler.h"
 #include "chrome/browser/ui/webui/ash/login/family_link_notice_screen_handler.h"
 #include "chrome/browser/ui/webui/ash/login/fingerprint_setup_screen_handler.h"
+#include "chrome/browser/ui/webui/ash/login/gaia_info_screen_handler.h"
 #include "chrome/browser/ui/webui/ash/login/gaia_password_changed_screen_handler.h"
 #include "chrome/browser/ui/webui/ash/login/gaia_screen_handler.h"
 #include "chrome/browser/ui/webui/ash/login/gesture_navigation_screen_handler.h"
@@ -749,6 +751,14 @@
       oobe_ui->GetView<PackagedLicenseScreenHandler>()->AsWeakPtr(),
       base::BindRepeating(&WizardController::OnPackagedLicenseScreenExit,
                           weak_factory_.GetWeakPtr())));
+
+  if (features::IsOobeGaiaInfoScreenEnabled()) {
+    append(std::make_unique<GaiaInfoScreen>(
+        oobe_ui->GetView<GaiaInfoScreenHandler>()->AsWeakPtr(),
+        base::BindRepeating(&WizardController::OnGaiaInfoScreenExit,
+                            weak_factory_.GetWeakPtr())));
+  }
+
   append(std::make_unique<GaiaScreen>(
       oobe_ui->GetView<GaiaScreenHandler>()->AsWeakPtr(),
       base::BindRepeating(&WizardController::OnGaiaScreenExit,
@@ -975,6 +985,10 @@
   SetCurrentScreen(GetScreen<GaiaPasswordChangedScreen>());
 }
 
+void WizardController::ShowGaiaInfoScreen() {
+  SetCurrentScreen(GetScreen(GaiaInfoScreenView::kScreenId));
+}
+
 void WizardController::ShowEnrollmentScreen() {
   // Update the enrollment configuration and start the screen.
   prescribed_enrollment_config_ =
@@ -1208,7 +1222,11 @@
   switch (result) {
     case UserCreationScreen::Result::SIGNIN:
     case UserCreationScreen::Result::SKIPPED:
-      AdvanceToSigninScreen();
+      if (features::IsOobeGaiaInfoScreenEnabled()) {
+        ShowGaiaInfoScreen();
+      } else {
+        AdvanceToSigninScreen();
+      }
       break;
     case UserCreationScreen::Result::CHILD_SIGNIN:
       GetScreen<GaiaScreen>()->LoadOnlineForChildSignin();
@@ -1236,16 +1254,42 @@
   OnScreenExit(GaiaView::kScreenId, GaiaScreen::GetResultString(result));
   switch (result) {
     case GaiaScreen::Result::BACK:
+    case GaiaScreen::Result::BACK_CHILD:
     case GaiaScreen::Result::CANCEL: {
-      if (result == GaiaScreen::Result::BACK &&
-          wizard_context_->is_user_creation_enabled) {
-        // `Result::BACK` is only triggered when pressing back button. It goes
-        // back to UserCreationScreen if screen is enabled; otherwise, it
-        // behaves the same as `Result::CANCEL` which is triggered by pressing
-        // ESC key.
-        AdvanceToScreen(UserCreationView::kScreenId);
-        break;
+      if (features::IsOobeGaiaInfoScreenEnabled()) {
+        if (wizard_context_->is_user_creation_enabled) {
+          // `Result::BACK` and `Result::BACK_CHILD` are only triggered when
+          // pressing back button. It goes back to GaiaInfoScreenView if user
+          // creation is enabled; otherwise, it behaves the same as
+          // `Result::CANCEL` which is triggered by pressing ESC key.
+          // In the child flow the GaiaInfo screen is not shown, so in case of
+          // `Result::BACK_CHILD` we should go back to user creation screen
+          if (result == GaiaScreen::Result::BACK) {
+            if (wizard_context_->is_add_person_flow) {
+              AdvanceToScreen(UserCreationView::kScreenId);
+            } else {
+              AdvanceToScreen(GaiaInfoScreenView::kScreenId);
+            }
+            break;
+          }
+          if (result == GaiaScreen::Result::BACK_CHILD) {
+            AdvanceToScreen(UserCreationView::kScreenId);
+            break;
+          }
+        }
+      } else {
+        // TODO: delete this part after removing the feature flag
+        if (result == GaiaScreen::Result::BACK &&
+            wizard_context_->is_user_creation_enabled) {
+          // `Result::BACK` is only triggered when pressing back button. It goes
+          // back to UserCreationScreen if screen is enabled; otherwise, it
+          // behaves the same as `Result::CANCEL` which is triggered by pressing
+          // ESC key.
+          AdvanceToScreen(UserCreationView::kScreenId);
+          break;
+        }
       }
+
       // If a default redirection to third party IdP is set we can hide the
       // dialog.
       const bool gaia_page_defaults_to_saml = IsGaiaPageDefaultsToSAML();
@@ -1270,6 +1314,20 @@
   }
 }
 
+void WizardController::OnGaiaInfoScreenExit(GaiaInfoScreen::Result result) {
+  OnScreenExit(GaiaInfoScreenView::kScreenId,
+               GaiaInfoScreen::GetResultString(result));
+  switch (result) {
+    case GaiaInfoScreen::Result::kBack:
+      AdvanceToScreen(UserCreationView::kScreenId);
+      break;
+    case GaiaInfoScreen::Result::kNext:
+    case GaiaInfoScreen::Result::kNotApplicable:
+      AdvanceToSigninScreen();
+      break;
+  }
+}
+
 void WizardController::OnSamlConfirmPasswordScreenExit(
     SamlConfirmPasswordScreen::Result result) {
   OnScreenExit(SamlConfirmPasswordView::kScreenId,
@@ -2439,6 +2497,8 @@
     ShowArcVmDataMigrationScreen();
   } else if (screen_id == TouchpadScrollScreenView::kScreenId) {
     ShowTouchpadScrollScreen();
+  } else if (screen_id == GaiaInfoScreenView::kScreenId) {
+    ShowGaiaInfoScreen();
   } else if (screen_id == TpmErrorView::kScreenId ||
              screen_id == GaiaPasswordChangedView::kScreenId ||
              screen_id == ActiveDirectoryPasswordChangeView::kScreenId ||
diff --git a/chrome/browser/ash/login/wizard_controller.h b/chrome/browser/ash/login/wizard_controller.h
index 4090f7e..515d3ae 100644
--- a/chrome/browser/ash/login/wizard_controller.h
+++ b/chrome/browser/ash/login/wizard_controller.h
@@ -38,6 +38,7 @@
 #include "chrome/browser/ash/login/screens/enable_debugging_screen.h"
 #include "chrome/browser/ash/login/screens/family_link_notice_screen.h"
 #include "chrome/browser/ash/login/screens/fingerprint_setup_screen.h"
+#include "chrome/browser/ash/login/screens/gaia_info_screen.h"
 #include "chrome/browser/ash/login/screens/gaia_password_changed_screen.h"
 #include "chrome/browser/ash/login/screens/gaia_password_changed_screen_legacy.h"
 #include "chrome/browser/ash/login/screens/gaia_screen.h"
@@ -332,6 +333,7 @@
   void ShowDisplaySizeScreen();
   void ShowGaiaPasswordChangedScreen(std::unique_ptr<UserContext> user_context);
   void ShowDrivePinningScreen();
+  void ShowGaiaInfoScreen();
 
   // Shows images login screen.
   void ShowLoginScreen();
@@ -420,6 +422,7 @@
   void OnTouchpadScreenExit(TouchpadScrollScreen::Result result);
   void OnDisplaySizeScreenExit(DisplaySizeScreen::Result result);
   void OnDrivePinningScreenExit(DrivePinningScreen::Result result);
+  void OnGaiaInfoScreenExit(GaiaInfoScreen::Result result);
 
   // Callback invoked once it has been determined whether the device is disabled
   // or not.
diff --git a/chrome/browser/ash/login/wizard_controller_browsertest.cc b/chrome/browser/ash/login/wizard_controller_browsertest.cc
index 3966e4bd..e3d9904e 100644
--- a/chrome/browser/ash/login/wizard_controller_browsertest.cc
+++ b/chrome/browser/ash/login/wizard_controller_browsertest.cc
@@ -5,7 +5,9 @@
 #include "chrome/browser/ash/login/wizard_controller.h"
 
 #include <memory>
+#include <tuple>
 
+#include "ash/constants/ash_features.h"
 #include "ash/constants/ash_switches.h"
 #include "ash/public/cpp/login_screen_test_api.h"
 #include "base/command_line.h"
@@ -54,6 +56,7 @@
 #include "chrome/browser/ash/login/test/device_state_mixin.h"
 #include "chrome/browser/ash/login/test/js_checker.h"
 #include "chrome/browser/ash/login/test/login_manager_mixin.h"
+#include "chrome/browser/ash/login/test/network_portal_detector_mixin.h"
 #include "chrome/browser/ash/login/test/oobe_base_test.h"
 #include "chrome/browser/ash/login/test/oobe_configuration_waiter.h"
 #include "chrome/browser/ash/login/test/oobe_screen_exit_waiter.h"
@@ -80,6 +83,7 @@
 #include "chrome/browser/profiles/profile_manager.h"
 #include "chrome/browser/ui/webui/ash/login/consolidated_consent_screen_handler.h"
 #include "chrome/browser/ui/webui/ash/login/error_screen_handler.h"
+#include "chrome/browser/ui/webui/ash/login/gaia_info_screen_handler.h"
 #include "chrome/browser/ui/webui/ash/login/gaia_screen_handler.h"
 #include "chrome/browser/ui/webui/ash/login/gesture_navigation_screen_handler.h"
 #include "chrome/browser/ui/webui/ash/login/local_state_error_screen_handler.h"
@@ -3040,6 +3044,131 @@
   OobeScreenWaiter(ThemeSelectionScreenView::kScreenId).Wait();
 }
 
+class GaiaInfoTest : public WizardControllerTest {
+ public:
+  GaiaInfoTest() {
+    feature_list_.InitAndEnableFeature(features::kOobeGaiaInfoScreen);
+  }
+
+ protected:
+  base::test::ScopedFeatureList feature_list_;
+};
+
+class GaiaInfoScreenForEnterpriseEnrollmentTest : public GaiaInfoTest {
+ private:
+  DeviceStateMixin device_state_{
+      &mixin_host_, DeviceStateMixin::State::OOBE_COMPLETED_CLOUD_ENROLLED};
+};
+
+IN_PROC_BROWSER_TEST_F(GaiaInfoScreenForEnterpriseEnrollmentTest,
+                       SkippingGaiaInfo) {
+  WizardController::default_controller()->AdvanceToScreen(
+      GaiaInfoScreenView::kScreenId);
+  OobeScreenWaiter(GaiaView::kScreenId).Wait();
+}
+
+IN_PROC_BROWSER_TEST_F(GaiaInfoTest, TransitionToGaiaInfo) {
+  WizardController::default_controller()->AdvanceToScreen(
+      UserCreationView::kScreenId);
+  test::OobeJS().ClickOnPath({"user-creation", "selfButton"});
+  test::OobeJS().ClickOnPath({"user-creation", "nextButton"});
+  OobeScreenWaiter(GaiaInfoScreenView::kScreenId).Wait();
+}
+
+IN_PROC_BROWSER_TEST_F(GaiaInfoTest, TransitionFromGaiaInfo) {
+  WizardController::default_controller()->AdvanceToScreen(
+      GaiaInfoScreenView::kScreenId);
+  test::OobeJS().ClickOnPath({"gaia-info", "nextButton"});
+  OobeScreenWaiter(GaiaView::kScreenId).Wait();
+}
+
+IN_PROC_BROWSER_TEST_F(GaiaInfoTest, SkipGaiaInfoForChildAccountCreation) {
+  WizardController::default_controller()->AdvanceToScreen(
+      UserCreationView::kScreenId);
+  test::OobeJS().ClickOnPath({"user-creation", "childButton"});
+  test::OobeJS().ClickOnPath({"user-creation", "nextButton"});
+  test::OobeJS().ClickOnPath({"user-creation", "childCreateButton"});
+  test::OobeJS().ClickOnPath({"user-creation", "childNextButton"});
+  OobeScreenWaiter(GaiaView::kScreenId).Wait();
+}
+
+IN_PROC_BROWSER_TEST_F(GaiaInfoTest, SkipGaiaInfoForChildSignIn) {
+  WizardController::default_controller()->AdvanceToScreen(
+      UserCreationView::kScreenId);
+  test::OobeJS().ClickOnPath({"user-creation", "childButton"});
+  test::OobeJS().ClickOnPath({"user-creation", "nextButton"});
+  test::OobeJS().ClickOnPath({"user-creation", "childSignInButton"});
+  test::OobeJS().ClickOnPath({"user-creation", "childNextButton"});
+  OobeScreenWaiter(GaiaView::kScreenId).Wait();
+}
+
+class WizardControllerGaiaTest : public GaiaInfoTest {
+ protected:
+  FakeGaiaMixin fake_gaia_{&mixin_host_};
+};
+
+IN_PROC_BROWSER_TEST_F(WizardControllerGaiaTest, GoBackToGaiaInfo) {
+  LoginDisplayHost::default_host()
+      ->GetWizardContext()
+      ->is_user_creation_enabled = true;
+  WizardController::default_controller()->AdvanceToScreen(
+      GaiaInfoScreenView::kScreenId);
+  test::OobeJS().ClickOnPath({"gaia-info", "nextButton"});
+  OobeScreenWaiter(GaiaView::kScreenId).Wait();
+
+  test::OobeJS().ClickOnPath(
+      {"gaia-signin", "signin-frame-dialog", "signin-back-button"});
+  OobeScreenWaiter(GaiaInfoScreenView::kScreenId).Wait();
+}
+
+IN_PROC_BROWSER_TEST_F(WizardControllerGaiaTest,
+                       GoBackSkippingGaiaInfoInAddPersonFlow) {
+  LoginDisplayHost::default_host()
+      ->GetWizardContext()
+      ->is_user_creation_enabled = true;
+  LoginDisplayHost::default_host()->GetWizardContext()->is_add_person_flow =
+      true;
+
+  WizardController::default_controller()->AdvanceToScreen(
+      GaiaInfoScreenView::kScreenId);
+  test::OobeJS().ClickOnPath({"gaia-info", "nextButton"});
+  OobeScreenWaiter(GaiaView::kScreenId).Wait();
+
+  test::OobeJS().ClickOnPath(
+      {"gaia-signin", "signin-frame-dialog", "signin-back-button"});
+  OobeScreenWaiter(UserCreationView::kScreenId).Wait();
+}
+
+class GoingBackFromGaiaScreenInChildFlowTest
+    : public GaiaInfoTest,
+      public testing::WithParamInterface<std::tuple<bool, std::string>> {};
+
+IN_PROC_BROWSER_TEST_P(GoingBackFromGaiaScreenInChildFlowTest,
+                       SkippingGaiaInfoScreen) {
+  LoginDisplayHost::default_host()->GetWizardContext()->is_add_person_flow =
+      std::get<0>(GetParam());
+
+  WizardController::default_controller()->AdvanceToScreen(
+      UserCreationView::kScreenId);
+  test::OobeJS().ClickOnPath({"user-creation", "childButton"});
+  test::OobeJS().ClickOnPath({"user-creation", "nextButton"});
+  test::OobeJS().ClickOnPath({"user-creation", std::get<1>(GetParam())});
+  test::OobeJS().ClickOnPath({"user-creation", "childNextButton"});
+
+  OobeScreenWaiter(GaiaView::kScreenId).Wait();
+
+  test::OobeJS().ClickOnPath(
+      {"gaia-signin", "signin-frame-dialog", "signin-back-button"});
+  OobeScreenWaiter(UserCreationView::kScreenId).Wait();
+}
+
+INSTANTIATE_TEST_SUITE_P(
+    All,
+    GoingBackFromGaiaScreenInChildFlowTest,
+    testing::Values(std::make_tuple(true, "childCreateButton"),
+                    std::make_tuple(true, "childSignInButton"),
+                    std::make_tuple(false, "childCreateButton"),
+                    std::make_tuple(false, "childSignInButton")));
 // TODO(nkostylev): Add test for WebUI accelerators http://crosbug.com/22571
 
 // TODO(merkulova): Add tests for bluetooth HID detection screen variations when
diff --git a/chrome/browser/ash/plugin_vm/plugin_vm_uninstaller_notification.cc b/chrome/browser/ash/plugin_vm/plugin_vm_uninstaller_notification.cc
index c4f5791..4847e408 100644
--- a/chrome/browser/ash/plugin_vm/plugin_vm_uninstaller_notification.cc
+++ b/chrome/browser/ash/plugin_vm/plugin_vm_uninstaller_notification.cc
@@ -43,7 +43,7 @@
   message_center::RichNotificationData rich_notification_data;
   rich_notification_data.vector_small_image = &kNotificationPluginVmIcon;
   if (chromeos::features::IsJellyEnabled()) {
-    rich_notification_data.accent_color_id = cros_tokens::kCrosSysOnPrimary;
+    rich_notification_data.accent_color_id = cros_tokens::kCrosSysPrimary;
   } else {
     rich_notification_data.accent_color = ash::kSystemNotificationColorNormal;
   }
diff --git a/chrome/browser/ash/policy/reporting/metrics_reporting/metric_reporting_prefs.cc b/chrome/browser/ash/policy/reporting/metrics_reporting/metric_reporting_prefs.cc
new file mode 100644
index 0000000..fcd6e91a
--- /dev/null
+++ b/chrome/browser/ash/policy/reporting/metrics_reporting/metric_reporting_prefs.cc
@@ -0,0 +1,23 @@
+// Copyright 2023 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/ash/policy/reporting/metrics_reporting/metric_reporting_prefs.h"
+
+#include "base/values.h"
+#include "chrome/browser/chromeos/reporting/metric_default_utils.h"
+#include "components/pref_registry/pref_registry_syncable.h"
+
+namespace ash::reporting {
+
+void RegisterProfilePrefs(::user_prefs::PrefRegistrySyncable* registry) {
+  DCHECK(registry);
+  registry->RegisterListPref(kReportAppInventory);
+  registry->RegisterListPref(kReportAppUsage);
+  registry->RegisterIntegerPref(
+      kReportAppUsageCollectionRateMs,
+      ::reporting::metrics::kDefaultAppUsageTelemetryCollectionRate
+          .InMilliseconds());
+}
+
+}  // namespace ash::reporting
diff --git a/chrome/browser/ash/policy/reporting/metrics_reporting/metric_reporting_prefs.h b/chrome/browser/ash/policy/reporting/metrics_reporting/metric_reporting_prefs.h
new file mode 100644
index 0000000..57995b5
--- /dev/null
+++ b/chrome/browser/ash/policy/reporting/metrics_reporting/metric_reporting_prefs.h
@@ -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.
+
+#ifndef CHROME_BROWSER_ASH_POLICY_REPORTING_METRICS_REPORTING_METRIC_REPORTING_PREFS_H_
+#define CHROME_BROWSER_ASH_POLICY_REPORTING_METRICS_REPORTING_METRIC_REPORTING_PREFS_H_
+
+namespace user_prefs {
+class PrefRegistrySyncable;
+}
+
+namespace ash::reporting {
+
+// A list pref that controls app inventory event reporting for the specified app
+// types.
+constexpr char kReportAppInventory[] = "reporting.report_app_inventory";
+
+// A list pref that controls app usage telemetry reporting for the specified app
+// types.
+constexpr char kReportAppUsage[] = "reporting.report_app_usage";
+
+// An integer pref that controls the collection frequency of app usage
+// telemetry.
+constexpr char kReportAppUsageCollectionRateMs[] =
+    "reporting.report_app_usage_collection_rate_ms";
+
+void RegisterProfilePrefs(::user_prefs::PrefRegistrySyncable* registry);
+
+}  // namespace ash::reporting
+
+#endif  // CHROME_BROWSER_ASH_POLICY_REPORTING_METRICS_REPORTING_METRIC_REPORTING_PREFS_H_
diff --git a/chrome/browser/ash/preferences.cc b/chrome/browser/ash/preferences.cc
index d76f2ee..388d0cf 100644
--- a/chrome/browser/ash/preferences.cc
+++ b/chrome/browser/ash/preferences.cc
@@ -347,6 +347,13 @@
   // device.
   registry->RegisterBooleanPref(prefs::kSendFunctionKeys, false);
 
+  registry->RegisterBooleanPref(prefs::kEventRemappedToRightClick, false);
+  registry->RegisterIntegerPref(prefs::kKeyEventRemappedToSixPackDelete, 0);
+  registry->RegisterIntegerPref(prefs::kKeyEventRemappedToSixPackEnd, 0);
+  registry->RegisterIntegerPref(prefs::kKeyEventRemappedToSixPackHome, 0);
+  registry->RegisterIntegerPref(prefs::kKeyEventRemappedToSixPackPageUp, 0);
+  registry->RegisterIntegerPref(prefs::kKeyEventRemappedToSixPackPageDown, 0);
+
   // Don't sync the note-taking app; it may not be installed on other devices.
   registry->RegisterStringPref(::prefs::kNoteTakingAppId, std::string());
   registry->RegisterBooleanPref(::prefs::kRestoreLastLockScreenNote, true);
diff --git a/chrome/browser/ash/printing/cups_print_job_notification.cc b/chrome/browser/ash/printing/cups_print_job_notification.cc
index dcc12e43..4c3b7c0 100644
--- a/chrome/browser/ash/printing/cups_print_job_notification.cc
+++ b/chrome/browser/ash/printing/cups_print_job_notification.cc
@@ -231,7 +231,7 @@
     case CupsPrintJob::State::STATE_SUSPENDED:
     case CupsPrintJob::State::STATE_RESUMED: {
       if (chromeos::features::IsJellyEnabled()) {
-        notification_->set_accent_color_id(cros_tokens::kCrosSysOnPrimary);
+        notification_->set_accent_color_id(cros_tokens::kCrosSysPrimary);
       } else {
         notification_->set_accent_color(kSystemNotificationColorNormal);
       }
@@ -240,7 +240,7 @@
     }
     case CupsPrintJob::State::STATE_DOCUMENT_DONE: {
       if (chromeos::features::IsJellyEnabled()) {
-        notification_->set_accent_color_id(cros_tokens::kCrosSysOnPrimary);
+        notification_->set_accent_color_id(cros_tokens::kCrosSysPrimary);
       } else {
         notification_->set_accent_color(kSystemNotificationColorNormal);
       }
diff --git a/chrome/browser/ash/printing/usb_printer_notification.cc b/chrome/browser/ash/printing/usb_printer_notification.cc
index 0ac52a50..1341034 100644
--- a/chrome/browser/ash/printing/usb_printer_notification.cc
+++ b/chrome/browser/ash/printing/usb_printer_notification.cc
@@ -44,7 +44,7 @@
   message_center::RichNotificationData rich_notification_data;
   rich_notification_data.vector_small_image = &kNotificationPrintingIcon;
   if (chromeos::features::IsJellyEnabled()) {
-    rich_notification_data.accent_color_id = cros_tokens::kCrosSysOnPrimary;
+    rich_notification_data.accent_color_id = cros_tokens::kCrosSysPrimary;
   } else {
     rich_notification_data.accent_color = ash::kSystemNotificationColorNormal;
   }
diff --git a/chrome/browser/ash/usb/cros_usb_detector.cc b/chrome/browser/ash/usb/cros_usb_detector.cc
index 149a08f..cdb977ac 100644
--- a/chrome/browser/ash/usb/cros_usb_detector.cc
+++ b/chrome/browser/ash/usb/cros_usb_detector.cc
@@ -287,7 +287,7 @@
       gfx::CreateVectorIcon(vector_icons::kUsbIcon, 64, gfx::kGoogleBlue800));
 
   if (chromeos::features::IsJellyEnabled()) {
-    rich_notification_data.accent_color_id = cros_tokens::kCrosSysOnPrimary;
+    rich_notification_data.accent_color_id = cros_tokens::kCrosSysPrimary;
   } else {
     rich_notification_data.accent_color = ash::kSystemNotificationColorNormal;
   }
diff --git a/chrome/browser/banners/app_banner_manager_browsertest_base.cc b/chrome/browser/banners/app_banner_manager_browsertest_base.cc
index 35980df..f663fbb 100644
--- a/chrome/browser/banners/app_banner_manager_browsertest_base.cc
+++ b/chrome/browser/banners/app_banner_manager_browsertest_base.cc
@@ -38,7 +38,8 @@
   if (with_gesture)
     EXPECT_TRUE(content::ExecuteScript(web_contents, script));
   else
-    EXPECT_TRUE(content::ExecuteScriptWithoutUserGesture(web_contents, script));
+    EXPECT_TRUE(content::ExecJs(web_contents, script,
+                                content::EXECUTE_SCRIPT_NO_USER_GESTURE));
 }
 
 GURL AppBannerManagerBrowserTestBase::GetBannerURLWithAction(
diff --git a/chrome/browser/chrome_browser_main.cc b/chrome/browser/chrome_browser_main.cc
index 0b571c1b..67d7134 100644
--- a/chrome/browser/chrome_browser_main.cc
+++ b/chrome/browser/chrome_browser_main.cc
@@ -1739,10 +1739,7 @@
 
   variations::VariationsService* variations_service =
       browser_process_->variations_service();
-  // Only call PerformPreMainMessageLoopStartup() on VariationsService outside
-  // of integration (browser) tests.
-  if (!is_integration_test())
-    variations_service->PerformPreMainMessageLoopStartup();
+  variations_service->PerformPreMainMessageLoopStartup();
 
 #if BUILDFLAG(IS_ANDROID)
   // The profile picker is never shown on Android.
diff --git a/chrome/browser/chrome_navigation_browsertest.cc b/chrome/browser/chrome_navigation_browsertest.cc
index f049549..1004d89 100644
--- a/chrome/browser/chrome_navigation_browsertest.cc
+++ b/chrome/browser/chrome_navigation_browsertest.cc
@@ -1649,8 +1649,9 @@
   content::WebContents* main_contents =
       browser()->tab_strip_model()->GetActiveWebContents();
   content::TestNavigationObserver observer(main_contents);
-  EXPECT_TRUE(ExecuteScriptWithoutUserGesture(
-      main_contents, "location = '" + redirected_url.spec() + "';"));
+  EXPECT_TRUE(ExecJs(main_contents,
+                     "location = '" + redirected_url.spec() + "';",
+                     content::EXECUTE_SCRIPT_NO_USER_GESTURE));
   observer.Wait();
   EXPECT_EQ(redirected_url, main_contents->GetLastCommittedURL());
 
@@ -1692,8 +1693,9 @@
     content::WebContents* main_contents =
         browser()->tab_strip_model()->GetActiveWebContents();
     content::TestNavigationObserver observer(main_contents);
-    EXPECT_TRUE(ExecuteScriptWithoutUserGesture(
-        main_contents, "location = '" + redirected_url.spec() + "';"));
+    EXPECT_TRUE(ExecJs(main_contents,
+                       "location = '" + redirected_url.spec() + "';",
+                       content::EXECUTE_SCRIPT_NO_USER_GESTURE));
     observer.Wait();
     EXPECT_EQ(redirected_url, main_contents->GetLastCommittedURL());
   }
@@ -1947,8 +1949,9 @@
   // Navigate to a new document from the renderer without a user gesture.
   GURL redirected_url(embedded_test_server()->GetURL("/title2.html"));
   content::TestNavigationManager manager(main_contents, redirected_url);
-  EXPECT_TRUE(ExecuteScriptWithoutUserGesture(
-      main_contents, "location = '" + redirected_url.spec() + "';"));
+  EXPECT_TRUE(ExecJs(main_contents,
+                     "location = '" + redirected_url.spec() + "';",
+                     content::EXECUTE_SCRIPT_NO_USER_GESTURE));
   ASSERT_TRUE(manager.WaitForNavigationFinished());
   ASSERT_EQ(redirected_url, main_contents->GetLastCommittedURL());
   ASSERT_EQ(2, main_contents->GetController().GetEntryCount());
@@ -1977,8 +1980,9 @@
   // Navigate to a new document from the renderer without a user gesture.
   GURL redirected_url(embedded_test_server()->GetURL("/title2.html"));
   content::TestNavigationManager manager(main_contents, redirected_url);
-  EXPECT_TRUE(ExecuteScriptWithoutUserGesture(
-      main_contents, "location = '" + redirected_url.spec() + "';"));
+  EXPECT_TRUE(ExecJs(main_contents,
+                     "location = '" + redirected_url.spec() + "';",
+                     content::EXECUTE_SCRIPT_NO_USER_GESTURE));
   ASSERT_TRUE(manager.WaitForNavigationFinished());
   ASSERT_EQ(redirected_url, main_contents->GetLastCommittedURL());
   ASSERT_EQ(3, main_contents->GetController().GetEntryCount());
@@ -2035,8 +2039,8 @@
   content::WebContents* main_contents =
       browser()->tab_strip_model()->GetActiveWebContents();
   content::TestNavigationObserver observer(main_contents);
-  EXPECT_TRUE(ExecuteScriptWithoutUserGesture(
-      main_contents, "location = '" + url.spec() + "';"));
+  EXPECT_TRUE(ExecJs(main_contents, "location = '" + url.spec() + "';",
+                     content::EXECUTE_SCRIPT_NO_USER_GESTURE));
   observer.Wait();
   EXPECT_EQ(url, main_contents->GetLastCommittedURL());
 
diff --git a/chrome/browser/chromeos/reporting/metric_default_utils.h b/chrome/browser/chromeos/reporting/metric_default_utils.h
index 7d1d8c79..89b4d8b 100644
--- a/chrome/browser/chromeos/reporting/metric_default_utils.h
+++ b/chrome/browser/chromeos/reporting/metric_default_utils.h
@@ -9,6 +9,10 @@
 
 namespace reporting::metrics {
 
+// Default app telemetry collection rate.
+constexpr base::TimeDelta kDefaultAppUsageTelemetryCollectionRate =
+    base::Minutes(15);
+
 // Default audio telemetry collection rate.
 constexpr base::TimeDelta kDefaultAudioTelemetryCollectionRate =
     base::Minutes(15);
diff --git a/chrome/browser/custom_handlers/protocol_handler_registry_browsertest.cc b/chrome/browser/custom_handlers/protocol_handler_registry_browsertest.cc
index 505d2cc6..aa33fc7 100644
--- a/chrome/browser/custom_handlers/protocol_handler_registry_browsertest.cc
+++ b/chrome/browser/custom_handlers/protocol_handler_registry_browsertest.cc
@@ -222,10 +222,10 @@
   ASSERT_TRUE(content_settings->pending_protocol_handler().IsEmpty());
 
   // Attempt to add an entry.
-  ASSERT_TRUE(content::ExecuteScriptWithoutUserGesture(
-      web_contents,
-      "navigator.registerProtocolHandler('web+"
-      "search', 'test.html?%s', 'test');"));
+  ASSERT_TRUE(content::ExecJs(web_contents,
+                              "navigator.registerProtocolHandler('web+"
+                              "search', 'test.html?%s', 'test');",
+                              content::EXECUTE_SCRIPT_NO_USER_GESTURE));
 
   // Verify the registration is ignored if no user gesture involved.
   ASSERT_EQ(0u, registry->GetHandlersFor(url.scheme()).size());
diff --git a/chrome/browser/download/download_frame_policy_browsertest.cc b/chrome/browser/download/download_frame_policy_browsertest.cc
index 24c67ba..9902dee 100644
--- a/chrome/browser/download/download_frame_policy_browsertest.cc
+++ b/chrome/browser/download/download_frame_policy_browsertest.cc
@@ -165,7 +165,8 @@
     if (initiate_with_gesture) {
       EXPECT_TRUE(ExecJs(adapter, script));
     } else {
-      EXPECT_TRUE(ExecuteScriptWithoutUserGesture(adapter, script));
+      EXPECT_TRUE(
+          ExecJs(adapter, script, content::EXECUTE_SCRIPT_NO_USER_GESTURE));
     }
   }
 
@@ -548,7 +549,8 @@
     if (initiate_with_gesture) {
       EXPECT_TRUE(ExecJs(web_contents(), script));
     } else {
-      EXPECT_TRUE(ExecuteScriptWithoutUserGesture(web_contents(), script));
+      EXPECT_TRUE(ExecJs(web_contents(), script,
+                         content::EXECUTE_SCRIPT_NO_USER_GESTURE));
     }
   }
 
diff --git a/chrome/browser/download/notification/download_item_notification.cc b/chrome/browser/download/notification/download_item_notification.cc
index 8e47f83e..20e614d 100644
--- a/chrome/browser/download/notification/download_item_notification.cc
+++ b/chrome/browser/download/notification/download_item_notification.cc
@@ -609,10 +609,10 @@
   }
   SkColor notification_color = GetNotificationIconColor();
   if (chromeos::features::IsJellyEnabled()) {
-    ui::ColorId color_id = cros_tokens::kCrosSysOnPrimary;
+    ui::ColorId color_id = cros_tokens::kCrosSysPrimary;
     switch (notification_color) {
       case ash::kSystemNotificationColorNormal:
-        color_id = cros_tokens::kCrosSysOnPrimary;
+        color_id = cros_tokens::kCrosSysPrimary;
         break;
       case ash::kSystemNotificationColorWarning:
         color_id = cros_tokens::kCrosSysWarning;
diff --git a/chrome/browser/flag-metadata.json b/chrome/browser/flag-metadata.json
index a1dd43c..12860d5 100644
--- a/chrome/browser/flag-metadata.json
+++ b/chrome/browser/flag-metadata.json
@@ -6592,17 +6592,17 @@
   {
     "name": "scrollable-tabstrip",
     "owners": [ "chrome-desktop-ui-sea@google.com", "tbergquist" ],
-    "expiry_milestone": 115
+    "expiry_milestone": 120
   },
   {
     "name": "scrollable-tabstrip-overflow",
     "owners": [ "chrome-desktop-ui-sea@google.com", "dpenning" ],
-    "expiry_milestone": 115
+    "expiry_milestone": 120
   },
   {
     "name": "scrollable-tabstrip-with-dragging",
     "owners": [ "chrome-desktop-ui-sea@google.com", "shibalik" ],
-    "expiry_milestone": 115
+    "expiry_milestone": 120
   },
   {
     "name": "sct-auditing",
diff --git a/chrome/browser/media/autoplay_metrics_browsertest.cc b/chrome/browser/media/autoplay_metrics_browsertest.cc
index 2b57ec3..922a084 100644
--- a/chrome/browser/media/autoplay_metrics_browsertest.cc
+++ b/chrome/browser/media/autoplay_metrics_browsertest.cc
@@ -34,8 +34,8 @@
     base::RunLoop run_loop;
     ukm_recorder.SetOnAddEntryCallback(Entry::kEntryName,
                                        run_loop.QuitClosure());
-    EXPECT_TRUE(ExecuteScriptWithoutUserGesture(adapter.render_frame_host(),
-                                                "tryPlayback();"));
+    EXPECT_TRUE(ExecJs(adapter.render_frame_host(), "tryPlayback();",
+                       content::EXECUTE_SCRIPT_NO_USER_GESTURE));
     run_loop.Run();
   }
 
diff --git a/chrome/browser/media/chromeos_login_and_lock_media_access_handler.cc b/chrome/browser/media/chromeos_login_and_lock_media_access_handler.cc
index 1424f913..e81b169 100644
--- a/chrome/browser/media/chromeos_login_and_lock_media_access_handler.cc
+++ b/chrome/browser/media/chromeos_login_and_lock_media_access_handler.cc
@@ -54,13 +54,12 @@
     return false;
 
   // The following checks are for SAML logins.
-  const base::Value* const list_value =
-      settings->GetPref(ash::kLoginVideoCaptureAllowedUrls);
-  if (!list_value)
+  const base::Value::List* allowed_urls_list;
+  if (!settings->GetList(ash::kLoginVideoCaptureAllowedUrls,
+                         &allowed_urls_list))
     return false;
 
-  DCHECK(list_value->is_list());
-  for (const auto& base_value : list_value->GetList()) {
+  for (const auto& base_value : *allowed_urls_list) {
     const std::string* value = base_value.GetIfString();
     if (value) {
       const ContentSettingsPattern pattern =
diff --git a/chrome/browser/media/media_engagement_autoplay_browsertest.cc b/chrome/browser/media/media_engagement_autoplay_browsertest.cc
index 3f2580ed..8927e14 100644
--- a/chrome/browser/media/media_engagement_autoplay_browsertest.cc
+++ b/chrome/browser/media/media_engagement_autoplay_browsertest.cc
@@ -105,10 +105,11 @@
   }
 
   void LoadSubFrame(const std::string& page) {
-    EXPECT_TRUE(content::ExecuteScriptWithoutUserGesture(
-        GetWebContents(), "document.getElementsByName('subframe')[0].src = \"" +
-                              http_server_origin2_.GetURL("/" + page).spec() +
-                              "\""));
+    EXPECT_TRUE(content::ExecJs(
+        GetWebContents(),
+        "document.getElementsByName('subframe')[0].src = \"" +
+            http_server_origin2_.GetURL("/" + page).spec() + "\"",
+        content::EXECUTE_SCRIPT_NO_USER_GESTURE));
   }
 
   void SetScores(const url::Origin& origin, int visits, int media_playbacks) {
diff --git a/chrome/browser/media/unified_autoplay_browsertest.cc b/chrome/browser/media/unified_autoplay_browsertest.cc
index fa820f6f..f4067c5 100644
--- a/chrome/browser/media/unified_autoplay_browsertest.cc
+++ b/chrome/browser/media/unified_autoplay_browsertest.cc
@@ -104,8 +104,9 @@
   bool NavigateInRenderer(content::WebContents* web_contents, const GURL& url) {
     content::TestNavigationObserver observer(web_contents);
 
-    bool result = content::ExecuteScriptWithoutUserGesture(
-        web_contents, "window.location = '" + url.spec() + "';");
+    bool result =
+        content::ExecJs(web_contents, "window.location = '" + url.spec() + "';",
+                        content::EXECUTE_SCRIPT_NO_USER_GESTURE);
 
     if (result)
       observer.Wait();
diff --git a/chrome/browser/metrics/process_memory_metrics_emitter_browsertest.cc b/chrome/browser/metrics/process_memory_metrics_emitter_browsertest.cc
index 2f7ba5f..2323dde 100644
--- a/chrome/browser/metrics/process_memory_metrics_emitter_browsertest.cc
+++ b/chrome/browser/metrics/process_memory_metrics_emitter_browsertest.cc
@@ -16,6 +16,7 @@
 #include "base/test/metrics/histogram_tester.h"
 #include "base/test/scoped_feature_list.h"
 #include "base/test/trace_event_analyzer.h"
+#include "base/threading/thread_restrictions.h"
 #include "base/trace_event/memory_dump_manager.h"
 #include "base/trace_event/trace_config_memory_test_util.h"
 #include "build/build_config.h"
@@ -870,6 +871,10 @@
            content::RenderProcessHost::AllHostsIterator();
        !rph_iter.IsAtEnd(); rph_iter.Advance()) {
     const base::Process& process = rph_iter.GetCurrentValue()->GetProcess();
+    // The main module's path might be a relative one, e.g. browser_tests.
+    // To match with the memory maps, need to convert it to absolute path,
+    // which may hit ScopedBlockingCall.
+    base::ScopedAllowBlockingForTesting allow_blocking;
     auto maps =
         memory_instrumentation::OSMetrics::GetProcessMemoryMaps(process.Pid());
     bool found = false;
diff --git a/chrome/browser/nearby_sharing/nearby_notification_manager.cc b/chrome/browser/nearby_sharing/nearby_notification_manager.cc
index 9e15aff0..87a1e82 100644
--- a/chrome/browser/nearby_sharing/nearby_notification_manager.cc
+++ b/chrome/browser/nearby_sharing/nearby_notification_manager.cc
@@ -89,7 +89,7 @@
       /*delegate=*/nullptr);
 
   if (chromeos::features::IsJellyEnabled()) {
-    notification.set_accent_color_id(cros_tokens::kCrosSysOnPrimary);
+    notification.set_accent_color_id(cros_tokens::kCrosSysPrimary);
   } else {
     notification.set_accent_color(ash::kSystemNotificationColorNormal);
   }
diff --git a/chrome/browser/page_load_metrics/observers/ad_metrics/ads_page_load_metrics_observer_browsertest.cc b/chrome/browser/page_load_metrics/observers/ad_metrics/ads_page_load_metrics_observer_browsertest.cc
index 446ec917..f21d52f 100644
--- a/chrome/browser/page_load_metrics/observers/ad_metrics/ads_page_load_metrics_observer_browsertest.cc
+++ b/chrome/browser/page_load_metrics/observers/ad_metrics/ads_page_load_metrics_observer_browsertest.cc
@@ -920,10 +920,12 @@
       browser()->tab_strip_model()->GetActiveWebContents();
 
   // Create a second frame that will not receive activation.
-  EXPECT_TRUE(content::ExecuteScriptWithoutUserGesture(
-      web_contents, "createAdFrame('/ad_tagging/ad.html', '');"));
-  EXPECT_TRUE(content::ExecuteScriptWithoutUserGesture(
-      web_contents, "createAdFrame('/ad_tagging/ad.html', '');"));
+  EXPECT_TRUE(content::ExecJs(web_contents,
+                              "createAdFrame('/ad_tagging/ad.html', '');",
+                              content::EXECUTE_SCRIPT_NO_USER_GESTURE));
+  EXPECT_TRUE(content::ExecJs(web_contents,
+                              "createAdFrame('/ad_tagging/ad.html', '');",
+                              content::EXECUTE_SCRIPT_NO_USER_GESTURE));
 
   // Wait for the frames resources to be loaded as we only log histograms for
   // frames that have non-zero bytes. Four resources in the main frame and one
@@ -971,10 +973,12 @@
       browser()->tab_strip_model()->GetActiveWebContents();
 
   // Create two same-origin ad frames.
-  EXPECT_TRUE(content::ExecuteScriptWithoutUserGesture(
-      web_contents, "createAdFrame('/ad_tagging/ad.html', '');"));
-  EXPECT_TRUE(content::ExecuteScriptWithoutUserGesture(
-      web_contents, "createAdFrame('/ad_tagging/ad.html', '');"));
+  EXPECT_TRUE(content::ExecJs(web_contents,
+                              "createAdFrame('/ad_tagging/ad.html', '');",
+                              content::EXECUTE_SCRIPT_NO_USER_GESTURE));
+  EXPECT_TRUE(content::ExecJs(web_contents,
+                              "createAdFrame('/ad_tagging/ad.html', '');",
+                              content::EXECUTE_SCRIPT_NO_USER_GESTURE));
 
   // Wait for the frames resources to be loaded as we only log histograms for
   // frames that have non-zero bytes. Four resources in the main frame and one
@@ -1356,9 +1360,9 @@
       browser()->tab_strip_model()->GetActiveWebContents();
 
   // Create a second frame that will not receive activation.
-  EXPECT_TRUE(content::ExecuteScriptWithoutUserGesture(
-      web_contents,
-      "createAdFrame('/ad_tagging/multiple_mimes.html', 'test');"));
+  EXPECT_TRUE(content::ExecJs(
+      web_contents, "createAdFrame('/ad_tagging/multiple_mimes.html', 'test');",
+      content::EXECUTE_SCRIPT_NO_USER_GESTURE));
 
   waiter->AddMinimumCompleteResourcesExpectation(8);
   waiter->Wait();
diff --git a/chrome/browser/password_manager/password_manager_browsertest.cc b/chrome/browser/password_manager/password_manager_browsertest.cc
index 9aed4ca..e86658b 100644
--- a/chrome/browser/password_manager/password_manager_browsertest.cc
+++ b/chrome/browser/password_manager/password_manager_browsertest.cc
@@ -513,8 +513,9 @@
   // Don't fill the password form, just navigate away. Shouldn't prompt.
   PasswordsNavigationObserver observer(WebContents());
   BubbleObserver prompt_observer(WebContents());
-  ASSERT_TRUE(content::ExecuteScriptWithoutUserGesture(
-      RenderFrameHost(), "window.location.href = 'done.html';"));
+  ASSERT_TRUE(content::ExecJs(RenderFrameHost(),
+                              "window.location.href = 'done.html';",
+                              content::EXECUTE_SCRIPT_NO_USER_GESTURE));
   ASSERT_TRUE(observer.Wait());
   EXPECT_FALSE(prompt_observer.IsSavePromptShownAutomatically());
 }
@@ -1648,8 +1649,8 @@
       embedded_test_server()->GetURL("/password/simple_password.html"));
   std::string attacker_redirect =
       "window.location.href = '" + http_url.spec() + "';";
-  ASSERT_TRUE(content::ExecuteScriptWithoutUserGesture(RenderFrameHost(),
-                                                       attacker_redirect));
+  ASSERT_TRUE(content::ExecJs(RenderFrameHost(), attacker_redirect,
+                              content::EXECUTE_SCRIPT_NO_USER_GESTURE));
 
   PasswordsNavigationObserver attacker_observer(WebContents());
   attacker_observer.SetPathToWaitFor("/password/simple_password.html");
@@ -2218,8 +2219,8 @@
       "abc.foo.com", "/password/crossite_iframe_content.html");
   std::string create_iframe =
       base::StringPrintf("create_iframe('%s');", iframe_url.spec().c_str());
-  ASSERT_TRUE(content::ExecuteScriptWithoutUserGesture(RenderFrameHost(),
-                                                       create_iframe));
+  ASSERT_TRUE(content::ExecJs(RenderFrameHost(), create_iframe,
+                              content::EXECUTE_SCRIPT_NO_USER_GESTURE));
   ASSERT_TRUE(iframe_observer.Wait());
 
   // Store a password for autofill later.
@@ -2242,8 +2243,8 @@
 
   PasswordsNavigationObserver iframe_observer_2(WebContents());
   iframe_observer_2.SetPathToWaitFor("/password/crossite_iframe_content.html");
-  ASSERT_TRUE(content::ExecuteScriptWithoutUserGesture(RenderFrameHost(),
-                                                       create_iframe));
+  ASSERT_TRUE(content::ExecJs(RenderFrameHost(), create_iframe,
+                              content::EXECUTE_SCRIPT_NO_USER_GESTURE));
   ASSERT_TRUE(iframe_observer_2.Wait());
 
   // Simulate the user interaction in the iframe which should trigger autofill.
@@ -2280,8 +2281,8 @@
       "www.bar.com", "/password/crossite_iframe_content.html");
   std::string create_iframe =
       base::StringPrintf("create_iframe('%s');", iframe_url.spec().c_str());
-  ASSERT_TRUE(content::ExecuteScriptWithoutUserGesture(RenderFrameHost(),
-                                                       create_iframe));
+  ASSERT_TRUE(content::ExecJs(RenderFrameHost(), create_iframe,
+                              content::EXECUTE_SCRIPT_NO_USER_GESTURE));
   ASSERT_TRUE(iframe_observer.Wait());
 
   // Store a password for autofill later.
@@ -2304,8 +2305,8 @@
 
   PasswordsNavigationObserver iframe_observer_2(WebContents());
   iframe_observer_2.SetPathToWaitFor("/password/crossite_iframe_content.html");
-  ASSERT_TRUE(content::ExecuteScriptWithoutUserGesture(RenderFrameHost(),
-                                                       create_iframe));
+  ASSERT_TRUE(content::ExecJs(RenderFrameHost(), create_iframe,
+                              content::EXECUTE_SCRIPT_NO_USER_GESTURE));
   ASSERT_TRUE(iframe_observer_2.Wait());
 
   // Simulate the user interaction in the iframe which should trigger autofill.
@@ -3690,11 +3691,12 @@
       embedded_test_server()->GetURL("www.foo.com", "/password/done.html");
   PasswordsNavigationObserver observer_done(WebContents());
   observer_done.SetPathToWaitFor("/password/done.html");
-  ASSERT_TRUE(content::ExecuteScriptWithoutUserGesture(
+  ASSERT_TRUE(content::ExecJs(
       RenderFrameHost(),
       "document.getElementById('new_p').value = 'new password';"
       "document.getElementById('conf_p').value = 'new password';"
-      "document.getElementById('testform').submit();"));
+      "document.getElementById('testform').submit();",
+      content::EXECUTE_SCRIPT_NO_USER_GESTURE));
   ASSERT_TRUE(observer_done.Wait());
 
   // Check that the password for origin A was not updated automatically and the
@@ -3792,18 +3794,18 @@
 
   // Simulate that a script removes username/password elements and adds the
   // elements identical to them.
-  ASSERT_TRUE(content::ExecuteScriptWithoutUserGesture(
-      RenderFrameHost(),
-      "function replaceElement(id) {"
-      "  var elem = document.getElementById(id);"
-      "  var parent = elem.parentElement;"
-      "  var cloned_elem = elem.cloneNode();"
-      "  cloned_elem.value = '';"
-      "  parent.removeChild(elem);"
-      "  parent.appendChild(cloned_elem);"
-      "}"
-      "replaceElement('username_field');"
-      "replaceElement('password_field');"));
+  ASSERT_TRUE(content::ExecJs(RenderFrameHost(),
+                              "function replaceElement(id) {"
+                              "  var elem = document.getElementById(id);"
+                              "  var parent = elem.parentElement;"
+                              "  var cloned_elem = elem.cloneNode();"
+                              "  cloned_elem.value = '';"
+                              "  parent.removeChild(elem);"
+                              "  parent.appendChild(cloned_elem);"
+                              "}"
+                              "replaceElement('username_field');"
+                              "replaceElement('password_field');",
+                              content::EXECUTE_SCRIPT_NO_USER_GESTURE));
 
   // Let the user interact with the page, so that DOM gets modification events,
   // needed for autofilling fields.
diff --git a/chrome/browser/permissions/permission_request_manager_browsertest.cc b/chrome/browser/permissions/permission_request_manager_browsertest.cc
index 80ffa2f..1d2a8b6 100644
--- a/chrome/browser/permissions/permission_request_manager_browsertest.cc
+++ b/chrome/browser/permissions/permission_request_manager_browsertest.cc
@@ -178,8 +178,9 @@
     // In response, simulate the website automatically triggering a
     // renderer-initiated cross-origin navigation without user gesture.
     content::TestNavigationObserver navigation_observer(web_contents);
-    ASSERT_TRUE(content::ExecuteScriptWithoutUserGesture(
-        web_contents, "window.location = \"" + kSecondURL.spec() + "\";"));
+    ASSERT_TRUE(content::ExecJs(
+        web_contents, "window.location = \"" + kSecondURL.spec() + "\";",
+        content::EXECUTE_SCRIPT_NO_USER_GESTURE));
     navigation_observer.Wait();
 
     bubble_factory()->ResetCounts();
@@ -547,8 +548,9 @@
   // In response, simulate the website automatically triggering a
   // renderer-initiated cross-origin navigation without user gesture.
   content::TestNavigationObserver navigation_observer(web_contents);
-  ASSERT_TRUE(content::ExecuteScriptWithoutUserGesture(
-      web_contents, "window.location = \"" + kSecondURL.spec() + "\";"));
+  ASSERT_TRUE(content::ExecJs(
+      web_contents, "window.location = \"" + kSecondURL.spec() + "\";",
+      content::EXECUTE_SCRIPT_NO_USER_GESTURE));
   navigation_observer.Wait();
 
   // Request the notification permission again from a different origin.
@@ -605,8 +607,8 @@
   TriggerAndExpectPromptCooldownToBeStillActiveAfterNavigationAction(
       [](content::WebContents* web_contents, const GURL& unused_url) {
         content::TestNavigationObserver navigation_observer(web_contents);
-        EXPECT_TRUE(content::ExecuteScriptWithoutUserGesture(
-            web_contents, "window.location.reload();"));
+        EXPECT_TRUE(content::ExecJs(web_contents, "window.location.reload();",
+                                    content::EXECUTE_SCRIPT_NO_USER_GESTURE));
         navigation_observer.Wait();
       },
       true /* expect_cooldown */);
@@ -619,8 +621,9 @@
   TriggerAndExpectPromptCooldownToBeStillActiveAfterNavigationAction(
       [](content::WebContents* web_contents, const GURL& url) {
         content::TestNavigationObserver navigation_observer(web_contents);
-        EXPECT_TRUE(content::ExecuteScriptWithoutUserGesture(
-            web_contents, "window.location = \"" + url.spec() + "\";"));
+        EXPECT_TRUE(content::ExecJs(web_contents,
+                                    "window.location = \"" + url.spec() + "\";",
+                                    content::EXECUTE_SCRIPT_NO_USER_GESTURE));
         navigation_observer.Wait();
       },
       true /* expect_cooldown */);
diff --git a/chrome/browser/policy/configuration_policy_handler_list_factory.cc b/chrome/browser/policy/configuration_policy_handler_list_factory.cc
index fdf44dd..461ff00 100644
--- a/chrome/browser/policy/configuration_policy_handler_list_factory.cc
+++ b/chrome/browser/policy/configuration_policy_handler_list_factory.cc
@@ -168,6 +168,7 @@
 #include "chrome/browser/ash/policy/handlers/configuration_policy_handler_ash.h"
 #include "chrome/browser/ash/policy/handlers/lacros_availability_policy_handler.h"
 #include "chrome/browser/ash/policy/handlers/lacros_selection_policy_handler.h"
+#include "chrome/browser/ash/policy/reporting/metrics_reporting/metric_reporting_prefs.h"
 #include "chrome/browser/nearby_sharing/common/nearby_share_prefs.h"
 #include "chrome/browser/policy/default_geolocation_policy_handler.h"
 #include "chrome/browser/policy/device_login_screen_geolocation_access_level_policy_handler.h"
@@ -1425,6 +1426,15 @@
   { key::kShowDisplaySizeScreenEnabled,
     ash::prefs::kShowDisplaySizeScreenEnabled,
     base::Value::Type::BOOLEAN },
+  { key::kReportAppInventory,
+    ash::reporting::kReportAppInventory,
+    base::Value::Type::LIST },
+  { key::kReportAppUsage,
+    ash::reporting::kReportAppUsage,
+    base::Value::Type::LIST },
+  { key::kReportAppUsageCollectionRateMs,
+    ash::reporting::kReportAppUsageCollectionRateMs,
+    base::Value::Type::INTEGER },
 #endif // BUILDFLAG(IS_CHROMEOS_ASH)
 
 #if BUILDFLAG(IS_LINUX)
diff --git a/chrome/browser/policy/test/autoplay_policy_browsertest.cc b/chrome/browser/policy/test/autoplay_policy_browsertest.cc
index 9c81ac3e..ff5a5cc 100644
--- a/chrome/browser/policy/test/autoplay_policy_browsertest.cc
+++ b/chrome/browser/policy/test/autoplay_policy_browsertest.cc
@@ -49,7 +49,8 @@
         "\",0)",
         origin2.spec().c_str());
     content::TestNavigationObserver load_observer(GetWebContents());
-    EXPECT_TRUE(ExecuteScriptWithoutUserGesture(GetWebContents(), script));
+    EXPECT_TRUE(ExecJs(GetWebContents(), script,
+                       content::EXECUTE_SCRIPT_NO_USER_GESTURE));
     load_observer.Wait();
   }
 
diff --git a/chrome/browser/prefs/browser_prefs.cc b/chrome/browser/prefs/browser_prefs.cc
index e056747..f0d6d64b 100644
--- a/chrome/browser/prefs/browser_prefs.cc
+++ b/chrome/browser/prefs/browser_prefs.cc
@@ -387,6 +387,7 @@
 #include "chrome/browser/ash/policy/handlers/tpm_auto_update_mode_policy_handler.h"
 #include "chrome/browser/ash/policy/reporting/app_install_event_log_manager_wrapper.h"
 #include "chrome/browser/ash/policy/reporting/arc_app_install_event_logger.h"
+#include "chrome/browser/ash/policy/reporting/metrics_reporting/metric_reporting_prefs.h"
 #include "chrome/browser/ash/policy/scheduled_task_handler/reboot_notifications_scheduler.h"
 #include "chrome/browser/ash/policy/status_collector/device_status_collector.h"
 #include "chrome/browser/ash/policy/status_collector/status_collector.h"
@@ -1681,6 +1682,7 @@
   file_manager::prefs::RegisterProfilePrefs(registry);
   bruschetta::prefs::RegisterProfilePrefs(registry);
   wallpaper_handlers::prefs::RegisterProfilePrefs(registry);
+  ash::reporting::RegisterProfilePrefs(registry);
 #endif  // BUILDFLAG(IS_CHROMEOS_ASH)
 
 #if BUILDFLAG(IS_CHROMEOS_LACROS)
diff --git a/chrome/browser/resources/chromeos/login/BUILD.gn b/chrome/browser/resources/chromeos/login/BUILD.gn
index c2f5faf..9f4e31c2 100644
--- a/chrome/browser/resources/chromeos/login/BUILD.gn
+++ b/chrome/browser/resources/chromeos/login/BUILD.gn
@@ -146,6 +146,7 @@
     "images/encryption_migration.svg",
     "images/enrollment_complete.svg",
     "images/error.svg",
+    "images/gaia_info.svg",
     "images/kids_turn.svg",
     "images/kiosk_enrollment.svg",
     "images/os_sync_consent.svg",
@@ -282,6 +283,7 @@
     "screens/common/error_message.js",
     "screens/common/family_link_notice.js",
     "screens/common/fingerprint_setup.js",
+    "screens/common/gaia_info.js",
     "screens/common/gaia_signin.js",
     "screens/common/gesture_navigation.js",
     "screens/common/guest_tos.js",
diff --git a/chrome/browser/resources/chromeos/login/components/display_manager_types.js b/chrome/browser/resources/chromeos/login/components/display_manager_types.js
index 28a58090..ce50f02 100644
--- a/chrome/browser/resources/chromeos/login/components/display_manager_types.js
+++ b/chrome/browser/resources/chromeos/login/components/display_manager_types.js
@@ -44,6 +44,7 @@
   ENROLLMENT_SUCCESS: 17,
   THEME_SELECTION: 18,
   MARKETING_OPT_IN: 19,
+  GAIA_INFO: 21,
 };
 
 // TODO(crbug.com/1229130) - Refactor/remove these constants.
diff --git a/chrome/browser/resources/chromeos/login/debug/debug.js b/chrome/browser/resources/chromeos/login/debug/debug.js
index 5f8bc8e..a508406 100644
--- a/chrome/browser/resources/chromeos/login/debug/debug.js
+++ b/chrome/browser/resources/chromeos/login/debug/debug.js
@@ -669,6 +669,10 @@
       ],
     },
     {
+      id: 'gaia-info',
+      kind: ScreenKind.NORMAL,
+    },
+    {
       id: 'offline-login',
       kind: ScreenKind.NORMAL,
       states: [
diff --git a/chrome/browser/resources/chromeos/login/images/gaia_info.svg b/chrome/browser/resources/chromeos/login/images/gaia_info.svg
new file mode 100644
index 0000000..11a36c2
--- /dev/null
+++ b/chrome/browser/resources/chromeos/login/images/gaia_info.svg
@@ -0,0 +1,66 @@
+<svg width="520" height="320" viewBox="0 0 520 320" fill="none" xmlns="http://www.w3.org/2000/svg">
+<rect width="520" height="320" fill="white"/>
+<path d="M179.51 156.738H340.214C344.173 156.738 347.388 159.952 347.388 163.911V280.458H172.336V163.911C172.336 159.952 175.551 156.738 179.51 156.738Z" fill="white" stroke="#4285F4" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M182.73 164.412H337.182C338.634 164.412 339.811 165.589 339.811 167.041V270.625H180.101V167.041C180.101 165.589 181.278 164.412 182.73 164.412Z" fill="#D2E3FC"/>
+<path d="M159.727 276.676H360.002C360.795 276.676 361.436 277.316 361.436 278.109V281.361C361.436 282.947 360.149 284.228 358.569 284.228H161.161C159.575 284.228 158.294 282.941 158.294 281.361V278.109C158.294 277.316 158.934 276.676 159.727 276.676Z" fill="white" stroke="#4285F4" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M258.639 162.295C259.312 162.295 259.859 161.749 259.859 161.075C259.859 160.401 259.312 159.855 258.639 159.855C257.965 159.855 257.419 160.401 257.419 161.075C257.419 161.749 257.965 162.295 258.639 162.295Z" fill="#D2E3FC"/>
+<path d="M169.111 126.012L161.393 130.846C160.419 131.456 160.124 132.74 160.734 133.713L165.513 141.344C166.123 142.317 167.407 142.612 168.38 142.002L176.099 137.168C177.072 136.558 177.367 135.275 176.757 134.301L171.978 126.671C171.368 125.697 170.085 125.402 169.111 126.012Z" fill="#EE5FFA"/>
+<path fill-rule="evenodd" clip-rule="evenodd" d="M236.429 50.0549L227.303 43.5767C226.98 43.3449 226.522 43.5767 226.473 43.9793L225.223 55.5327C225.18 55.9414 225.583 56.2281 225.949 56.0512L236.325 50.9699C236.691 50.7869 236.746 50.2806 236.422 50.0488" fill="#EA4335"/>
+<path fill-rule="evenodd" clip-rule="evenodd" d="M400.531 129.349L397.139 125.957C396.743 125.561 396.584 124.975 396.73 124.432L397.969 119.802C398.115 119.259 398.542 118.832 399.085 118.686L403.715 117.448C404.258 117.301 404.843 117.46 405.24 117.856L408.632 121.248C409.028 121.645 409.187 122.23 409.04 122.773L407.802 127.403C407.656 127.946 407.229 128.373 406.686 128.519L402.056 129.758C401.513 129.904 400.927 129.745 400.531 129.349Z" fill="#30E2EA"/>
+<path d="M270.808 87.7834C271.491 87.2344 272.492 87.4052 272.961 88.1433C276.92 94.3775 275.633 102.722 269.735 107.45C263.836 112.171 255.412 111.61 250.19 106.388C249.574 105.772 249.623 104.754 250.306 104.205L270.808 87.7834Z" stroke="#D2E3FC" stroke-width="3" stroke-miterlimit="10"/>
+<path d="M328.179 193.008H191.746C188.842 193.008 186.488 195.363 186.488 198.267V228.754C186.488 231.658 188.842 234.013 191.746 234.013H328.179C331.083 234.013 333.437 231.658 333.437 228.754V198.267C333.437 195.363 331.083 193.008 328.179 193.008Z" fill="white" stroke="#4285F4" stroke-width="3" stroke-miterlimit="10"/>
+<path d="M243.047 219.123C241.889 219.123 240.84 218.858 239.901 218.33C238.962 217.811 238.221 217.104 237.678 216.21C237.146 215.305 236.88 214.314 236.88 213.236C236.88 212.169 237.146 211.187 237.678 210.293C238.221 209.388 238.962 208.671 239.901 208.143C240.84 207.614 241.889 207.35 243.047 207.35C244.195 207.35 245.234 207.614 246.162 208.143C247.101 208.671 247.832 209.383 248.354 210.278C248.886 211.172 249.152 212.148 249.152 213.206C249.152 214.141 248.938 214.908 248.51 215.508C248.093 216.108 247.498 216.408 246.726 216.408C246.329 216.408 245.969 216.311 245.646 216.118C245.333 215.915 245.093 215.64 244.926 215.295C244.435 216.037 243.715 216.408 242.766 216.408C242.202 216.408 241.691 216.276 241.232 216.012C240.772 215.747 240.407 215.381 240.136 214.914C239.865 214.436 239.729 213.897 239.729 213.297C239.729 212.707 239.865 212.174 240.136 211.696C240.407 211.218 240.767 210.847 241.216 210.583C241.675 210.318 242.171 210.186 242.703 210.186C243.089 210.186 243.439 210.262 243.752 210.415C244.065 210.567 244.331 210.776 244.55 211.04H244.644V210.4H245.959V214.273C245.959 214.537 246.032 214.771 246.178 214.975C246.324 215.168 246.538 215.264 246.82 215.264C247.195 215.264 247.462 215.066 247.618 214.67C247.785 214.263 247.869 213.775 247.869 213.206C247.869 212.372 247.655 211.604 247.227 210.903C246.809 210.191 246.23 209.627 245.489 209.21C244.759 208.793 243.945 208.585 243.047 208.585C242.16 208.585 241.352 208.788 240.621 209.195C239.891 209.591 239.311 210.145 238.884 210.857C238.456 211.569 238.242 212.362 238.242 213.236C238.242 214.11 238.461 214.903 238.899 215.615C239.338 216.327 239.922 216.886 240.652 217.293C241.393 217.689 242.192 217.887 243.047 217.887C243.809 217.887 244.487 217.735 245.082 217.43L245.552 218.604C244.811 218.95 243.976 219.123 243.047 219.123ZM242.875 215.097C243.355 215.097 243.762 214.934 244.096 214.609C244.44 214.283 244.613 213.846 244.613 213.297C244.613 212.748 244.44 212.311 244.096 211.986C243.762 211.66 243.355 211.498 242.875 211.498C242.385 211.498 241.967 211.66 241.623 211.986C241.289 212.311 241.122 212.748 241.122 213.297C241.122 213.846 241.289 214.283 241.623 214.609C241.967 214.934 242.385 215.097 242.875 215.097Z" fill="#4285F4"/>
+<path d="M254.423 221.41C253.453 221.41 252.649 221.186 252.013 220.739C251.386 220.302 250.964 219.788 250.745 219.199L252.373 218.543C252.529 218.96 252.79 219.29 253.155 219.534C253.52 219.778 253.943 219.9 254.423 219.9C255.143 219.9 255.707 219.687 256.114 219.26C256.531 218.833 256.74 218.243 256.74 217.491V216.835H256.646C256.395 217.191 256.051 217.476 255.613 217.689C255.185 217.903 254.694 218.009 254.141 218.009C253.494 218.009 252.889 217.842 252.326 217.506C251.773 217.171 251.324 216.703 250.979 216.103C250.646 215.493 250.479 214.807 250.479 214.044C250.479 213.282 250.646 212.601 250.979 212.001C251.324 211.391 251.773 210.918 252.326 210.583C252.889 210.237 253.494 210.064 254.141 210.064C254.694 210.064 255.185 210.171 255.613 210.384C256.051 210.598 256.395 210.882 256.646 211.238H256.74V210.308H258.383V217.445C258.383 218.279 258.211 218.995 257.867 219.595C257.533 220.195 257.063 220.648 256.458 220.953C255.863 221.258 255.185 221.41 254.423 221.41ZM254.47 216.484C254.877 216.484 255.253 216.388 255.597 216.195C255.941 216.001 256.218 215.722 256.427 215.356C256.635 214.98 256.74 214.542 256.74 214.044C256.74 213.536 256.635 213.099 256.427 212.733C256.218 212.357 255.941 212.072 255.597 211.879C255.253 211.686 254.877 211.589 254.47 211.589C254.063 211.589 253.687 211.691 253.343 211.894C252.999 212.087 252.722 212.372 252.513 212.748C252.305 213.114 252.2 213.546 252.2 214.044C252.2 214.542 252.305 214.98 252.513 215.356C252.722 215.722 252.999 216.001 253.343 216.195C253.687 216.388 254.063 216.484 254.47 216.484Z" fill="#4285F4"/>
+<path d="M260.318 210.308H261.946V211.315H262.04C262.269 210.949 262.603 210.649 263.042 210.415C263.48 210.181 263.939 210.064 264.419 210.064C264.983 210.064 265.473 210.191 265.89 210.445C266.308 210.699 266.616 211.04 266.814 211.467C267.085 211.04 267.445 210.699 267.894 210.445C268.353 210.191 268.875 210.064 269.459 210.064C270.367 210.064 271.056 210.339 271.526 210.888C271.995 211.426 272.23 212.163 272.23 213.099V217.872H270.508V213.373C270.508 212.743 270.378 212.291 270.117 212.016C269.866 211.742 269.491 211.604 268.99 211.604C268.437 211.604 267.988 211.828 267.644 212.275C267.31 212.712 267.143 213.246 267.143 213.877V217.872H265.421V213.373C265.421 212.743 265.29 212.291 265.029 212.016C264.769 211.742 264.377 211.604 263.855 211.604C263.313 211.604 262.875 211.828 262.541 212.275C262.207 212.712 262.04 213.246 262.04 213.877V217.872H260.318V210.308Z" fill="#4285F4"/>
+<path d="M276.503 218.116C275.95 218.116 275.449 218.009 275 217.796C274.562 217.572 274.212 217.267 273.951 216.881C273.701 216.484 273.576 216.037 273.576 215.539C273.576 214.746 273.878 214.121 274.484 213.663C275.099 213.206 275.877 212.977 276.816 212.977C277.64 212.977 278.345 213.114 278.929 213.389V213.068C278.929 212.611 278.741 212.235 278.366 211.94C278 211.635 277.557 211.482 277.035 211.482C276.242 211.482 275.605 211.797 275.125 212.428L273.81 211.543C274.176 211.065 274.63 210.699 275.172 210.445C275.725 210.191 276.346 210.064 277.035 210.064C278.173 210.064 279.049 210.344 279.665 210.903C280.291 211.452 280.604 212.23 280.604 213.236V217.872H278.929V216.942H278.835C278.585 217.287 278.266 217.572 277.88 217.796C277.494 218.009 277.035 218.116 276.503 218.116ZM276.785 216.728C277.171 216.728 277.526 216.637 277.849 216.454C278.183 216.261 278.444 216.012 278.632 215.707C278.83 215.391 278.929 215.056 278.929 214.7C278.376 214.405 277.786 214.258 277.16 214.258C276.586 214.258 276.138 214.38 275.814 214.624C275.491 214.868 275.329 215.178 275.329 215.554C275.329 215.9 275.47 216.184 275.751 216.408C276.044 216.622 276.388 216.728 276.785 216.728Z" fill="#4285F4"/>
+<path d="M283.408 209.027C283.084 209.027 282.808 208.92 282.578 208.707C282.359 208.483 282.25 208.214 282.25 207.899C282.25 207.583 282.359 207.319 282.578 207.106C282.808 206.882 283.084 206.77 283.408 206.77C283.731 206.77 284.003 206.882 284.222 207.106C284.451 207.319 284.566 207.583 284.566 207.899C284.566 208.214 284.451 208.483 284.222 208.707C284.003 208.92 283.731 209.027 283.408 209.027ZM282.547 210.308H284.269V217.872H282.547V210.308Z" fill="#4285F4"/>
+<path d="M286.335 206.953H288.057V217.872H286.335V206.953Z" fill="#4285F4"/>
+<path d="M291.084 217.964C290.75 217.964 290.463 217.852 290.223 217.628C289.993 217.394 289.879 217.115 289.879 216.789C289.879 216.464 289.993 216.189 290.223 215.966C290.463 215.742 290.75 215.63 291.084 215.63C291.418 215.63 291.7 215.742 291.929 215.966C292.159 216.189 292.273 216.464 292.273 216.789C292.273 217.115 292.159 217.394 291.929 217.628C291.7 217.852 291.418 217.964 291.084 217.964Z" fill="#4285F4"/>
+<path d="M297.685 218.116C296.913 218.116 296.219 217.943 295.604 217.598C294.988 217.252 294.508 216.774 294.163 216.164C293.819 215.544 293.647 214.853 293.647 214.09C293.647 213.317 293.819 212.626 294.163 212.016C294.508 211.406 294.988 210.928 295.604 210.583C296.219 210.237 296.913 210.064 297.685 210.064C298.541 210.064 299.266 210.262 299.861 210.659C300.466 211.045 300.905 211.559 301.176 212.199L299.595 212.84C299.219 212.016 298.562 211.604 297.623 211.604C297.226 211.604 296.856 211.711 296.511 211.925C296.167 212.128 295.891 212.418 295.682 212.794C295.473 213.17 295.369 213.602 295.369 214.09C295.369 214.578 295.473 215.01 295.682 215.386C295.891 215.762 296.167 216.057 296.511 216.271C296.856 216.474 297.226 216.576 297.623 216.576C298.103 216.576 298.52 216.469 298.875 216.256C299.23 216.032 299.501 215.717 299.689 215.31L301.239 215.966C300.936 216.606 300.482 217.125 299.877 217.521C299.272 217.918 298.541 218.116 297.685 218.116Z" fill="#4285F4"/>
+<path d="M306.185 218.116C305.413 218.116 304.719 217.943 304.103 217.598C303.487 217.242 303.002 216.759 302.647 216.149C302.303 215.539 302.131 214.853 302.131 214.09C302.131 213.328 302.303 212.641 302.647 212.031C303.002 211.421 303.487 210.943 304.103 210.598C304.719 210.242 305.413 210.064 306.185 210.064C306.968 210.064 307.667 210.242 308.282 210.598C308.898 210.943 309.378 211.421 309.723 212.031C310.067 212.641 310.239 213.328 310.239 214.09C310.239 214.853 310.067 215.539 309.723 216.149C309.378 216.759 308.898 217.242 308.282 217.598C307.667 217.943 306.968 218.116 306.185 218.116ZM306.185 216.576C306.602 216.576 306.988 216.474 307.343 216.271C307.698 216.067 307.985 215.778 308.204 215.402C308.423 215.025 308.533 214.588 308.533 214.09C308.533 213.582 308.423 213.139 308.204 212.763C307.985 212.387 307.698 212.102 307.343 211.909C306.988 211.706 306.602 211.604 306.185 211.604C305.778 211.604 305.392 211.706 305.027 211.909C304.672 212.102 304.385 212.387 304.166 212.763C303.957 213.139 303.853 213.582 303.853 214.09C303.853 214.588 303.962 215.025 304.181 215.402C304.4 215.778 304.687 216.067 305.042 216.271C305.397 216.474 305.778 216.576 306.185 216.576Z" fill="#4285F4"/>
+<path d="M311.588 210.308H313.216V211.315H313.31C313.539 210.949 313.873 210.649 314.312 210.415C314.75 210.181 315.209 210.064 315.689 210.064C316.253 210.064 316.743 210.191 317.16 210.445C317.578 210.699 317.886 211.04 318.084 211.467C318.355 211.04 318.715 210.699 319.164 210.445C319.623 210.191 320.145 210.064 320.729 210.064C321.637 210.064 322.326 210.339 322.796 210.888C323.265 211.426 323.5 212.163 323.5 213.099V217.872H321.778V213.373C321.778 212.743 321.648 212.291 321.387 212.016C321.136 211.742 320.761 211.604 320.26 211.604C319.707 211.604 319.258 211.828 318.914 212.275C318.58 212.712 318.413 213.246 318.413 213.877V217.872H316.691V213.373C316.691 212.743 316.56 212.291 316.3 212.016C316.039 211.742 315.647 211.604 315.126 211.604C314.583 211.604 314.145 211.828 313.811 212.275C313.477 212.712 313.31 213.246 313.31 213.877V217.872H311.588V210.308Z" fill="#4285F4"/>
+<path d="M320.45 256.18C330.22 256.18 338.14 248.26 338.14 238.49C338.14 228.72 330.22 220.8 320.45 220.8C310.68 220.8 302.76 228.72 302.76 238.49C302.76 248.26 310.68 256.18 320.45 256.18Z" fill="#34A853"/>
+<path fill-rule="evenodd" clip-rule="evenodd" d="M332.672 231.753C333.254 232.334 333.256 233.276 332.674 233.858L318.753 247.785C318.467 248.072 318.075 248.23 317.669 248.221C317.263 248.213 316.878 248.04 316.602 247.742L310.464 241.098C309.906 240.493 309.944 239.552 310.549 238.994C311.154 238.437 312.097 238.476 312.655 239.08L317.74 244.584L330.564 231.755C331.145 231.173 332.089 231.172 332.672 231.753Z" fill="white"/>
+<rect x="197.84" y="206.77" width="37.21" height="11.59" rx="4" fill="#8AB4F8"/>
+<path d="M355.684 104.247C354.567 102.515 352.591 101.496 350.529 101.6C346.796 101.783 332.998 106.218 332.998 106.218C332.998 106.218 319.199 110.652 316.058 112.684C314.326 113.8 313.307 115.776 313.411 117.838C313.594 121.571 315.899 128.751 315.899 128.751C315.899 128.751 318.211 135.931 320.236 139.072C321.353 140.805 323.329 141.823 325.391 141.72C329.124 141.537 342.922 137.102 342.922 137.102C342.922 137.102 356.721 132.667 359.862 130.636C361.594 129.52 362.613 127.543 362.509 125.481C362.326 121.748 360.021 114.569 360.021 114.569C360.021 114.569 357.709 107.389 355.684 104.247Z" fill="#FF0000"/>
+<path d="M335.676 129.697L345.009 119.394L331.418 116.466L335.676 129.697Z" fill="white"/>
+<path d="M265.959 50.6283L271.985 59.3696L281.013 64.9511L285.887 44.7601L305.816 38.8797C299.808 33.1762 291.011 30.7423 282.496 33.2494C273.98 35.7565 267.911 42.5763 265.959 50.6222V50.6283Z" fill="url(#paint0_linear_6_610)"/>
+<path d="M296.062 79.2739L300.619 69.6847L300.936 59.0768L281.013 64.9511L265.959 50.6283C264.025 58.6803 266.312 67.5192 272.748 73.6375C279.183 79.7558 288.12 81.6041 296.068 79.2739H296.062Z" fill="url(#paint1_linear_6_610)"/>
+<path d="M305.822 38.8797L285.893 44.754L300.942 59.0768L296.068 79.2739C304.01 76.9254 310.519 70.5204 312.599 61.8889C314.685 53.2574 311.812 44.5954 305.816 38.8797L305.822 38.8797Z" fill="url(#paint2_linear_6_610)"/>
+<path d="M292.676 67.7544C299.029 65.881 302.66 59.2123 300.787 52.8594C298.913 46.5066 292.245 42.8752 285.892 44.7486C279.539 46.6219 275.908 53.2906 277.781 59.6435C279.654 65.9964 286.323 69.6277 292.676 67.7544Z" fill="white"/>
+<path d="M292.041 65.6012C297.201 64.0795 300.151 58.6598 298.628 53.4961C297.105 48.3324 291.687 45.3799 286.527 46.9017C281.366 48.4235 278.417 53.8431 279.94 59.0068C281.463 64.1706 286.881 67.123 292.041 65.6012Z" fill="#1A73E8"/>
+<path d="M131.807 117.96L134.674 124.817L140.726 130.002C141.409 129.501 141.958 128.818 142.306 128.013L146.319 118.771C146.667 117.966 146.789 117.1 146.691 116.258L138.835 115.648L131.807 117.96Z" fill="#EA4335"/>
+<path d="M127.287 90.0526C126.513 89.7171 125.646 89.5768 124.774 89.6805L114.764 90.8212C113.898 90.9188 113.08 91.2543 112.403 91.7545L115.386 98.7695L121.291 103.759L121.315 103.796L125.97 97.5312L127.281 90.0526H127.287Z" fill="#188038"/>
+<path d="M114.294 119.961L106.419 119.076L99.4104 121.663C99.508 122.505 99.8191 123.316 100.338 124.024L106.334 132.124C106.858 132.826 107.548 133.369 108.322 133.704L113.007 127.635L114.294 119.967V119.961Z" fill="#1967D2"/>
+<path d="M137.517 102.771L129.276 91.6386C128.751 90.931 128.062 90.3942 127.287 90.0587L121.315 103.796L131.801 117.966L146.685 116.264C146.588 115.423 146.277 114.605 145.752 113.904L137.511 102.771H137.517Z" fill="#FBBC04"/>
+<path d="M131.807 117.96L114.294 119.961L108.322 133.698C109.097 134.034 109.963 134.174 110.836 134.07L138.359 130.929C139.231 130.831 140.042 130.502 140.72 129.995L131.807 117.954V117.96Z" fill="#4285F4"/>
+<path d="M121.322 103.796L112.409 91.7545C111.726 92.2547 111.183 92.9318 110.83 93.737L99.7825 119.156C99.4348 119.961 99.3128 120.821 99.4104 121.657L114.294 119.955L121.322 103.79V103.796Z" fill="#34A853"/>
+<path d="M400.604 146.185C406.789 148.552 409.888 155.487 407.521 161.673L407.131 162.691L385.744 154.505C385.183 154.292 384.903 153.657 385.116 153.096C387.483 146.911 394.419 143.812 400.604 146.179V146.185Z" fill="#FBBC04"/>
+<path d="M423.638 156.164C421.271 162.35 414.335 165.449 408.15 163.082L407.131 162.691L415.317 141.305C415.531 140.744 416.165 140.463 416.726 140.676C422.912 143.043 426.01 149.979 423.644 156.164H423.638Z" fill="#EA4335"/>
+<path d="M413.658 179.198C407.473 176.831 404.374 169.895 406.741 163.71L407.131 162.691L428.518 170.878C429.079 171.091 429.359 171.725 429.146 172.287C426.779 178.472 419.843 181.571 413.658 179.204V179.198Z" fill="#4285F4"/>
+<path d="M390.618 169.218C392.985 163.033 399.921 159.934 406.106 162.301L407.125 162.691L398.939 184.078C398.725 184.639 398.091 184.92 397.53 184.706C391.344 182.339 388.245 175.404 390.612 169.218H390.618Z" fill="#34A853"/>
+<path d="M77.5969 182.937L79.2988 186.951L85.7953 189.458L92.5297 190.233L90.6692 183.047L87.0031 177.179L83.3004 176.258C79.372 175.288 76.0231 179.21 77.603 182.931" fill="#C5221F"/>
+<path d="M111.098 166.974L112.794 173.775L116.625 180.028L120.523 175.495L123.475 168.242L121.773 164.229C120.199 160.508 115.045 160.178 113.013 163.673L111.092 166.974H111.098Z" fill="#FBBC04"/>
+<path d="M92.7248 210.278L99.7521 207.301L92.5236 190.227L79.2927 186.945L88.4305 208.533C89.1381 210.198 91.0535 210.973 92.7188 210.271" fill="#4285F4"/>
+<path d="M123.853 197.101L130.88 194.125C132.546 193.417 133.32 191.502 132.619 189.836L123.481 168.249L116.631 180.034L123.859 197.108L123.853 197.101Z" fill="#34A853"/>
+<path d="M102.875 181.113L86.9969 177.179L92.5297 190.233L108.402 194.167L116.625 180.028L111.098 166.974L102.875 181.113Z" fill="#EA4335"/>
+<path d="M201.144 90.9115L200.041 94.5673L219.879 100.553L220.982 96.8973L201.144 90.9115Z" fill="white"/>
+<path d="M199.251 97.1707L198.148 100.827L212.766 105.237L213.869 101.581L199.251 97.1707Z" fill="white"/>
+<path d="M219.898 83.4707L225.839 86.5573L231.384 86.9355L223.362 71.9844L220.135 76.9498L219.898 83.4707Z" fill="#1967D2"/>
+<path d="M203.031 84.6441L201.927 88.2999L221.766 94.2856L222.869 90.6298L203.031 84.6441Z" fill="white"/>
+<path d="M219.898 83.4707L223.362 71.9844L204.568 66.3175C202.836 65.7929 201.012 66.775 200.493 68.5074L188.525 108.176C188.001 109.908 188.983 111.732 190.715 112.251L217.86 120.443C219.593 120.967 221.416 119.985 221.935 118.253L231.384 86.9355L219.898 83.4707ZM212.395 104.558L198.822 100.465L199.609 97.8545L213.181 101.948L212.395 104.558ZM219.507 99.8675L200.713 94.2006L201.5 91.5898L220.294 97.2567L219.507 99.8675ZM221.398 93.6028L202.604 87.9359L203.391 85.3251L222.185 90.992L221.398 93.6028Z" fill="#4285F4"/>
+<defs>
+<linearGradient id="paint0_linear_6_610" x1="248.928" y1="63.0307" x2="297.708" y2="51.2345" gradientUnits="userSpaceOnUse">
+<stop stop-color="#D93025"/>
+<stop offset="1" stop-color="#EA4335"/>
+</linearGradient>
+<linearGradient id="paint1_linear_6_610" x1="277.568" y1="67.748" x2="230.527" y2="30.5607" gradientUnits="userSpaceOnUse">
+<stop stop-color="#1E8E3E"/>
+<stop offset="1" stop-color="#34A853"/>
+</linearGradient>
+<linearGradient id="paint2_linear_6_610" x1="254.314" y1="39.969" x2="246.305" y2="88.9253" gradientUnits="userSpaceOnUse">
+<stop stop-color="#FBBC04"/>
+<stop offset="1" stop-color="#FCC934"/>
+</linearGradient>
+</defs>
+</svg>
diff --git a/chrome/browser/resources/chromeos/login/screens.js b/chrome/browser/resources/chromeos/login/screens.js
index 933b0730..e17e56e 100644
--- a/chrome/browser/resources/chromeos/login/screens.js
+++ b/chrome/browser/resources/chromeos/login/screens.js
@@ -21,6 +21,7 @@
 import './screens/common/error_message.js';
 import './screens/common/family_link_notice.js';
 import './screens/common/fingerprint_setup.js';
+import './screens/common/gaia_info.js';
 import './screens/common/gaia_signin.js';
 import './screens/common/gesture_navigation.js';
 import './screens/common/guest_tos.js';
@@ -101,6 +102,11 @@
   {tag: 'error-message-element', id: 'error-message'},
   {tag: 'family-link-notice-element', id: 'family-link-notice'},
   {tag: 'fingerprint-setup-element', id: 'fingerprint-setup'},
+  {
+    tag: 'gaia-info-element',
+    id: 'gaia-info',
+    condition: 'isOobeGaiaInfoScreenEnabled',
+  },
   {tag: 'gaia-signin-element', id: 'gaia-signin'},
   {tag: 'gesture-navigation-element', id: 'gesture-navigation'},
   {tag: 'guest-tos-element', id: 'guest-tos'},
diff --git a/chrome/browser/resources/chromeos/login/screens/common/BUILD.gn b/chrome/browser/resources/chromeos/login/screens/common/BUILD.gn
index 404e8fe..058db03f 100644
--- a/chrome/browser/resources/chromeos/login/screens/common/BUILD.gn
+++ b/chrome/browser/resources/chromeos/login/screens/common/BUILD.gn
@@ -42,6 +42,7 @@
     ":error_message",
     ":family_link_notice",
     ":fingerprint_setup",
+    ":gaia_info",
     ":gaia_signin",
     ":gesture_navigation",
     ":guest_tos",
@@ -521,6 +522,17 @@
   extra_deps = [ ":web_components" ]
 }
 
+js_library("gaia_info") {
+  sources = [ "$root_gen_dir/chrome/browser/resources/chromeos/login/screens/common/gaia_info.js" ]
+  deps = [
+    "../../components/behaviors:login_screen_behavior",
+    "../../components/behaviors:oobe_dialog_host_behavior",
+    "../../components/behaviors:oobe_i18n_behavior",
+    "../../components/dialogs:oobe_adaptive_dialog",
+  ]
+  extra_deps = [ ":web_components" ]
+}
+
 js_library("recommend_apps") {
   sources = [ "$root_gen_dir/chrome/browser/resources/chromeos/login/screens/common/recommend_apps.js" ]
   deps = [
@@ -698,6 +710,7 @@
     "error_message.js",
     "family_link_notice.js",
     "fingerprint_setup.js",
+    "gaia_info.js",
     "gaia_signin.js",
     "gesture_navigation.js",
     "guest_tos.js",
diff --git a/chrome/browser/resources/chromeos/login/screens/common/gaia_info.html b/chrome/browser/resources/chromeos/login/screens/common/gaia_info.html
new file mode 100644
index 0000000..0df07e6
--- /dev/null
+++ b/chrome/browser/resources/chromeos/login/screens/common/gaia_info.html
@@ -0,0 +1,33 @@
+<!--
+Copyright 2023 The Chromium Authors
+Use of this source code is governed by a BSD-style license that can be
+found in the LICENSE file.
+-->
+<style include="oobe-dialog-host-styles">
+</style>
+
+<oobe-adaptive-dialog id="gaiaInfoDialog" role="dialog"
+    for-step="overview" aria-live="polite">
+  <iron-icon slot="icon" icon="oobe-32:googleg"></iron-icon>
+  <h1 slot="title" id="gaia-info-title">
+    [[i18nDynamic(locale, 'gaiaInfoScreenTitle')]]
+  </h1>
+  <span inner-h-t-m-l=
+      "[[i18nAdvancedDynamic(locale, 'gaiaInfoScreenDescription')]]"
+      slot="subtitle">
+  </span>
+  <div slot="content" class="flex layout vertical center center-justified">
+    <img src="/images/gaia_info.svg" id="gaiaInfoImage"
+        class="oobe-illustration" aria-hidden="true">
+  </div>
+  <div slot="back-navigation">
+    <oobe-back-button id="backButton" on-click="onBackClicked_"
+        hidden="[[!isBackButtonVisible_]]">
+    </oobe-back-button>
+  </div>
+  <div slot="bottom-buttons">
+    <oobe-next-button id="nextButton" class="focus-on-show"
+      on-click="onNextClicked_">
+    </oobe-next-button>
+  </div>
+</oobe-adaptive-dialog>
diff --git a/chrome/browser/resources/chromeos/login/screens/common/gaia_info.js b/chrome/browser/resources/chromeos/login/screens/common/gaia_info.js
new file mode 100644
index 0000000..770d72f
--- /dev/null
+++ b/chrome/browser/resources/chromeos/login/screens/common/gaia_info.js
@@ -0,0 +1,85 @@
+// 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 '../../components/common_styles/oobe_common_styles.css.js';
+import '../../components/common_styles/oobe_dialog_host_styles.css.js';
+import '../../components/dialogs/oobe_adaptive_dialog.js';
+
+import {html, mixinBehaviors, PolymerElement} from '//resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+
+import {LoginScreenBehavior, LoginScreenBehaviorInterface} from '../../components/behaviors/login_screen_behavior.js';
+import {MultiStepBehavior, MultiStepBehaviorInterface} from '../../components/behaviors/multi_step_behavior.js';
+import {OobeI18nBehavior, OobeI18nBehaviorInterface} from '../../components/behaviors/oobe_i18n_behavior.js';
+import {OOBE_UI_STATE} from '../../components/display_manager_types.js';
+
+/**
+ * @constructor
+ * @extends {PolymerElement}
+ * @implements {LoginScreenBehaviorInterface}
+ * @implements {OobeI18nBehaviorInterface}
+ * @implements {MultiStepBehaviorInterface}
+ */
+const GaiaInfoScreenElementBase = mixinBehaviors(
+    [OobeI18nBehavior, LoginScreenBehavior, MultiStepBehavior], PolymerElement);
+
+/**
+ * @enum {string}
+ */
+const GaiaInfoStep = {
+  OVERVIEW: 'overview',
+};
+
+/**
+ * Available user actions.
+ * @enum {string}
+ */
+const UserAction = {
+  BACK: 'back',
+  NEXT: 'next',
+};
+
+/**
+ * @polymer
+ */
+class GaiaInfoScreen extends GaiaInfoScreenElementBase {
+  static get is() {
+    return 'gaia-info-element';
+  }
+
+  static get template() {
+    return html`{__html_template__}`;
+  }
+
+  static get properties() {
+    return {};
+  }
+
+  get UI_STEPS() {
+    return GaiaInfoStep;
+  }
+
+  defaultUIStep() {
+    return GaiaInfoStep.OVERVIEW;
+  }
+
+  /** @override */
+  ready() {
+    super.ready();
+    this.initializeLoginScreen('GaiaInfoScreen');
+  }
+
+  getOobeUIInitialState() {
+    return OOBE_UI_STATE.GAIA_INFO;
+  }
+
+  onNextClicked_() {
+    this.userActed(UserAction.NEXT);
+  }
+
+  onBackClicked_() {
+    this.userActed(UserAction.BACK);
+  }
+}
+
+customElements.define(GaiaInfoScreen.is, GaiaInfoScreen);
diff --git a/chrome/browser/resources/chromeos/login/test_api/test_api.js b/chrome/browser/resources/chromeos/login/test_api/test_api.js
index 4d23d0e..a0b0704 100644
--- a/chrome/browser/resources/chromeos/login/test_api/test_api.js
+++ b/chrome/browser/resources/chromeos/login/test_api/test_api.js
@@ -932,6 +932,13 @@
   }
 }
 
+class GaiaInfoScreenTester extends ScreenElementApi {
+  constructor() {
+    super('gaia-info');
+    this.nextButton = new PolymerElementApi(this, '#nextButton');
+  }
+}
+
 export class OobeApiProvider {
   constructor() {
     this.screens = {
@@ -958,6 +965,7 @@
       ConsolidatedConsentScreen: new ConsolidatedConsentScreenTester(),
       SmartPrivacyProtectionScreen: new SmartPrivacyProtectionScreenTester(),
       CryptohomeRecoverySetupScreen: new CryptohomeRecoverySetupScreenTester(),
+      GaiaInfoScreen: new GaiaInfoScreenTester(),
     };
 
     this.loginWithPin = function(username, pin) {
diff --git a/chrome/browser/resources/feedback/css/logs_map_page.css b/chrome/browser/resources/feedback/css/logs_map_page.css
index 817d932..f5ba12a 100644
--- a/chrome/browser/resources/feedback/css/logs_map_page.css
+++ b/chrome/browser/resources/feedback/css/logs_map_page.css
@@ -10,6 +10,11 @@
 
 html:has(body.jelly-enabled) {
   color: var(--cros-sys-on_surface);
+  font: var(--cros-body-2-font);
+}
+
+html:has(body.jelly-enabled) button {
+  font: var(--cros-button-2-font);
 }
 
 html:has(body.jelly-enabled) #header,
@@ -25,6 +30,22 @@
   background-color: var(--cros-sys-on_primary);
 }
 
+html:has(body.jelly-enabled) #tableTitle {
+  font: var(--cros-headline-1-font);
+}
+
+html:has(body.jelly-enabled) #title {
+  font: var(--cros-title-1-font);
+}
+
+html:has(body.jelly-enabled) #description {
+  font: var(--cros-annotation-2-font);
+}
+
+html:has(body.jelly-enabled) .list td {
+  font: var(--cros-body-2-font);
+}
+
 html:has(body.jelly-enabled) tr > *:nth-child(1),
 html:has(body.jelly-enabled) tr > *:nth-child(2) {
   border-inline-end: 1px solid var(--feedback-separator-color);
diff --git a/chrome/browser/resources/feedback/html/sys_info.html b/chrome/browser/resources/feedback/html/sys_info.html
index 1a81a05d..cf5aa65 100644
--- a/chrome/browser/resources/feedback/html/sys_info.html
+++ b/chrome/browser/resources/feedback/html/sys_info.html
@@ -9,6 +9,7 @@
     <link rel="stylesheet" href="../css/about_sys.css">
     <if expr="chromeos_ash">
       <link rel="stylesheet" href="//theme/colors.css?sets=sys">
+      <link rel="stylesheet" href="//theme/typography.css">
     </if>
     <link rel="stylesheet" href="../css/logs_map_page.css">
     <link rel="stylesheet" href="../css/feedback_shared_styles.css">
diff --git a/chrome/browser/resources/new_tab_page/modules/history_clusters_v2/history_clusters_v2.gni b/chrome/browser/resources/new_tab_page/modules/history_clusters_v2/history_clusters_v2.gni
index dae16964..4708c4d 100644
--- a/chrome/browser/resources/new_tab_page/modules/history_clusters_v2/history_clusters_v2.gni
+++ b/chrome/browser/resources/new_tab_page/modules/history_clusters_v2/history_clusters_v2.gni
@@ -3,5 +3,7 @@
 # found in the LICENSE file.
 
 # List of files that should be passed to html_to_wrapper().
-history_clusters_v2_web_component_files =
-    [ "modules/history_clusters_v2/module.ts" ]
+history_clusters_v2_web_component_files = [
+  "modules/history_clusters_v2/module.ts",
+  "modules/history_clusters_v2/module_header.ts",
+]
diff --git a/chrome/browser/resources/new_tab_page/modules/history_clusters_v2/module.html b/chrome/browser/resources/new_tab_page/modules/history_clusters_v2/module.html
index e2fe0e7..f9342b7 100644
--- a/chrome/browser/resources/new_tab_page/modules/history_clusters_v2/module.html
+++ b/chrome/browser/resources/new_tab_page/modules/history_clusters_v2/module.html
@@ -1,9 +1,22 @@
-<style>
+<style include="cr-icons">
   :host {
     height: 500px;
     width: 100%;
   }
 
+  cr-icon-button {
+    --cr-icon-button-icon-size: 20px;
+    --cr-icon-button-fill-color: var(--color-new-tab-page-primary-foreground);
+    --cr-icon-button-hover-background-color:
+        var(--color-new-tab-page-control-background-hovered);
+    margin-inline-end: -4px;
+    margin-inline-start: 0;
+  }
+
+  #doneButton {
+    --cr-icon-image: url(chrome://resources/images/icon_checkmark.svg);
+  }
+
   ntp-module-header::part(title) {
     overflow: hidden;
     text-overflow: ellipsis;
@@ -11,28 +24,23 @@
   }
 </style>
 <div id="content">
-    <ntp-module-header
+    <ntp-module-header-v2
       disable-text="[[i18nRecursive('',
                                   'modulesDisableButtonText',
                                   'modulesJourneyDisable')]]"
       dismiss-text="[[i18n('modulesDismissButtonText', cluster.label)]]"
-      show-dismiss-button
       on-disable-button-click="onDisableButtonClick_"
       on-dismiss-button-click="onDismissButtonClick_"
-      on-info-button-click="onInfoButtonClick_"
-      icon-src="chrome://resources/images/icon_journeys.svg">
+      on-info-button-click="onInfoButtonClick_">
     [[cluster.label]]
+    <cr-icon-button id="doneButton" slot="title-actions">
+    </cr-icon-button>
     <button id="openAllInTabGroupButton" class="dropdown-item"
         on-click="onOpenAllInTabGroupClick_"
         slot="action-menu-items" type="button">
       [[i18n('modulesJourneysOpenAllInNewTabGroupButtonText')]]
     </button>
-    <button id="openInfoDialogButton" class="dropdown-item"
-        on-click="onInfoButtonClick_"
-        slot="action-menu-items" type="button">
-      [[i18n('moduleInfoButtonTitle')]]
-    </button>
-  </ntp-module-header>
+  </ntp-module-header-v2>
 </div>
 <cr-lazy-render id="infoDialogRender">
   <template>
diff --git a/chrome/browser/resources/new_tab_page/modules/history_clusters_v2/module.ts b/chrome/browser/resources/new_tab_page/modules/history_clusters_v2/module.ts
index 4d90e011..80e8bb4 100644
--- a/chrome/browser/resources/new_tab_page/modules/history_clusters_v2/module.ts
+++ b/chrome/browser/resources/new_tab_page/modules/history_clusters_v2/module.ts
@@ -2,6 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+import './module_header.js';
+
 import {CrLazyRenderElement} from 'chrome://resources/cr_elements/cr_lazy_render/cr_lazy_render.js';
 import {assert} from 'chrome://resources/js/assert_ts.js';
 import {PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
diff --git a/chrome/browser/resources/new_tab_page/modules/history_clusters_v2/module_header.html b/chrome/browser/resources/new_tab_page/modules/history_clusters_v2/module_header.html
new file mode 100644
index 0000000..82535976
--- /dev/null
+++ b/chrome/browser/resources/new_tab_page/modules/history_clusters_v2/module_header.html
@@ -0,0 +1,98 @@
+<style include="cr-icons">
+  :host {
+    background: var(--color-new-tab-page-module-background);
+    border: solid var(--color-new-tab-page-border) 1px;
+    border-radius: var(--ntp-module-border-radius);
+    display: flex;
+    flex-direction: column;
+    padding: 16px;
+  }
+
+  #titleContainer {
+    align-items: center;
+    display: flex;
+    height: 20px;
+  }
+
+  #title {
+    color: var(--color-new-tab-page-primary-foreground);
+    font-size: 15px;
+    font-weight: normal;
+  }
+
+  #headerSpacer {
+    flex-grow: 1;
+  }
+
+  cr-action-menu {
+    --cr-menu-shadow: var(--ntp-menu-shadow);
+  }
+
+  cr-icon-button {
+    --cr-icon-button-icon-size: 20px;
+    --cr-icon-button-fill-color: var(--color-new-tab-page-primary-foreground);
+    --cr-icon-button-hover-background-color:
+        var(--color-new-tab-page-control-background-hovered);
+    margin-inline-end: -4px;
+    margin-inline-start: 0;
+  }
+
+  #menuButton {
+    margin-inline-end: -10px;
+  }
+
+  #menuButton {
+    background-color: var(--color-new-tab-page-module-scroll-button);
+    height: 18px;
+    margin: 0;
+    width: 18px;
+  }
+
+  #menuButton:hover {
+    background-color: var(--color-new-tab-page-module-scroll-button-hover);
+  }
+
+  #label {
+    -webkit-line-clamp: 2;
+    display: flex;
+    height: 48px;
+    line-height: 24px;
+    margin: 36px 8px 0 0;
+  }
+
+  #label span {
+    align-self: flex-end;
+    font-size: 24px;
+  }
+</style>
+<div id="titleContainer">
+  <h2 id="title">[[i18n('modulesJourneysResumeJourney', '')]]</h2>
+  <div id="headerSpacer"></div>
+  <slot name="title-actions"></slot>
+  <cr-icon-button id="menuButton" title="$i18n{moreActions}"
+      class="icon-more-vert" on-click="onMenuButtonClick_">
+  </cr-icon-button>
+</div>
+<div id="label">
+  <span><slot></slot></span>
+</div>
+
+<cr-action-menu id="actionMenu">
+  <slot name="action-menu-items"></slot>
+  <button id="openInfoDialogButton" class="dropdown-item"
+      on-click="onInfoButtonClick_" type="button">
+    [[i18n('moduleInfoButtonTitle')]]
+  </button>
+  <button id="dismissButton" class="dropdown-item"
+      on-click="onDismissButtonClick_">
+    [[dismissText]]
+  </button>
+  <button id="disableButton" class="dropdown-item"
+      on-click="onDisableButtonClick_">
+    [[disableText]]
+  </button>
+  <button id="customizeButton" class="dropdown-item"
+      on-click="onCustomizeButtonClick_">
+    $i18n{modulesCustomizeButtonText}
+  </button>
+</cr-action-menu>
diff --git a/chrome/browser/resources/new_tab_page/modules/history_clusters_v2/module_header.ts b/chrome/browser/resources/new_tab_page/modules/history_clusters_v2/module_header.ts
new file mode 100644
index 0000000..ef443b7
--- /dev/null
+++ b/chrome/browser/resources/new_tab_page/modules/history_clusters_v2/module_header.ts
@@ -0,0 +1,74 @@
+// Copyright 2023 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import 'chrome://resources/cr_elements/cr_action_menu/cr_action_menu.js';
+
+import {CrActionMenuElement} from 'chrome://resources/cr_elements/cr_action_menu/cr_action_menu.js';
+import {PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+
+import {I18nMixin} from '../../i18n_setup.js';
+
+import {getTemplate} from './module_header.html.js';
+
+export interface ModuleHeaderElementV2 {
+  $: {
+    actionMenu: CrActionMenuElement,
+  };
+}
+
+/** Element that displays a header inside a module.  */
+export class ModuleHeaderElementV2 extends I18nMixin
+(PolymerElement) {
+  static get is() {
+    return 'ntp-module-header-v2';
+  }
+
+  static get template() {
+    return getTemplate();
+  }
+
+  static get properties() {
+    return {
+      dismissText: String,
+      disableText: String,
+    };
+  }
+
+  dismissText: string;
+  disableText: string;
+
+  private onInfoButtonClick_() {
+    this.$.actionMenu.close();
+    this.dispatchEvent(
+        new Event('info-button-click', {bubbles: true, composed: true}));
+  }
+
+  private onMenuButtonClick_(e: Event) {
+    this.$.actionMenu.showAt(e.target as HTMLElement);
+  }
+
+  private onDismissButtonClick_() {
+    this.$.actionMenu.close();
+    this.dispatchEvent(new Event('dismiss-button-click', {bubbles: true}));
+  }
+
+  private onDisableButtonClick_() {
+    this.$.actionMenu.close();
+    this.dispatchEvent(new Event('disable-button-click', {bubbles: true}));
+  }
+
+  private onCustomizeButtonClick_() {
+    this.$.actionMenu.close();
+    this.dispatchEvent(
+        new Event('customize-module', {bubbles: true, composed: true}));
+  }
+}
+
+declare global {
+  interface HTMLElementTagNameMap {
+    'ntp-module-header-v2': ModuleHeaderElementV2;
+  }
+}
+
+customElements.define(ModuleHeaderElementV2.is, ModuleHeaderElementV2);
diff --git a/chrome/browser/resources/new_tab_page/modules/module_wrapper.html b/chrome/browser/resources/new_tab_page/modules/module_wrapper.html
index f69f228..324d904 100644
--- a/chrome/browser/resources/new_tab_page/modules/module_wrapper.html
+++ b/chrome/browser/resources/new_tab_page/modules/module_wrapper.html
@@ -9,6 +9,11 @@
     position: relative;
   }
 
+  :host([modules-redesigned-enabled_]) {
+    background-color: transparent;
+    border: none;
+  }
+
   #impressionProbe {
     height: 27px;
     pointer-events: none;
@@ -23,6 +28,10 @@
     height: 100%;
     justify-content: center;
   }
+
+  :host([modules-redesigned-enabled_]) #moduleElement {
+    background: none;
+  }
 </style>
 <div id="impressionProbe"></div>
 <div id="moduleElement"></div>
diff --git a/chrome/browser/resources/new_tab_page/modules/module_wrapper.ts b/chrome/browser/resources/new_tab_page/modules/module_wrapper.ts
index 139a8765..bf161a9 100644
--- a/chrome/browser/resources/new_tab_page/modules/module_wrapper.ts
+++ b/chrome/browser/resources/new_tab_page/modules/module_wrapper.ts
@@ -3,6 +3,7 @@
 // found in the LICENSE file.
 
 import {assert} from 'chrome://resources/js/assert_ts.js';
+import {loadTimeData} from 'chrome://resources/js/load_time_data.js';
 import {microTask, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
 import {recordLoadDuration, recordOccurence, recordPerdecage} from '../metrics_utils.js';
@@ -35,10 +36,17 @@
         observer: 'onModuleChange_',
         type: Object,
       },
+
+      modulesRedesignedEnabled_: {
+        type: Boolean,
+        value: () => loadTimeData.getBoolean('modulesRedesignedEnabled'),
+        reflectToAttribute: true,
+      },
     };
   }
 
   module: Module;
+  private modulesRedesignedEnabled_: boolean;
 
   private onModuleChange_(_newValue: Module, oldValue?: Module) {
     assert(!oldValue);
diff --git a/chrome/browser/resources/settings/chromeos/device_page/keyboard_remap_modifier_key_row.html b/chrome/browser/resources/settings/chromeos/device_page/keyboard_remap_modifier_key_row.html
index aff8e05..44f04ff 100644
--- a/chrome/browser/resources/settings/chromeos/device_page/keyboard_remap_modifier_key_row.html
+++ b/chrome/browser/resources/settings/chromeos/device_page/keyboard_remap_modifier_key_row.html
@@ -9,17 +9,21 @@
     box-sizing: border-box;
     color: var(--cros-text-color-secondary);
     display: flex;
-    font-family: 'Google Sans', Roboto, sans-serif;
-    font-size: 13px;
-    font-weight: 500;
     height: 28px;
     justify-content: center;
-    line-height: 20px;
     margin-inline-end: 8px;
     min-width: 28px;
     padding: 6px;
   }
 
+  /* TODO(b/281116317) Remove once Jelly is launched */
+  :host-context(body:not(.jelly-enabled)) .key-container {
+    font-family: 'Google Sans', Roboto, sans-serif;
+    font-size: 13px;
+    font-weight: 500;
+    line-height: 20px;
+  }
+
   #keyLabel {
     padding-inline: 6px;
   }
diff --git a/chrome/browser/resources/settings/chromeos/keyboard_shortcut_banner/keyboard_shortcut_banner.html b/chrome/browser/resources/settings/chromeos/keyboard_shortcut_banner/keyboard_shortcut_banner.html
index 098d020..c68770d 100644
--- a/chrome/browser/resources/settings/chromeos/keyboard_shortcut_banner/keyboard_shortcut_banner.html
+++ b/chrome/browser/resources/settings/chromeos/keyboard_shortcut_banner/keyboard_shortcut_banner.html
@@ -6,13 +6,17 @@
     column-gap: 12px;
     display: flex;
     flex-shrink: 0;
-    font-family: Roboto, sans-serif;
-    font-size: 13px;
     margin: 4px;
     outline-width: 0;
     padding: 12px;
   }
 
+  /* TODO(b/281116317) Remove once Jelly is launched */
+  :host-context(body:not(.jelly-enabled)):host {
+    font-family: Roboto, sans-serif;
+    font-size: 13px;
+  }
+
   #icon {
     background: center / cover no-repeat;
     background-image: url(chrome://os-settings/images/keyboard_shortcut.svg);
@@ -46,10 +50,18 @@
 
   h2 {
     color: var(--cr-primary-text-color);
+    margin: 0;
+  }
+
+  /* TODO(b/281116317) Remove once Jelly is launched */
+  :host-context(body:not(.jelly-enabled)) h2 {
     font-family: 'Google Sans', Roboto, sans-serif;
     font-size: 15px;
     font-weight: 500;
-    margin: 0;
+  }
+
+  :host-context(body.jelly-enabled) h2 {
+    font: var(--cros-headline-1-font);
   }
 
   .reminder-message {
diff --git a/chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_notification_access_setup_dialog.html b/chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_notification_access_setup_dialog.html
index 17ed8019..a5af6ca2 100644
--- a/chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_notification_access_setup_dialog.html
+++ b/chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_notification_access_setup_dialog.html
@@ -1,5 +1,6 @@
 <style include="cr-shared-style settings-shared">
-  :host {
+  /* TODO(b/281116317) Remove once Jelly is launched */
+  :host-context(body:not(.jelly-enabled)):host {
     --cr-dialog-font-family: 'Google Sans';
     --cr-dialog-title-font-size: 16px;
   }
diff --git a/chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_permissions_setup_dialog.html b/chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_permissions_setup_dialog.html
index 6cd90e0..cd055e5 100644
--- a/chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_permissions_setup_dialog.html
+++ b/chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_permissions_setup_dialog.html
@@ -23,20 +23,36 @@
     color: var(--cros-text-color-primary);
     display: flex;
     flex-direction: row;
-    font-family: 'Google Sans Medium';
-    font-size: 16px;
     gap: 8px;
     justify-content: flex-start;
+  }
+
+  /* TODO(b/281116317) Remove once Jelly is launched */
+  :host-context(body:not(.jelly-enabled)) #title {
+    font-family: 'Google Sans Medium';
+    font-size: 16px;
     line-height: 24px;
   }
 
+  :host-context(body.jelly-enabled) #title {
+    font: var(--cros-title-1-font);
+  }
+
   #subtitle {
     color: var(--cros-text-color-secondary);
+  }
+
+  /* TODO(b/281116317) Remove once Jelly is launched */
+  :host-context(body:not(.jelly-enabled)) #subtitle {
     font-family: 'Roboto';
     font-size: 14px;
     line-height: 20px;
   }
 
+  :host-context(body.jelly-enabled) #subtitle {
+    font: var(--cros-body-1-font);
+  }
+
   #dialogBody {
     display: flex;
     flex-direction: column;
@@ -113,10 +129,18 @@
 
   #description {
     color: var(--cros-text-color-secondary);
+    padding-top: 24px;
+  }
+
+  /* TODO(b/281116317) Remove once Jelly is launched */
+  :host-context(body:not(.jelly-enabled)) #description {
     font-family: 'Roboto';
     font-size: 13px;
     line-height: 20px;
-    padding-top: 24px;
+  }
+
+  :host-context(body.jelly-enabled) #description {
+    font: var(--cros-body-2-font);
   }
 
   #feature-description {
@@ -139,14 +163,22 @@
     color: var(--cros-text-color-secondary);
     display: flex;
     flex-direction: row;
-    font-family: 'Roboto';
-    font-size: 13px;
     gap: 20px;
     justify-content: start;
-    line-height: 20px;
     padding-bottom: 16px;
   }
 
+  /* TODO(b/281116317) Remove once Jelly is launched */
+  :host-context(body:not(.jelly-enabled)) #feature-details-container {
+    font-family: 'Roboto';
+    font-size: 13px;
+    line-height: 20px;
+  }
+
+  :host-context(body.jelly-enabled) #feature-details-container {
+    font: var(--cros-body-2-font);
+  }
+
   #half-container {
     flex: 1;
   }
@@ -160,7 +192,6 @@
     color: var(--cros-text-color-secondary);
     display: flex;
     flex-direction: row;
-    font-family: 'Roboto';
     font-size: 11px;
     gap: 6px;
     justify-content: start;
@@ -168,19 +199,32 @@
     padding-bottom: 24px;
   }
 
+  /* TODO(b/281116317) Remove once Jelly is launched */
+  :host-context(body:not(.jelly-enabled)) #instruction {
+    font-family: 'Roboto';
+  }
+
   #screen-lock-instruction {
     align-items: flex-start;
     color: var(--cros-text-color-secondary);
     display: flex;
     flex-direction: row;
-    font-family: 'Roboto';
-    font-size: 13px;
     gap: 8px;
     justify-content: start;
-    line-height: 20px;
     padding-bottom: 24px;
   }
 
+  /* TODO(b/281116317) Remove once Jelly is launched */
+  :host-context(body:not(.jelly-enabled)) #screen-lock-instruction {
+    font-family: 'Roboto';
+    font-size: 13px;
+    line-height: 20px;
+  }
+
+  :host-context(body.jelly-enabled) #screen-lock-instruction {
+    font: var(--cros-body-2-font);
+  }
+
   #illustration {
     background-position: center center;
     background-repeat: no-repeat;
diff --git a/chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_screen_lock_subpage.html b/chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_screen_lock_subpage.html
index 928eb9b6..13a522c 100644
--- a/chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_screen_lock_subpage.html
+++ b/chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_screen_lock_subpage.html
@@ -36,14 +36,20 @@
     padding-top: 20px;
   }
 
-  #passwordRadioButton {
-    --cr-radio-button-label-spacing: 20px;
-    --cr-radio-button-size: 20px;
-    color: var(--cr-primary-text-color);
+  /* TODO(b/281116317) Remove once Jelly is launched */
+  :host-context(body:not(.jelly-enabled)) #passwordRadioButton,
+  :host-context(body:not(.jelly-enabled)) #pinRadioButton,
+  :host-context(body:not(.jelly-enabled)) #subtext {
     font-family: 'Roboto';
     font-size: 13px;
     font-weight: medium;
     line-height: 20px;
+  }
+
+  #passwordRadioButton {
+    --cr-radio-button-label-spacing: 20px;
+    --cr-radio-button-size: 20px;
+    color: var(--cr-primary-text-color);
     min-height: 20px;
     padding-inline-start: 8px;
     padding-top: 24px;
@@ -53,10 +59,6 @@
     --cr-radio-button-label-spacing: 20px;
     --cr-radio-button-size: 20px;
     color: var(--cr-primary-text-color);
-    font-family: 'Roboto';
-    font-size: 13px;
-    font-weight: medium;
-    line-height: 20px;
     min-height: 20px;
     padding-inline-start: 8px;
     padding-top: 44px;
@@ -64,10 +66,6 @@
 
   #subtext {
     color: var(--cr-secondary-text-color);
-    font-family: 'Roboto';
-    font-size: 13px;
-    font-weight: medium;
-    line-height: 20px;
     padding-inline-start: 48px;
   }
 </style>
diff --git a/chrome/browser/resources/settings/chromeos/nearby_share_page/nearby_share_contact_visibility_dialog.html b/chrome/browser/resources/settings/chromeos/nearby_share_page/nearby_share_contact_visibility_dialog.html
index 5309792..1a2f8e2 100644
--- a/chrome/browser/resources/settings/chromeos/nearby_share_page/nearby_share_contact_visibility_dialog.html
+++ b/chrome/browser/resources/settings/chromeos/nearby_share_page/nearby_share_contact_visibility_dialog.html
@@ -6,6 +6,7 @@
     padding-block-start: 24px;
   }
 
+  /* TODO(b/281116317) Remove once Jelly is launched */
   :host-context(body:not(.jelly-enabled)) .title {
     font-family: 'Google Sans';
     font-weight: normal;
diff --git a/chrome/browser/resources/settings/chromeos/nearby_share_page/nearby_share_data_usage_dialog.html b/chrome/browser/resources/settings/chromeos/nearby_share_page/nearby_share_data_usage_dialog.html
index e0f1569..da7a203 100644
--- a/chrome/browser/resources/settings/chromeos/nearby_share_page/nearby_share_data_usage_dialog.html
+++ b/chrome/browser/resources/settings/chromeos/nearby_share_page/nearby_share_data_usage_dialog.html
@@ -10,6 +10,7 @@
     padding-block-start: 24px;
   }
 
+  /* TODO(b/281116317) Remove once Jelly is launched */
   :host-context(body:not(.jelly-enabled)) .title {
     font-family: 'Google Sans';
     font-size: 16px;
diff --git a/chrome/browser/resources/settings/chromeos/nearby_share_page/nearby_share_device_name_dialog.html b/chrome/browser/resources/settings/chromeos/nearby_share_page/nearby_share_device_name_dialog.html
index 3fd07b3..4592b1b 100644
--- a/chrome/browser/resources/settings/chromeos/nearby_share_page/nearby_share_device_name_dialog.html
+++ b/chrome/browser/resources/settings/chromeos/nearby_share_page/nearby_share_device_name_dialog.html
@@ -10,6 +10,7 @@
     padding-block-start: 24px;
   }
 
+  /* TODO(b/281116317) Remove once Jelly is launched */
   :host-context(body:not(.jelly-enabled)) .title {
     font-family: 'Google Sans';
     font-size: 16px;
diff --git a/chrome/browser/resources/settings/chromeos/os_settings.ts b/chrome/browser/resources/settings/chromeos/os_settings.ts
index e63d901..9cbe366 100644
--- a/chrome/browser/resources/settings/chromeos/os_settings.ts
+++ b/chrome/browser/resources/settings/chromeos/os_settings.ts
@@ -47,7 +47,6 @@
 import './nearby_share_page/nearby_share_receive_dialog.js';
 import './nearby_share_page/nearby_share_subpage.js';
 import './os_files_page/google_drive_subpage.js';
-import './personalization_page/personalization_page.js';
 import './os_apps_page/android_apps_subpage.js';
 import './os_apps_page/app_notifications_page/app_notifications_subpage.js';
 import './os_apps_page/app_management_page/app_detail_view.js';
diff --git a/chrome/browser/resources/settings/chromeos/os_settings_page/os_settings_page.ts b/chrome/browser/resources/settings/chromeos/os_settings_page/os_settings_page.ts
index 0805aab..b77b76e 100644
--- a/chrome/browser/resources/settings/chromeos/os_settings_page/os_settings_page.ts
+++ b/chrome/browser/resources/settings/chromeos/os_settings_page/os_settings_page.ts
@@ -7,28 +7,34 @@
  * 'os-settings-page' is the settings page containing the actual OS settings.
  */
 
+/**
+ * All top-level basic pages should be imported below. Top-level advanced pages
+ * should be imported in lazy_load.ts instead.
+ */
+// clang-format off
+import '../device_page/device_page.js';
+import '../internet_page/internet_page.js';
+import '../kerberos_page/kerberos_page.js';
+import '../multidevice_page/multidevice_page.js';
+import '../os_a11y_page/os_a11y_page.js';
+import '../os_apps_page/os_apps_page.js';
+import '../os_bluetooth_page/os_bluetooth_page.js';
+import '../os_people_page/os_people_page.js';
+import '../os_privacy_page/os_privacy_page.js';
+import '../os_search_page/os_search_page.js';
+import '../personalization_page/personalization_page.js';
+// clang-format on
+
 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/icons.html.js';
 import 'chrome://resources/cr_elements/cr_shared_vars.css.js';
 import 'chrome://resources/polymer/v3_0/iron-icon/iron-icon.js';
 import './settings_idle_load.js';
-import '../os_a11y_page/os_a11y_page.js';
 import '../os_about_page/eol_offer_section.js';
-import '../os_apps_page/os_apps_page.js';
-import '../os_people_page/os_people_page.js';
-import '../os_privacy_page/os_privacy_page.js';
-import '../os_printing_page/os_printing_page.js';
-import '../os_search_page/os_search_page.js';
-import '../personalization_page/personalization_page.js';
+import '../os_settings_icons.html.js';
 import '../os_settings_page/os_settings_section.js';
 import '../os_settings_page_styles.css.js';
-import '../device_page/device_page.js';
-import '../internet_page/internet_page.js';
-import '../kerberos_page/kerberos_page.js';
-import '../multidevice_page/multidevice_page.js';
-import '../os_bluetooth_page/os_bluetooth_page.js';
-import '../os_settings_icons.html.js';
 
 import {WebUiListenerMixin} from 'chrome://resources/cr_elements/web_ui_listener_mixin.js';
 import {assert} from 'chrome://resources/js/assert_ts.js';
diff --git a/chrome/browser/resources/settings/site_settings/site_settings_prefs_browser_proxy.ts b/chrome/browser/resources/settings/site_settings/site_settings_prefs_browser_proxy.ts
index e14a70f1..434d895 100644
--- a/chrome/browser/resources/settings/site_settings/site_settings_prefs_browser_proxy.ts
+++ b/chrome/browser/resources/settings/site_settings/site_settings_prefs_browser_proxy.ts
@@ -163,10 +163,8 @@
 
 export interface ZoomLevelEntry {
   displayName: string;
-  origin: string;
+  hostOrSpec: string;
   originForFavicon: string;
-  setting: string;
-  source: string;
   zoom: string;
 }
 
diff --git a/chrome/browser/resources/settings/site_settings/zoom_levels.ts b/chrome/browser/resources/settings/site_settings/zoom_levels.ts
index 60063271..61b167d 100644
--- a/chrome/browser/resources/settings/site_settings/zoom_levels.ts
+++ b/chrome/browser/resources/settings/site_settings/zoom_levels.ts
@@ -78,7 +78,7 @@
    * @param sites The up to date list of sites and their zoom levels.
    */
   private onZoomLevelsChanged_(sites: ZoomLevelEntry[]) {
-    this.updateList('sites_', item => item.origin, sites);
+    this.updateList('sites_', item => item.hostOrSpec, sites);
     this.showNoSites_ = this.sites_.length === 0;
   }
 
@@ -87,7 +87,7 @@
    */
   private removeZoomLevel_(event: DomRepeatEvent<ZoomLevelEntry>) {
     const site = this.sites_[event.model.index];
-    this.browserProxy.removeZoomLevel(site.origin);
+    this.browserProxy.removeZoomLevel(site.hostOrSpec);
   }
 }
 
diff --git a/chrome/browser/resources/side_panel/read_anything/app.ts b/chrome/browser/resources/side_panel/read_anything/app.ts
index 8f65c46..bade9826c 100644
--- a/chrome/browser/resources/side_panel/read_anything/app.ts
+++ b/chrome/browser/resources/side_panel/read_anything/app.ts
@@ -147,6 +147,9 @@
     this.showLoading();
 
     document.onselectionchange = () => {
+      if (!this.hasContent_) {
+        return;
+      }
       const shadowRoot = this.shadowRoot;
       assert(shadowRoot);
       const selection = shadowRoot.getSelection();
diff --git a/chrome/browser/site_isolation/chrome_site_per_process_browsertest.cc b/chrome/browser/site_isolation/chrome_site_per_process_browsertest.cc
index f11935a7..63a5483 100644
--- a/chrome/browser/site_isolation/chrome_site_per_process_browsertest.cc
+++ b/chrome/browser/site_isolation/chrome_site_per_process_browsertest.cc
@@ -697,12 +697,13 @@
   // Add a postMessage handler in the top frame.  The handler opens a new
   // popup.
   GURL popup_url(embedded_test_server()->GetURL("a.com", "/title2.html"));
-  EXPECT_TRUE(ExecuteScriptWithoutUserGesture(
+  EXPECT_TRUE(ExecJs(
       web_contents,
       base::StringPrintf("window.addEventListener('message', function() {\n"
                          "  window.w = window.open('%s');\n"
                          "});",
-                         popup_url.spec().c_str())));
+                         popup_url.spec().c_str()),
+      content::EXECUTE_SCRIPT_NO_USER_GESTURE));
 
   // Send a postMessage from the child frame to its parent.  Note that by
   // default ExecuteScript runs with a user gesture, which should be
@@ -746,12 +747,14 @@
   // Add a postMessage handler in the root frame.  The handler opens a new popup
   // for a URL constructed using postMessage event data.
   GURL popup_url(embedded_test_server()->GetURL("popup.com", "/"));
-  EXPECT_TRUE(ExecuteScriptWithoutUserGesture(
-      web_contents, base::StringPrintf(
-                        "window.addEventListener('message', function(event) {\n"
-                        "  window.w = window.open('%s' + event.data);\n"
-                        "});",
-                        popup_url.spec().c_str())));
+  EXPECT_TRUE(
+      ExecJs(web_contents,
+             base::StringPrintf(
+                 "window.addEventListener('message', function(event) {\n"
+                 "  window.w = window.open('%s' + event.data);\n"
+                 "});",
+                 popup_url.spec().c_str()),
+             content::EXECUTE_SCRIPT_NO_USER_GESTURE));
 
   // Send two postMessages from child frame to parent frame as part of the same
   // user gesture.  Ensure that only one popup can be opened.
@@ -799,8 +802,9 @@
       "  window.w = window.open('%s' + event.data);\n"
       "});",
       popup_url.spec().c_str());
-  EXPECT_TRUE(ExecuteScriptWithoutUserGesture(web_contents, script));
-  EXPECT_TRUE(ExecuteScriptWithoutUserGesture(frame_b, script));
+  EXPECT_TRUE(
+      ExecJs(web_contents, script, content::EXECUTE_SCRIPT_NO_USER_GESTURE));
+  EXPECT_TRUE(ExecJs(frame_b, script, content::EXECUTE_SCRIPT_NO_USER_GESTURE));
 
   // Add a popup observer.
   content::TestNavigationObserver popup_observer(nullptr);
@@ -865,22 +869,24 @@
   // Add a postMessage handler to middle frame to send another postMessage to
   // top frame and then immediately attempt window.open().
   GURL popup1_url(embedded_test_server()->GetURL("popup.com", "/title1.html"));
-  EXPECT_TRUE(ExecuteScriptWithoutUserGesture(
+  EXPECT_TRUE(ExecJs(
       child,
       base::StringPrintf("window.addEventListener('message', function() {\n"
                          "  parent.postMessage('foo', '*');\n"
                          "  window.w = window.open('%s');\n"
                          "});",
-                         popup1_url.spec().c_str())));
+                         popup1_url.spec().c_str()),
+      content::EXECUTE_SCRIPT_NO_USER_GESTURE));
 
   // Add a postMessage handler to top frame to attempt a window.open().
   GURL popup2_url(embedded_test_server()->GetURL("popup.com", "/title2.html"));
-  EXPECT_TRUE(ExecuteScriptWithoutUserGesture(
+  EXPECT_TRUE(ExecJs(
       web_contents,
       base::StringPrintf("window.addEventListener('message', function() {\n"
                          "  window.w = window.open('%s');\n"
                          "});",
-                         popup2_url.spec().c_str())));
+                         popup2_url.spec().c_str()),
+      content::EXECUTE_SCRIPT_NO_USER_GESTURE));
 
   // Send a postMessage from bottom frame to middle frame as part of the same
   // user gesture.  Ensure that only one popup can be opened.
@@ -921,12 +927,13 @@
   // Add a postMessage handler in the root frame.  The handler opens a new popup
   // for a URL constructed using postMessage event data.
   GURL popup_url(embedded_test_server()->GetURL("popup.com", "/title1.html"));
-  EXPECT_TRUE(ExecuteScriptWithoutUserGesture(
+  EXPECT_TRUE(ExecJs(
       web_contents,
       base::StringPrintf("window.addEventListener('message', function() {\n"
                          "  window.w = window.open('%s');\n"
                          "});",
-                         popup_url.spec().c_str())));
+                         popup_url.spec().c_str()),
+      content::EXECUTE_SCRIPT_NO_USER_GESTURE));
 
   // Send a postMessage from child frame to parent frame and then immediately
   // consume the user gesture with window.open().
@@ -985,14 +992,16 @@
 
   // Try opening popups from frame_c and root frame.
   GURL popup_url(embedded_test_server()->GetURL("popup.com", "/"));
-  EXPECT_TRUE(ExecuteScriptWithoutUserGesture(
-      frame_c,
-      base::StringPrintf("window.w = window.open('%s' + 'title1.html');",
-                         popup_url.spec().c_str())));
-  EXPECT_TRUE(ExecuteScriptWithoutUserGesture(
-      web_contents,
-      base::StringPrintf("window.w = window.open('%s' + 'title2.html');",
-                         popup_url.spec().c_str())));
+  EXPECT_TRUE(
+      ExecJs(frame_c,
+             base::StringPrintf("window.w = window.open('%s' + 'title1.html');",
+                                popup_url.spec().c_str()),
+             content::EXECUTE_SCRIPT_NO_USER_GESTURE));
+  EXPECT_TRUE(
+      ExecJs(web_contents,
+             base::StringPrintf("window.w = window.open('%s' + 'title2.html');",
+                                popup_url.spec().c_str()),
+             content::EXECUTE_SCRIPT_NO_USER_GESTURE));
 
   // Wait and check that only one popup has opened.
   popup_observer.Wait();
@@ -1038,18 +1047,21 @@
 
   // Try opening popups from all three frames.
   GURL popup_url(embedded_test_server()->GetURL("popup.com", "/"));
-  EXPECT_TRUE(ExecuteScriptWithoutUserGesture(
-      frame_b,
-      base::StringPrintf("window.w = window.open('%s' + 'title1.html');",
-                         popup_url.spec().c_str())));
-  EXPECT_TRUE(ExecuteScriptWithoutUserGesture(
-      frame_c,
-      base::StringPrintf("window.w = window.open('%s' + 'title2.html');",
-                         popup_url.spec().c_str())));
-  EXPECT_TRUE(ExecuteScriptWithoutUserGesture(
-      web_contents,
-      base::StringPrintf("window.w = window.open('%s' + 'title3.html');",
-                         popup_url.spec().c_str())));
+  EXPECT_TRUE(
+      ExecJs(frame_b,
+             base::StringPrintf("window.w = window.open('%s' + 'title1.html');",
+                                popup_url.spec().c_str()),
+             content::EXECUTE_SCRIPT_NO_USER_GESTURE));
+  EXPECT_TRUE(
+      ExecJs(frame_c,
+             base::StringPrintf("window.w = window.open('%s' + 'title2.html');",
+                                popup_url.spec().c_str()),
+             content::EXECUTE_SCRIPT_NO_USER_GESTURE));
+  EXPECT_TRUE(
+      ExecJs(web_contents,
+             base::StringPrintf("window.w = window.open('%s' + 'title3.html');",
+                                popup_url.spec().c_str()),
+             content::EXECUTE_SCRIPT_NO_USER_GESTURE));
 
   // Wait and check that only one popup has opened.
   popup_observer.Wait();
@@ -1094,18 +1106,20 @@
   // Open a popup from the frame, with `noopener`. This should consume
   // transient user activation.
   GURL popup_url(embedded_test_server()->GetURL("popup.com", "/"));
-  EXPECT_TRUE(ExecuteScriptWithoutUserGesture(
+  EXPECT_TRUE(ExecJs(
       web_contents,
       base::StringPrintf(
           "window.w = window.open('%s'+'title1.html', '_blank', 'noopener');",
-          popup_url.spec().c_str())));
+          popup_url.spec().c_str()),
+      content::EXECUTE_SCRIPT_NO_USER_GESTURE));
 
   // Try to open another popup.
-  EXPECT_TRUE(ExecuteScriptWithoutUserGesture(
+  EXPECT_TRUE(ExecJs(
       web_contents,
       base::StringPrintf(
           "window.w = window.open('%s'+'title2.html', '_blank', 'noopener');",
-          popup_url.spec().c_str())));
+          popup_url.spec().c_str()),
+      content::EXECUTE_SCRIPT_NO_USER_GESTURE));
 
   // Wait and check that only one popup was opened.
   popup_observer.Wait();
@@ -1388,13 +1402,16 @@
 
   // Try opening popups from frame_b and root frame.
   GURL popup_url(embedded_test_server()->GetURL("popup.com", "/"));
-  EXPECT_TRUE(ExecuteScriptWithoutUserGesture(
-      frame_b, content::JsReplace("window.w = window.open($1 + 'title1.html');",
-                                  popup_url)));
-  EXPECT_TRUE(ExecuteScriptWithoutUserGesture(
-      web_contents,
-      content::JsReplace("window.w = window.open($1 + 'title2.html');",
-                         popup_url)));
+  EXPECT_TRUE(
+      ExecJs(frame_b,
+             content::JsReplace("window.w = window.open($1 + 'title1.html');",
+                                popup_url),
+             content::EXECUTE_SCRIPT_NO_USER_GESTURE));
+  EXPECT_TRUE(
+      ExecJs(web_contents,
+             content::JsReplace("window.w = window.open($1 + 'title2.html');",
+                                popup_url),
+             content::EXECUTE_SCRIPT_NO_USER_GESTURE));
 
   // Wait and check that only one popup has opened.
   popup_observer.Wait();
diff --git a/chrome/browser/site_isolation/site_per_process_interactive_browsertest.cc b/chrome/browser/site_isolation/site_per_process_interactive_browsertest.cc
index 51263204..cf54f5a 100644
--- a/chrome/browser/site_isolation/site_per_process_interactive_browsertest.cc
+++ b/chrome/browser/site_isolation/site_per_process_interactive_browsertest.cc
@@ -1577,8 +1577,8 @@
   // later when it sends messages from its focus/blur events.
   content::WebContents* web_contents =
       browser()->tab_strip_model()->GetActiveWebContents();
-  EXPECT_TRUE(
-      ExecuteScriptWithoutUserGesture(web_contents, "window.name = 'main'"));
+  EXPECT_TRUE(ExecJs(web_contents, "window.name = 'main'",
+                     content::EXECUTE_SCRIPT_NO_USER_GESTURE));
 
   // Open a popup for a cross-site page.
   GURL popup_url =
diff --git a/chrome/browser/ssl/https_first_mode_settings_tracker.cc b/chrome/browser/ssl/https_first_mode_settings_tracker.cc
index bb14952..674628f 100644
--- a/chrome/browser/ssl/https_first_mode_settings_tracker.cc
+++ b/chrome/browser/ssl/https_first_mode_settings_tracker.cc
@@ -25,6 +25,21 @@
 #include "chrome/browser/ash/profiles/profile_helper.h"
 #endif  // BUILDFLAG(IS_CHROMEOS_ASH)
 
+// Minimum score of an HTTPS origin to enable HFM on its hostname.
+const base::FeatureParam<int> kHttpsAddThreshold{
+    &features::kHttpsFirstModeV2ForEngagedSites, "https-add-threshold", 40};
+
+// Maximum score of an HTTP origin to enable HFM on its hostname.
+const base::FeatureParam<int> kHttpsRemoveThreshold{
+    &features::kHttpsFirstModeV2ForEngagedSites, "https-remove-threshold", 30};
+
+// If HTTPS score goes below kHttpsRemoveThreshold or HTTP score goes above
+// kHttpRemoveThreshold, disable HFM on this hostname.
+const base::FeatureParam<int> kHttpAddThreshold{
+    &features::kHttpsFirstModeV2ForEngagedSites, "http-add-threshold", 5};
+const base::FeatureParam<int> kHttpRemoveThreshold{
+    &features::kHttpsFirstModeV2ForEngagedSites, "http-remove-threshold", 10};
+
 namespace {
 
 using security_interstitials::https_only_mode::SiteEngagementHeuristicState;
@@ -35,18 +50,6 @@
 const char kHttpsFirstModeSyntheticFieldTrialEnabledGroup[] = "Enabled";
 const char kHttpsFirstModeSyntheticFieldTrialDisabledGroup[] = "Disabled";
 
-// Minimum score of an HTTPS origin to enable HFM on its hostname.
-// TODO(crbug.com/1435222): Convert these into feature params.
-constexpr double kHttpsAddThreshold = 40;
-
-// Maximum score of an HTTP origin to enable HFM on its hostname.
-constexpr double kHttpAddThreshold = 5;
-
-// If HTTPS score goes below kHttpsRemoveThreshold or HTTP score goes above
-// kHttpRemoveThreshold, disable HFM on this hostname.
-constexpr double kHttpsRemoveThreshold = 30;
-constexpr double kHttpRemoveThreshold = 10;
-
 // Returns the HTTP URL from `http_url` using the test port numbers, if any.
 // TODO(crbug.com/1435222): Refactor and merge with UpgradeUrlToHttps().
 GURL GetHttpUrlFromHttps(const GURL& https_url) {
@@ -177,11 +180,17 @@
 
 void HttpsFirstModeService::MaybeEnableHttpsFirstModeForUrl(Profile* profile,
                                                             const GURL& url) {
+  // Ideal parameter order is kHttpsAddThreshold > kHttpsRemoveThreshold >
+  // kHttpRemoveThreshold > kHttpAddThreshold.
+  if (!(kHttpsAddThreshold.Get() > kHttpsRemoveThreshold.Get() &&
+        kHttpsRemoveThreshold.Get() > kHttpRemoveThreshold.Get() &&
+        kHttpRemoveThreshold.Get() > kHttpAddThreshold.Get())) {
+    return;
+  }
+
   StatefulSSLHostStateDelegate* state =
       static_cast<StatefulSSLHostStateDelegate*>(
           profile_->GetSSLHostStateDelegate());
-  static_assert(kHttpsAddThreshold > kHttpsRemoveThreshold);
-  static_assert(kHttpAddThreshold < kHttpRemoveThreshold);
 
   // StatefulSSLHostStateDelegate can be null during tests. In that case, we
   // can't save the site setting.
@@ -198,8 +207,8 @@
 
   double https_score = engagement_svc->GetScore(https_url);
   double http_score = engagement_svc->GetScore(http_url);
-  bool should_enable =
-      https_score >= kHttpsAddThreshold && http_score <= kHttpAddThreshold;
+  bool should_enable = https_score >= kHttpsAddThreshold.Get() &&
+                       http_score <= kHttpAddThreshold.Get();
   if (!enforced && should_enable) {
     state->SetHttpsEnforcementForHost(url.host(),
                                       /*enforced=*/true,
@@ -207,8 +216,8 @@
     return;
   }
 
-  bool should_disable = https_score <= kHttpsRemoveThreshold ||
-                        http_score >= kHttpRemoveThreshold;
+  bool should_disable = https_score <= kHttpsRemoveThreshold.Get() ||
+                        http_score >= kHttpRemoveThreshold.Get();
   if (enforced && should_disable) {
     state->SetHttpsEnforcementForHost(url.host(),
                                       /*enforced=*/false,
diff --git a/chrome/browser/tab_group/javatests/DEPS b/chrome/browser/tab_group/javatests/DEPS
index 990c5a0..9353057 100644
--- a/chrome/browser/tab_group/javatests/DEPS
+++ b/chrome/browser/tab_group/javatests/DEPS
@@ -2,6 +2,7 @@
   "+base/test/android",
   "+chrome/android",
   "+chrome/browser/flags/android",
+  "+chrome/browser/ui/android/layouts",
   "+chrome/test/android",
   "+content/public/android/java/src/org/chromium/content_public",
   "+content/public/test/android",
diff --git a/chrome/browser/tab_group/javatests/src/org/chromium/chrome/browser/tasks/tab_groups/TabGroupsTest.java b/chrome/browser/tab_group/javatests/src/org/chromium/chrome/browser/tasks/tab_groups/TabGroupsTest.java
index 8fd60ec9..7e0abc8 100644
--- a/chrome/browser/tab_group/javatests/src/org/chromium/chrome/browser/tasks/tab_groups/TabGroupsTest.java
+++ b/chrome/browser/tab_group/javatests/src/org/chromium/chrome/browser/tasks/tab_groups/TabGroupsTest.java
@@ -5,27 +5,39 @@
 package org.chromium.chrome.browser.tasks.tab_groups;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.inOrder;
 
 import static org.chromium.base.test.util.Restriction.RESTRICTION_TYPE_NON_LOW_END_DEVICE;
 
 import androidx.test.filters.SmallTest;
 import androidx.test.platform.app.InstrumentationRegistry;
 
+import org.junit.After;
 import org.junit.Before;
 import org.junit.ClassRule;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.InOrder;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
 
 import org.chromium.base.test.util.Batch;
 import org.chromium.base.test.util.CommandLineFlags;
 import org.chromium.base.test.util.Restriction;
+import org.chromium.chrome.browser.ChromeTabbedActivity;
 import org.chromium.chrome.browser.flags.ChromeFeatureList;
 import org.chromium.chrome.browser.flags.ChromeSwitches;
+import org.chromium.chrome.browser.layouts.LayoutTestUtils;
+import org.chromium.chrome.browser.layouts.LayoutType;
 import org.chromium.chrome.browser.tab.Tab;
 import org.chromium.chrome.browser.tab.TabLaunchType;
+import org.chromium.chrome.browser.tab.TabSelectionType;
 import org.chromium.chrome.browser.tabmodel.TabCreator;
 import org.chromium.chrome.browser.tabmodel.TabModel;
+import org.chromium.chrome.browser.tabmodel.TabModelObserver;
 import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
 import org.chromium.chrome.test.ChromeTabbedActivityTestRule;
 import org.chromium.chrome.test.batch.BlankCTATabInitialStateRule;
@@ -57,16 +69,28 @@
     public BlankCTATabInitialStateRule mBlankCTATabInitialStateRule =
             new BlankCTATabInitialStateRule(sActivityTestRule, false);
 
+    @Mock
+    private TabModelObserver mTabModelFilterObserver;
+
     private TabModel mTabModel;
     private TabGroupModelFilter mTabGroupModelFilter;
 
     @Before
     public void setUp() {
+        MockitoAnnotations.initMocks(this);
         mTabModel = sActivityTestRule.getActivity().getTabModelSelector().getModel(false);
         mTabGroupModelFilter = (TabGroupModelFilter) sActivityTestRule.getActivity()
                                        .getTabModelSelector()
                                        .getTabModelFilterProvider()
                                        .getTabModelFilter(false);
+        TestThreadUtils.runOnUiThreadBlocking(
+                () -> { mTabGroupModelFilter.addObserver(mTabModelFilterObserver); });
+    }
+
+    @After
+    public void tearDown() {
+        TestThreadUtils.runOnUiThreadBlocking(
+                () -> { mTabGroupModelFilter.removeObserver(mTabModelFilterObserver); });
     }
 
     private void prepareTabs(List<Integer> tabsPerGroup) {
@@ -205,6 +229,49 @@
         assertOrderValid(true);
     }
 
+    @Test
+    @SmallTest
+    public void testTabModelFilterObserverUndoClosure() {
+        // Four tabs plus the first blank one.
+        prepareTabs(Arrays.asList(new Integer[] {3, 1}));
+        final List<Tab> tabs = getCurrentTabs();
+        assertEquals(5, tabs.size());
+
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            mTabModel.setIndex(2, TabSelectionType.FROM_USER, false);
+            mTabModel.closeAllTabs();
+        });
+
+        List<Tab> noTabs = getCurrentTabs();
+        assertTrue(noTabs.isEmpty());
+
+        InOrder calledInOrder = inOrder(mTabModelFilterObserver);
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            for (Tab tab : tabs) {
+                mTabModel.cancelTabClosure(tab.getId());
+            }
+        });
+        // Ensure didSelectTab is called and the call occurs after the tab closure is actually
+        // undone.
+        calledInOrder.verify(mTabModelFilterObserver).tabClosureUndone(eq(tabs.get(0)));
+        calledInOrder.verify(mTabModelFilterObserver)
+                .didSelectTab(eq(tabs.get(0)), eq(TabSelectionType.FROM_UNDO), /*lastId=*/eq(-1));
+        calledInOrder.verify(mTabModelFilterObserver).tabClosureUndone(eq(tabs.get(1)));
+        calledInOrder.verify(mTabModelFilterObserver).tabClosureUndone(eq(tabs.get(2)));
+        calledInOrder.verify(mTabModelFilterObserver).tabClosureUndone(eq(tabs.get(3)));
+        calledInOrder.verify(mTabModelFilterObserver).tabClosureUndone(eq(tabs.get(4)));
+
+        // Closing all tabs enters the tab switcher. Exit it.
+        ChromeTabbedActivity cta = (ChromeTabbedActivity) sActivityTestRule.getActivity();
+        assertTrue(cta.getLayoutManager().isLayoutVisible(LayoutType.TAB_SWITCHER));
+        TestThreadUtils.runOnUiThreadBlocking(() -> cta.onBackPressed());
+        LayoutTestUtils.waitForLayout(cta.getLayoutManager(), LayoutType.BROWSING);
+
+        List<Tab> finalTabs = getCurrentTabs();
+        assertEquals(5, finalTabs.size());
+        assertEquals(tabs, finalTabs);
+    }
+
     private void assertOrderValid(boolean expectedState) {
         boolean isOrderValid = TestThreadUtils.runOnUiThreadBlockingNoException(
                 () -> { return mTabGroupModelFilter.isOrderValid(); });
diff --git a/chrome/browser/ui/BUILD.gn b/chrome/browser/ui/BUILD.gn
index a963fb0..14e15404 100644
--- a/chrome/browser/ui/BUILD.gn
+++ b/chrome/browser/ui/BUILD.gn
@@ -2861,6 +2861,8 @@
       "webui/ash/login/family_link_notice_screen_handler.h",
       "webui/ash/login/fingerprint_setup_screen_handler.cc",
       "webui/ash/login/fingerprint_setup_screen_handler.h",
+      "webui/ash/login/gaia_info_screen_handler.cc",
+      "webui/ash/login/gaia_info_screen_handler.h",
       "webui/ash/login/gaia_password_changed_screen_handler.cc",
       "webui/ash/login/gaia_password_changed_screen_handler.h",
       "webui/ash/login/gaia_screen_handler.cc",
diff --git a/chrome/browser/ui/blocked_content/popup_blocker_browsertest.cc b/chrome/browser/ui/blocked_content/popup_blocker_browsertest.cc
index 4c28169..ee4d9aa6 100644
--- a/chrome/browser/ui/blocked_content/popup_blocker_browsertest.cc
+++ b/chrome/browser/ui/blocked_content/popup_blocker_browsertest.cc
@@ -134,7 +134,8 @@
     // Do a round trip to the renderer first to flush any in-flight IPCs to
     // create a to-be-blocked window.
     WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();
-    if (!content::ExecuteScriptWithoutUserGesture(tab, std::string())) {
+    if (!content::ExecJs(tab, std::string(),
+                         content::EXECUTE_SCRIPT_NO_USER_GESTURE)) {
       ADD_FAILURE() << "Failed to execute script in active tab.";
       return -1;
     }
@@ -822,7 +823,8 @@
   // before we perform the checks further down. Since we have no control over
   // that script we just run some more (that we do control) and wait for it to
   // finish.
-  EXPECT_TRUE(content::ExecuteScriptWithoutUserGesture(tab_2, ""));
+  EXPECT_TRUE(
+      content::ExecJs(tab_2, "", content::EXECUTE_SCRIPT_NO_USER_GESTURE));
 
   EXPECT_FALSE(content_settings::PageSpecificContentSettings::GetForFrame(
                    tab_1->GetPrimaryMainFrame())
diff --git a/chrome/browser/ui/blocked_content/tab_under_blocker_browsertest.cc b/chrome/browser/ui/blocked_content/tab_under_blocker_browsertest.cc
index d6311b4..fe1683f 100644
--- a/chrome/browser/ui/blocked_content/tab_under_blocker_browsertest.cc
+++ b/chrome/browser/ui/blocked_content/tab_under_blocker_browsertest.cc
@@ -144,9 +144,11 @@
   content::WebContentsConsoleObserver console_observer(opener);
   console_observer.SetPattern(expected_error);
 
-  EXPECT_TRUE(content::ExecuteScriptWithoutUserGesture(
-      opener, base::StringPrintf("window.location = '%s';",
-                                 cross_origin_url.spec().c_str())));
+  EXPECT_TRUE(
+      content::ExecJs(opener,
+                      base::StringPrintf("window.location = '%s';",
+                                         cross_origin_url.spec().c_str()),
+                      content::EXECUTE_SCRIPT_NO_USER_GESTURE));
   tab_under_observer.Wait();
   EXPECT_FALSE(tab_under_observer.last_navigation_succeeded());
 
@@ -173,9 +175,11 @@
   content::WebContentsConsoleObserver console_observer(opener);
   console_observer.SetPattern(expected_error);
 
-  EXPECT_TRUE(content::ExecuteScriptWithoutUserGesture(
-      opener, base::StringPrintf("window.location = '%s';",
-                                 cross_origin_url.spec().c_str())));
+  EXPECT_TRUE(
+      content::ExecJs(opener,
+                      base::StringPrintf("window.location = '%s';",
+                                         cross_origin_url.spec().c_str()),
+                      content::EXECUTE_SCRIPT_NO_USER_GESTURE));
   tab_under_observer.Wait();
   EXPECT_FALSE(tab_under_observer.last_navigation_succeeded());
 
@@ -200,9 +204,11 @@
       embedded_test_server()->GetURL("a.com", "/title1.html");
   content::WebContentsConsoleObserver console_observer(opener);
   console_observer.SetPattern(GetError(cross_origin_url));
-  EXPECT_TRUE(content::ExecuteScriptWithoutUserGesture(
-      opener, base::StringPrintf("window.location = '%s';",
-                                 cross_origin_url.spec().c_str())));
+  EXPECT_TRUE(
+      content::ExecJs(opener,
+                      base::StringPrintf("window.location = '%s';",
+                                         cross_origin_url.spec().c_str()),
+                      content::EXECUTE_SCRIPT_NO_USER_GESTURE));
   tab_under_observer.Wait();
   EXPECT_TRUE(tab_under_observer.last_navigation_succeeded());
 
@@ -227,9 +233,11 @@
       embedded_test_server()->GetURL("a.com", "/title1.html");
   content::WebContentsConsoleObserver console_observer(opener);
   console_observer.SetPattern(GetError(cross_origin_url));
-  EXPECT_TRUE(content::ExecuteScriptWithoutUserGesture(
-      opener, base::StringPrintf("window.location = '%s';",
-                                 cross_origin_url.spec().c_str())));
+  EXPECT_TRUE(
+      content::ExecJs(opener,
+                      base::StringPrintf("window.location = '%s';",
+                                         cross_origin_url.spec().c_str()),
+                      content::EXECUTE_SCRIPT_NO_USER_GESTURE));
   tab_under_observer.Wait();
   EXPECT_TRUE(tab_under_observer.last_navigation_succeeded());
 
@@ -284,9 +292,11 @@
     content::TestNavigationObserver tab_under_observer(opener, 1);
     const GURL cross_origin_url =
         embedded_test_server()->GetURL("a.com", "/title1.html");
-    EXPECT_TRUE(content::ExecuteScriptWithoutUserGesture(
-        opener, base::StringPrintf("window.location = '%s';",
-                                   cross_origin_url.spec().c_str())));
+    EXPECT_TRUE(
+        content::ExecJs(opener,
+                        base::StringPrintf("window.location = '%s';",
+                                           cross_origin_url.spec().c_str()),
+                        content::EXECUTE_SCRIPT_NO_USER_GESTURE));
     tab_under_observer.Wait();
 
     EXPECT_TRUE(tab_under_observer.last_navigation_succeeded());
@@ -300,9 +310,11 @@
     content::TestNavigationObserver tab_under_observer(opener, 1);
     const GURL cross_origin_url =
         embedded_test_server()->GetURL("b.com", "/title1.html");
-    EXPECT_TRUE(content::ExecuteScriptWithoutUserGesture(
-        opener, base::StringPrintf("window.location = '%s';",
-                                   cross_origin_url.spec().c_str())));
+    EXPECT_TRUE(
+        content::ExecJs(opener,
+                        base::StringPrintf("window.location = '%s';",
+                                           cross_origin_url.spec().c_str()),
+                        content::EXECUTE_SCRIPT_NO_USER_GESTURE));
     tab_under_observer.Wait();
 
     EXPECT_FALSE(tab_under_observer.last_navigation_succeeded());
@@ -325,9 +337,11 @@
   content::TestNavigationObserver tab_under_observer(opener, 1);
   const GURL cross_origin_url =
       embedded_test_server()->GetURL("b.com", "/title1.html");
-  EXPECT_TRUE(content::ExecuteScriptWithoutUserGesture(
-      opener, base::StringPrintf("window.location = '%s';",
-                                 cross_origin_url.spec().c_str())));
+  EXPECT_TRUE(
+      content::ExecJs(opener,
+                      base::StringPrintf("window.location = '%s';",
+                                         cross_origin_url.spec().c_str()),
+                      content::EXECUTE_SCRIPT_NO_USER_GESTURE));
   tab_under_observer.Wait();
 
   EXPECT_TRUE(tab_under_observer.last_navigation_succeeded());
@@ -345,9 +359,11 @@
     content::TestNavigationObserver tab_under_observer(opener, 1);
     const GURL cross_origin_url =
         embedded_test_server()->GetURL("b.com", "/title1.html");
-    EXPECT_TRUE(content::ExecuteScriptWithoutUserGesture(
-        opener, base::StringPrintf("window.location = '%s';",
-                                   cross_origin_url.spec().c_str())));
+    EXPECT_TRUE(
+        content::ExecJs(opener,
+                        base::StringPrintf("window.location = '%s';",
+                                           cross_origin_url.spec().c_str()),
+                        content::EXECUTE_SCRIPT_NO_USER_GESTURE));
     tab_under_observer.Wait();
 
     EXPECT_FALSE(tab_under_observer.last_navigation_succeeded());
@@ -364,9 +380,11 @@
     content::TestNavigationObserver tab_under_observer(opener, 1);
     const GURL cross_origin_url =
         embedded_test_server()->GetURL("a.com", "/title1.html");
-    EXPECT_TRUE(content::ExecuteScriptWithoutUserGesture(
-        opener, base::StringPrintf("window.location = '%s';",
-                                   cross_origin_url.spec().c_str())));
+    EXPECT_TRUE(
+        content::ExecJs(opener,
+                        base::StringPrintf("window.location = '%s';",
+                                           cross_origin_url.spec().c_str()),
+                        content::EXECUTE_SCRIPT_NO_USER_GESTURE));
     tab_under_observer.Wait();
 
     EXPECT_TRUE(tab_under_observer.last_navigation_succeeded());
diff --git a/chrome/browser/ui/content_settings/framebust_block_browsertest.cc b/chrome/browser/ui/content_settings/framebust_block_browsertest.cc
index be11186..8861eb7 100644
--- a/chrome/browser/ui/content_settings/framebust_block_browsertest.cc
+++ b/chrome/browser/ui/content_settings/framebust_block_browsertest.cc
@@ -105,9 +105,10 @@
         iframe.src='%s'
     )";
     content::TestNavigationObserver load_observer(contents);
-    bool result = content::ExecuteScriptWithoutUserGesture(
+    bool result = content::ExecJs(
         contents,
-        base::StringPrintf(kScript, iframe_id.c_str(), url.spec().c_str()));
+        base::StringPrintf(kScript, iframe_id.c_str(), url.spec().c_str()),
+        content::EXECUTE_SCRIPT_NO_USER_GESTURE);
     load_observer.Wait();
     return result;
   }
diff --git a/chrome/browser/ui/tabs/saved_tab_groups/local_tab_group_listener.cc b/chrome/browser/ui/tabs/saved_tab_groups/local_tab_group_listener.cc
index 6c325a9f..b216826e 100644
--- a/chrome/browser/ui/tabs/saved_tab_groups/local_tab_group_listener.cc
+++ b/chrome/browser/ui/tabs/saved_tab_groups/local_tab_group_listener.cc
@@ -121,7 +121,8 @@
   const base::Uuid tab_guid = saved_group()->GetTab(tab_id)->saved_tab_guid();
 
   web_contents_to_tab_id_map_.erase(web_contents);
-  model_->RemoveTabFromGroup(saved_guid_, tab_guid);
+  model_->RemoveTabFromGroup(saved_guid_, tab_guid,
+                             /*update_tab_positions=*/true);
 
   return model_->Contains(saved_guid_) ? Liveness::kGroupExists
                                        : Liveness::kGroupDeleted;
diff --git a/chrome/browser/ui/views/webid/fedcm_account_selection_view_desktop.cc b/chrome/browser/ui/views/webid/fedcm_account_selection_view_desktop.cc
index de3b3c75..d9c2fb3d 100644
--- a/chrome/browser/ui/views/webid/fedcm_account_selection_view_desktop.cc
+++ b/chrome/browser/ui/views/webid/fedcm_account_selection_view_desktop.cc
@@ -48,6 +48,14 @@
   Close();
 
   TabStripModelObserver::StopObservingAll(this);
+
+  if (idp_signin_modal_dialog_) {
+    // Important to remove the observer here, so that we don't try to use it in
+    // FedCmModalDialogView's destructor to inform this
+    // FedCmAccountSelectionView, which would cause a use-after-free.
+    idp_signin_modal_dialog_->RemoveObserver();
+    CloseModalDialog();
+  }
 }
 
 void FedCmAccountSelectionView::Show(
@@ -57,6 +65,16 @@
         identity_provider_data_list,
     Account::SignInMode sign_in_mode,
     bool show_auto_reauthn_checkbox) {
+  // If IDP sign-in modal dialog is open, we delay the showing of the accounts
+  // dialog until the modal dialog is destroyed.
+  if (idp_signin_modal_dialog_) {
+    show_accounts_dialog_callback_ = base::BindOnce(
+        &FedCmAccountSelectionView::Show, weak_ptr_factory_.GetWeakPtr(),
+        top_frame_etld_plus_one, iframe_etld_plus_one,
+        identity_provider_data_list, sign_in_mode, show_auto_reauthn_checkbox);
+    return;
+  }
+
   idp_display_data_list_.clear();
 
   size_t accounts_size = 0u;
@@ -184,8 +202,9 @@
 
 void FedCmAccountSelectionView::OnVisibilityChanged(
     content::Visibility visibility) {
-  if (!bubble_widget_)
+  if (!bubble_widget_ || idp_signin_modal_dialog_) {
     return;
+  }
 
   if (visibility == content::Visibility::VISIBLE) {
     bubble_widget_->Show();
@@ -356,17 +375,30 @@
 
 void FedCmAccountSelectionView::ShowModalDialog(const GURL& url) {
   idp_signin_modal_dialog_ = FedCmModalDialogView::ShowFedCmModalDialog(
-      delegate_->GetWebContents(), url);
+      delegate_->GetWebContents(), url, this);
   if (GetBubbleView()->HasIdentityRegistryCallback()) {
     std::move(GetBubbleView()->GetIdentityRegistryCallback())
         .Run(idp_signin_modal_dialog_->GetWebViewWebContents());
   }
+
+  input_protector_->VisibilityChanged(false);
+  bubble_widget_->Hide();
 }
 
 void FedCmAccountSelectionView::CloseModalDialog() {
   if (idp_signin_modal_dialog_) {
     idp_signin_modal_dialog_->CloseFedCmModalDialog();
-    idp_signin_modal_dialog_ = nullptr;
+  }
+}
+
+void FedCmAccountSelectionView::OnFedCmModalDialogViewDestroyed() {
+  // The underlying FedCmModalDialogView has been destroyed.
+  idp_signin_modal_dialog_ = nullptr;
+
+  if (show_accounts_dialog_callback_) {
+    std::move(show_accounts_dialog_callback_).Run();
+    input_protector_->VisibilityChanged(true);
+    bubble_widget_->Show();
   }
 }
 
diff --git a/chrome/browser/ui/views/webid/fedcm_account_selection_view_desktop.h b/chrome/browser/ui/views/webid/fedcm_account_selection_view_desktop.h
index 52bc356..15816ec 100644
--- a/chrome/browser/ui/views/webid/fedcm_account_selection_view_desktop.h
+++ b/chrome/browser/ui/views/webid/fedcm_account_selection_view_desktop.h
@@ -23,6 +23,7 @@
 // account chooser to the user.
 class FedCmAccountSelectionView : public AccountSelectionView,
                                   public AccountSelectionBubbleView::Observer,
+                                  public FedCmModalDialogView::Observer,
                                   content::WebContentsObserver,
                                   TabStripModelObserver,
                                   views::WidgetObserver {
@@ -63,6 +64,9 @@
   std::string GetTitle() const override;
   absl::optional<std::string> GetSubtitle() const override;
 
+  // FedCmModalDialogView::Observer
+  void OnFedCmModalDialogViewDestroyed() override;
+
   // content::WebContentsObserver
   void OnVisibilityChanged(content::Visibility visibility) override;
   void PrimaryPageChanged(content::Page& page) override;
@@ -159,7 +163,10 @@
 
   std::unique_ptr<views::InputEventActivationProtector> input_protector_;
 
-  raw_ptr<FedCmModalDialogView> idp_signin_modal_dialog_;
+  raw_ptr<FedCmModalDialogView> idp_signin_modal_dialog_{nullptr};
+
+  // Callback to show accounts dialog upon closing IDP sign-in modal dialog.
+  base::OnceClosure show_accounts_dialog_callback_;
 
   base::WeakPtrFactory<FedCmAccountSelectionView> weak_ptr_factory_{this};
 };
diff --git a/chrome/browser/ui/views/webid/fedcm_modal_dialog_view.cc b/chrome/browser/ui/views/webid/fedcm_modal_dialog_view.cc
index 14d2e64..a6ccc9d 100644
--- a/chrome/browser/ui/views/webid/fedcm_modal_dialog_view.cc
+++ b/chrome/browser/ui/views/webid/fedcm_modal_dialog_view.cc
@@ -13,22 +13,33 @@
 #include "ui/views/layout/box_layout.h"
 #include "ui/views/layout/table_layout.h"
 
-FedCmModalDialogView::FedCmModalDialogView(content::WebContents* web_contents,
-                                           const GURL& url)
-    : web_contents_(web_contents), curr_origin_(url::Origin::Create(url)) {
+FedCmModalDialogView::FedCmModalDialogView(
+    content::WebContents* web_contents,
+    const GURL& url,
+    FedCmModalDialogView::Observer* observer)
+    : web_contents_(web_contents),
+      observer_(observer),
+      curr_origin_(url::Origin::Create(url)) {
   SetModalType(ui::MODAL_TYPE_CHILD);
   SetButtons(ui::DIALOG_BUTTON_NONE);
   Init(url);
 }
 
-FedCmModalDialogView::~FedCmModalDialogView() = default;
+FedCmModalDialogView::~FedCmModalDialogView() {
+  // Let the observer know that this object is being destroyed.
+  if (observer_) {
+    observer_->OnFedCmModalDialogViewDestroyed();
+  }
+}
 
 // static
 FedCmModalDialogView* FedCmModalDialogView::ShowFedCmModalDialog(
     content::WebContents* web_contents,
-    const GURL& url) {
+    const GURL& url,
+    FedCmModalDialogView::Observer* observer) {
   // This dialog owns itself. DialogDelegateView will delete |dialog| instance.
-  FedCmModalDialogView* dialog = new FedCmModalDialogView(web_contents, url);
+  FedCmModalDialogView* dialog =
+      new FedCmModalDialogView(web_contents, url, observer);
   constrained_window::ShowWebModalDialogViews(dialog, web_contents);
   return dialog;
 }
@@ -42,6 +53,10 @@
   return web_view_->GetWebContents();
 }
 
+void FedCmModalDialogView::RemoveObserver() {
+  observer_ = nullptr;
+}
+
 void FedCmModalDialogView::Init(const GURL& url) {
   constexpr int kDialogMinWidth = 512;
   constexpr int kDialogHeight = 450;
diff --git a/chrome/browser/ui/views/webid/fedcm_modal_dialog_view.h b/chrome/browser/ui/views/webid/fedcm_modal_dialog_view.h
index 613405db..50178d9c 100644
--- a/chrome/browser/ui/views/webid/fedcm_modal_dialog_view.h
+++ b/chrome/browser/ui/views/webid/fedcm_modal_dialog_view.h
@@ -23,7 +23,16 @@
                              public ChromeWebModalDialogManagerDelegate {
  public:
   METADATA_HEADER(FedCmModalDialogView);
-  FedCmModalDialogView(content::WebContents* web_contents, const GURL& url);
+
+  class Observer {
+   public:
+    // Tells observers that their references to the view are becoming invalid.
+    virtual void OnFedCmModalDialogViewDestroyed() = 0;
+  };
+
+  FedCmModalDialogView(content::WebContents* web_contents,
+                       const GURL& url,
+                       FedCmModalDialogView::Observer* observer);
   FedCmModalDialogView(const FedCmModalDialogView&) = delete;
   FedCmModalDialogView& operator=(const FedCmModalDialogView&) = delete;
   ~FedCmModalDialogView() override;
@@ -33,11 +42,14 @@
   // with an identity provider.
   static FedCmModalDialogView* ShowFedCmModalDialog(
       content::WebContents* web_contents,
-      const GURL& url);
+      const GURL& url,
+      FedCmModalDialogView::Observer* observer);
   void CloseFedCmModalDialog();
 
   content::WebContents* GetWebViewWebContents();
 
+  void RemoveObserver();
+
  private:
   views::View* PopulateSheetHeaderView(views::View* container, const GURL& url);
   void Init(const GURL& url);
@@ -49,6 +61,7 @@
   raw_ptr<views::View> contents_wrapper_;
   raw_ptr<views::WebView> web_view_;
   raw_ptr<views::Label> origin_label_;
+  raw_ptr<Observer> observer_;
   url::Origin curr_origin_;
 
   base::WeakPtrFactory<FedCmModalDialogView> weak_ptr_factory_{this};
diff --git a/chrome/browser/ui/views/webid/fedcm_modal_dialog_view_browsertest.cc b/chrome/browser/ui/views/webid/fedcm_modal_dialog_view_browsertest.cc
index c1931f75..5aa5163f6 100644
--- a/chrome/browser/ui/views/webid/fedcm_modal_dialog_view_browsertest.cc
+++ b/chrome/browser/ui/views/webid/fedcm_modal_dialog_view_browsertest.cc
@@ -20,7 +20,7 @@
   void ShowUi(const std::string& name) override {
     FedCmModalDialogView::ShowFedCmModalDialog(
         browser()->tab_strip_model()->GetActiveWebContents(),
-        GURL(u"https://example.com"));
+        GURL(u"https://example.com"), /*observer=*/nullptr);
   }
 
  private:
diff --git a/chrome/browser/ui/views/webid/fedcm_modal_dialog_view_unittest.cc b/chrome/browser/ui/views/webid/fedcm_modal_dialog_view_unittest.cc
index 0436b85d..9442b13 100644
--- a/chrome/browser/ui/views/webid/fedcm_modal_dialog_view_unittest.cc
+++ b/chrome/browser/ui/views/webid/fedcm_modal_dialog_view_unittest.cc
@@ -52,7 +52,8 @@
 
 TEST_F(FedCmModalDialogViewTest, Init) {
   FedCmModalDialogView modal_dialog_view =
-      FedCmModalDialogView(web_contents(), GURL(u"https://example.com"));
+      FedCmModalDialogView(web_contents(), GURL(u"https://example.com"),
+                           /*observer=*/nullptr);
   views::View* view = modal_dialog_view.GetContentsView();
 
   const std::vector<views::View*> container = view->children();
diff --git a/chrome/browser/ui/webui/ash/login/gaia_info_screen_handler.cc b/chrome/browser/ui/webui/ash/login/gaia_info_screen_handler.cc
new file mode 100644
index 0000000..f05b9b9
--- /dev/null
+++ b/chrome/browser/ui/webui/ash/login/gaia_info_screen_handler.cc
@@ -0,0 +1,34 @@
+// Copyright 2023 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/ui/webui/ash/login/gaia_info_screen_handler.h"
+
+#include "base/logging.h"
+
+#include "chrome/browser/ash/login/oobe_screen.h"
+#include "chrome/browser/ash/login/wizard_controller.h"
+#include "chrome/browser/ui/webui/ash/login/base_screen_handler.h"
+#include "chrome/grit/generated_resources.h"
+#include "components/login/localized_values_builder.h"
+#include "ui/chromeos/devicetype_utils.h"
+
+namespace ash {
+
+GaiaInfoScreenHandler::GaiaInfoScreenHandler() : BaseScreenHandler(kScreenId) {}
+
+GaiaInfoScreenHandler::~GaiaInfoScreenHandler() = default;
+
+// Add localized values that you want to propagate to the JS side here.
+void GaiaInfoScreenHandler::DeclareLocalizedValues(
+    ::login::LocalizedValuesBuilder* builder) {
+  builder->AddF("gaiaInfoScreenTitle", IDS_GAIA_INFO_TITLE,
+                ui::GetChromeOSDeviceTypeResourceId());
+  builder->Add("gaiaInfoScreenDescription", IDS_GAIA_INFO_DESCRIPTION);
+}
+
+void GaiaInfoScreenHandler::Show() {
+  ShowInWebUI();
+}
+
+}  // namespace ash
diff --git a/chrome/browser/ui/webui/ash/login/gaia_info_screen_handler.h b/chrome/browser/ui/webui/ash/login/gaia_info_screen_handler.h
new file mode 100644
index 0000000..0f89054
--- /dev/null
+++ b/chrome/browser/ui/webui/ash/login/gaia_info_screen_handler.h
@@ -0,0 +1,51 @@
+// Copyright 2023 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_UI_WEBUI_ASH_LOGIN_GAIA_INFO_SCREEN_HANDLER_H_
+#define CHROME_BROWSER_UI_WEBUI_ASH_LOGIN_GAIA_INFO_SCREEN_HANDLER_H_
+
+#include "base/memory/weak_ptr.h"
+#include "base/values.h"
+#include "chrome/browser/ui/webui/ash/login/base_screen_handler.h"
+
+namespace ash {
+
+class GaiaInfoScreen;
+
+// Interface for dependency injection between GaiaInfoScreen and its
+// WebUI representation.
+class GaiaInfoScreenView : public base::SupportsWeakPtr<GaiaInfoScreenView> {
+ public:
+  inline constexpr static StaticOobeScreenId kScreenId{"gaia-info",
+                                                       "GaiaInfoScreen"};
+
+  virtual ~GaiaInfoScreenView() = default;
+
+  // Shows the contents of the screen.
+  virtual void Show() = 0;
+};
+
+class GaiaInfoScreenHandler : public BaseScreenHandler,
+                              public GaiaInfoScreenView {
+ public:
+  using TView = GaiaInfoScreenView;
+
+  GaiaInfoScreenHandler();
+
+  GaiaInfoScreenHandler(const GaiaInfoScreenHandler&) = delete;
+  GaiaInfoScreenHandler& operator=(const GaiaInfoScreenHandler&) = delete;
+
+  ~GaiaInfoScreenHandler() override;
+
+  // BaseScreenHandler:
+  void DeclareLocalizedValues(
+      ::login::LocalizedValuesBuilder* builder) override;
+
+  // GaiaInfoScreenView:
+  void Show() override;
+};
+
+}  // namespace ash
+
+#endif  // CHROME_BROWSER_UI_WEBUI_ASH_LOGIN_GAIA_INFO_SCREEN_HANDLER_H_
diff --git a/chrome/browser/ui/webui/ash/login/gaia_screen_handler.cc b/chrome/browser/ui/webui/ash/login/gaia_screen_handler.cc
index 4226632..8759bc56 100644
--- a/chrome/browser/ui/webui/ash/login/gaia_screen_handler.cc
+++ b/chrome/browser/ui/webui/ash/login/gaia_screen_handler.cc
@@ -1171,6 +1171,10 @@
   gaia_path_ = gaia_path;
 }
 
+GaiaScreenHandler::GaiaPath GaiaScreenHandler::GetGaiaPath() {
+  return gaia_path_;
+}
+
 void GaiaScreenHandler::LoadGaiaAsync(const AccountId& account_id) {
   // TODO(https://crbug.com/1317991): Investigate why the call is making Gaia
   // loading slowly.
diff --git a/chrome/browser/ui/webui/ash/login/gaia_screen_handler.h b/chrome/browser/ui/webui/ash/login/gaia_screen_handler.h
index a499da5..c17f578 100644
--- a/chrome/browser/ui/webui/ash/login/gaia_screen_handler.h
+++ b/chrome/browser/ui/webui/ash/login/gaia_screen_handler.h
@@ -83,6 +83,8 @@
   virtual void Hide() = 0;
   // Sets Gaia path for sign-in, child sign-in or child sign-up.
   virtual void SetGaiaPath(GaiaPath gaia_path) = 0;
+  // Returns the currently set Gaia path
+  virtual GaiaPath GetGaiaPath() = 0;
   // Show error UI at the end of GAIA flow when user is not allowlisted.
   virtual void ShowAllowlistCheckFailedError() = 0;
   // Reloads authenticator.
@@ -142,6 +144,7 @@
   void Show() override;
   void Hide() override;
   void SetGaiaPath(GaiaPath gaia_path) override;
+  GaiaPath GetGaiaPath() override;
   void ShowAllowlistCheckFailedError() override;
   void ReloadGaiaAuthenticator() override;
   void SetReauthRequestToken(const std::string& reauth_request_token) override;
diff --git a/chrome/browser/ui/webui/ash/login/oobe_ui.cc b/chrome/browser/ui/webui/ash/login/oobe_ui.cc
index 1566f24..1308a7ef 100644
--- a/chrome/browser/ui/webui/ash/login/oobe_ui.cc
+++ b/chrome/browser/ui/webui/ash/login/oobe_ui.cc
@@ -67,6 +67,7 @@
 #include "chrome/browser/ui/webui/ash/login/error_screen_handler.h"
 #include "chrome/browser/ui/webui/ash/login/family_link_notice_screen_handler.h"
 #include "chrome/browser/ui/webui/ash/login/fingerprint_setup_screen_handler.h"
+#include "chrome/browser/ui/webui/ash/login/gaia_info_screen_handler.h"
 #include "chrome/browser/ui/webui/ash/login/gaia_password_changed_screen_handler.h"
 #include "chrome/browser/ui/webui/ash/login/gaia_screen_handler.h"
 #include "chrome/browser/ui/webui/ash/login/gesture_navigation_screen_handler.h"
@@ -285,6 +286,8 @@
   source->AddBoolean("isOobeJellyEnabled", features::IsOobeJellyEnabled());
   // TODO (b/269117729) Cleanup OobeSimon
   source->AddBoolean("isOobeSimonEnabled", features::IsOobeSimonEnabled());
+  source->AddBoolean("isOobeGaiaInfoScreenEnabled",
+                     features::IsOobeGaiaInfoScreenEnabled());
   source->AddBoolean("isChoobeEnabled", features::IsOobeChoobeEnabled());
   source->AddBoolean(
       "isArcVmDataMigrationEnabled",
@@ -441,6 +444,10 @@
   auto password_change_handler =
       std::make_unique<ActiveDirectoryPasswordChangeScreenHandler>();
 
+  if (features::IsOobeGaiaInfoScreenEnabled()) {
+    AddScreenHandler(std::make_unique<GaiaInfoScreenHandler>());
+  }
+
   AddScreenHandler(std::make_unique<GaiaScreenHandler>(network_state_informer_,
                                                        error_screen));
 
diff --git a/chrome/browser/ui/webui/ash/notification_tester/notification_tester_handler.cc b/chrome/browser/ui/webui/ash/notification_tester/notification_tester_handler.cc
index c0a4ba0..a59fbd32 100644
--- a/chrome/browser/ui/webui/ash/notification_tester/notification_tester_handler.cc
+++ b/chrome/browser/ui/webui/ash/notification_tester/notification_tester_handler.cc
@@ -117,11 +117,11 @@
       base::UTF8ToUTF16(*display_source), origin_url, notifier_id,
       optional_fields, delegate);
 
-  ui::ColorId color_id = cros_tokens::kCrosSysOnPrimary;
+  ui::ColorId color_id = cros_tokens::kCrosSysPrimary;
   if (chromeos::features::IsJellyEnabled()) {
     switch (warning_level) {
       case message_center::SystemNotificationWarningLevel::NORMAL:
-        color_id = cros_tokens::kCrosSysOnPrimary;
+        color_id = cros_tokens::kCrosSysPrimary;
         break;
       case message_center::SystemNotificationWarningLevel::WARNING:
         color_id = cros_tokens::kCrosSysWarning;
diff --git a/chrome/browser/ui/webui/settings/site_settings_handler.cc b/chrome/browser/ui/webui/settings/site_settings_handler.cc
index d97099b..aed6170 100644
--- a/chrome/browser/ui/webui/settings/site_settings_handler.cc
+++ b/chrome/browser/ui/webui/settings/site_settings_handler.cc
@@ -55,9 +55,13 @@
 #include "chrome/browser/ui/webui/settings/site_settings_helper.h"
 #include "chrome/browser/usb/usb_chooser_context.h"
 #include "chrome/browser/usb/usb_chooser_context_factory.h"
+#include "chrome/browser/web_applications/isolated_web_apps/isolated_web_app_url_info.h"
+#include "chrome/browser/web_applications/web_app_provider.h"
+#include "chrome/browser/web_applications/web_app_registrar.h"
 #include "chrome/common/chrome_features.h"
 #include "chrome/common/extensions/manifest_handlers/app_launch_info.h"
 #include "chrome/common/pref_names.h"
+#include "chrome/common/url_constants.h"
 #include "chrome/grit/generated_resources.h"
 #include "components/browsing_data/content/browsing_data_model.h"
 #include "components/browsing_topics/browsing_topics_service.h"
@@ -662,6 +666,24 @@
   return is_minimal_engagement || is_low_engagement;
 }
 
+base::Value::Dict CreateZoomLevelException(
+    const std::string& host_or_spec,
+    const std::string& origin_for_favicon,
+    const std::string& display_name,
+    double zoom) {
+  base::Value::Dict exception;
+  exception.Set(site_settings::kHostOrSpec, host_or_spec);
+  exception.Set(site_settings::kOriginForFavicon, origin_for_favicon);
+  exception.Set(site_settings::kDisplayName, display_name);
+
+  // Calculate the zoom percent from the factor. Round up to the nearest
+  // whole number.
+  int zoom_percent =
+      static_cast<int>(blink::PageZoomLevelToZoomFactor(zoom) * 100 + 0.5);
+  exception.Set(kZoom, base::FormatPercent(zoom_percent));
+  return exception;
+}
+
 }  // namespace
 
 // static
@@ -907,15 +929,22 @@
       ObserveSourcesForProfile(primary_otr_profile);
   }
 
-  // Here we only subscribe to the HostZoomMap for the default storage partition
-  // since we don't allow the user to manage the zoom levels for apps.
-  // We're only interested in zoom-levels that are persisted, since the user
-  // is given the opportunity to view/delete these in the content-settings page.
-  host_zoom_map_subscription_ =
+  // Listen for zoom changes in the default StoragePartition and the primary
+  // StoragePartition of all installed Isolated Web Apps.
+  auto zoom_changed_callback = base::BindRepeating(
+      &SiteSettingsHandler::OnZoomLevelChanged, base::Unretained(this));
+  host_zoom_map_subscriptions_.push_back(
       content::HostZoomMap::GetDefaultForBrowserContext(profile_)
-          ->AddZoomLevelChangedCallback(
-              base::BindRepeating(&SiteSettingsHandler::OnZoomLevelChanged,
-                                  base::Unretained(this)));
+          ->AddZoomLevelChangedCallback(zoom_changed_callback));
+  for (const web_app::IsolatedWebAppUrlInfo& iwa_url_info :
+       site_settings::GetInstalledIsolatedWebApps(profile_)) {
+    content::StoragePartition* iwa_storage_partition =
+        profile_->GetStoragePartition(
+            iwa_url_info.storage_partition_config(profile_));
+    host_zoom_map_subscriptions_.push_back(
+        content::HostZoomMap::GetForStoragePartition(iwa_storage_partition)
+            ->AddZoomLevelChangedCallback(zoom_changed_callback));
+  }
 
   pref_change_registrar_ = std::make_unique<PrefChangeRegistrar>();
   pref_change_registrar_->Init(profile_->GetPrefs());
@@ -936,7 +965,7 @@
 void SiteSettingsHandler::OnJavascriptDisallowed() {
   observations_.RemoveAllObservations();
   chooser_observations_.RemoveAllObservations();
-  host_zoom_map_subscription_ = {};
+  host_zoom_map_subscriptions_.clear();
   pref_change_registrar_->Remove(prefs::kBlockAutoplayEnabled);
   pref_change_registrar_->Remove(prefs::kCookieControlsMode);
   observed_profiles_.RemoveAllObservations();
@@ -2053,6 +2082,40 @@
 
   base::Value::List zoom_levels_exceptions;
 
+  // Show any non-default Isolated Web App zoom levels at the top of the page.
+  auto* web_app_provider = web_app::WebAppProvider::GetForWebApps(profile_);
+  if (web_app_provider) {
+    const web_app::WebAppRegistrar& registrar =
+        web_app_provider->registrar_unsafe();
+    for (const web_app::IsolatedWebAppUrlInfo& iwa_url_info :
+         site_settings::GetInstalledIsolatedWebApps(profile_)) {
+      content::StoragePartition* iwa_storage_partition =
+          profile_->GetStoragePartition(
+              iwa_url_info.storage_partition_config(profile_));
+      auto* host_zoom_map =
+          content::HostZoomMap::GetForStoragePartition(iwa_storage_partition);
+      double iwa_zoom = host_zoom_map->GetZoomLevelForHostAndScheme(
+          chrome::kIsolatedAppScheme, iwa_url_info.origin().host());
+      if (iwa_zoom == host_zoom_map->GetDefaultZoomLevel()) {
+        continue;
+      }
+
+      zoom_levels_exceptions.Append(CreateZoomLevelException(
+          iwa_url_info.origin().Serialize(), iwa_url_info.origin().Serialize(),
+          registrar.GetAppShortName(iwa_url_info.app_id()), iwa_zoom));
+    }
+
+    // Sort by app name.
+    std::sort(zoom_levels_exceptions.begin(), zoom_levels_exceptions.end(),
+              [](const base::Value& a, const base::Value& b) {
+                const std::string& name_a =
+                    *a.GetDict().FindString(site_settings::kDisplayName);
+                const std::string& name_b =
+                    *b.GetDict().FindString(site_settings::kDisplayName);
+                return name_a < name_b;
+              });
+  }
+
   content::HostZoomMap* host_zoom_map =
       content::HostZoomMap::GetDefaultForBrowserContext(profile_);
   content::HostZoomMap::ZoomLevelVector zoom_levels(
@@ -2067,32 +2130,35 @@
                const content::HostZoomMap::ZoomLevelChange& b) {
               return a.host == b.host ? a.scheme < b.scheme : a.host < b.host;
             });
+  GURL unreachable_web_data_url(content::kUnreachableWebDataURL);
   for (const auto& zoom_level : zoom_levels) {
     base::Value::Dict exception;
     switch (zoom_level.mode) {
       case content::HostZoomMap::ZOOM_CHANGED_FOR_HOST: {
-        std::string host = zoom_level.host;
-        if (host == content::kUnreachableWebDataURL) {
-          host =
+        std::string host_or_spec = zoom_level.host;
+        std::string origin_for_favicon = host_or_spec;
+        std::string display_name = host_or_spec;
+
+        if (host_or_spec == unreachable_web_data_url.host()) {
+          display_name =
               l10n_util::GetStringUTF8(IDS_ZOOMLEVELS_CHROME_ERROR_PAGES_LABEL);
         }
-        exception.Set(site_settings::kOrigin, host);
 
-        std::string display_name = host;
-        std::string origin_for_favicon = host;
         // As an optimization, only check hosts that could be an extension.
-        if (crx_file::id_util::IdIsValid(host)) {
+        if (crx_file::id_util::IdIsValid(host_or_spec)) {
           // Look up the host as an extension, if found then it is an extension.
           const extensions::Extension* extension =
               extension_registry->GetExtensionById(
-                  host, extensions::ExtensionRegistry::EVERYTHING);
+                  host_or_spec, extensions::ExtensionRegistry::EVERYTHING);
           if (extension) {
             origin_for_favicon = extension->url().spec();
             display_name = extension->name();
           }
         }
-        exception.Set(site_settings::kDisplayName, display_name);
-        exception.Set(site_settings::kOriginForFavicon, origin_for_favicon);
+
+        zoom_levels_exceptions.Append(
+            CreateZoomLevelException(host_or_spec, origin_for_favicon,
+                                     display_name, zoom_level.zoom_level));
         break;
       }
       case content::HostZoomMap::ZOOM_CHANGED_FOR_SCHEME_AND_HOST:
@@ -2102,23 +2168,6 @@
       case content::HostZoomMap::ZOOM_CHANGED_TEMPORARY_ZOOM:
         NOTREACHED();
     }
-
-    std::string setting_string =
-        content_settings::ContentSettingToString(CONTENT_SETTING_DEFAULT);
-    DCHECK(!setting_string.empty());
-
-    exception.Set(site_settings::kSetting, setting_string);
-
-    // Calculate the zoom percent from the factor. Round up to the nearest whole
-    // number.
-    int zoom_percent = static_cast<int>(
-        blink::PageZoomLevelToZoomFactor(zoom_level.zoom_level) * 100 + 0.5);
-    exception.Set(kZoom, base::FormatPercent(zoom_percent));
-    exception.Set(site_settings::kSource,
-                  site_settings::SiteSettingSourceToString(
-                      site_settings::SiteSettingSource::kPreference));
-    // Append the new entry to the list and map.
-    zoom_levels_exceptions.Append(std::move(exception));
   }
 
   FireWebUIListener("onZoomLevelsChanged", zoom_levels_exceptions);
@@ -2127,17 +2176,29 @@
 void SiteSettingsHandler::HandleRemoveZoomLevel(const base::Value::List& args) {
   CHECK_EQ(1U, args.size());
 
-  std::string origin = args[0].GetString();
+  std::string host_or_spec = args[0].GetString();
 
-  if (origin ==
-      l10n_util::GetStringUTF8(IDS_ZOOMLEVELS_CHROME_ERROR_PAGES_LABEL)) {
-    origin = content::kUnreachableWebDataURL;
+  GURL url(host_or_spec);
+  if (url.is_valid() && url.scheme() == chrome::kIsolatedAppScheme) {
+    base::expected<web_app::IsolatedWebAppUrlInfo, std::string> iwa_url_info =
+        web_app::IsolatedWebAppUrlInfo::Create(url);
+    if (!iwa_url_info.has_value()) {
+      return;
+    }
+    content::StoragePartition* iwa_storage_partition =
+        profile_->GetStoragePartition(
+            iwa_url_info->storage_partition_config(profile_));
+    auto* host_zoom_map =
+        content::HostZoomMap::GetForStoragePartition(iwa_storage_partition);
+    double default_level = host_zoom_map->GetDefaultZoomLevel();
+    host_zoom_map->SetZoomLevelForHost(url.host(), default_level);
+    return;
   }
 
-  content::HostZoomMap* host_zoom_map;
-  host_zoom_map = content::HostZoomMap::GetDefaultForBrowserContext(profile_);
+  content::HostZoomMap* host_zoom_map =
+      content::HostZoomMap::GetDefaultForBrowserContext(profile_);
   double default_level = host_zoom_map->GetDefaultZoomLevel();
-  host_zoom_map->SetZoomLevelForHost(origin, default_level);
+  host_zoom_map->SetZoomLevelForHost(host_or_spec, default_level);
 }
 
 void SiteSettingsHandler::HandleFetchBlockAutoplayStatus(
diff --git a/chrome/browser/ui/webui/settings/site_settings_handler.h b/chrome/browser/ui/webui/settings/site_settings_handler.h
index b4edc1b..f08b126e 100644
--- a/chrome/browser/ui/webui/settings/site_settings_handler.h
+++ b/chrome/browser/ui/webui/settings/site_settings_handler.h
@@ -9,6 +9,7 @@
 #include <memory>
 #include <set>
 #include <string>
+#include <vector>
 
 #include "base/gtest_prod_util.h"
 #include "base/memory/raw_ptr.h"
@@ -215,6 +216,9 @@
   FRIEND_TEST_ALL_PREFIXES(SiteSettingsHandlerTest, HandleGetExtensionName);
 #endif
   FRIEND_TEST_ALL_PREFIXES(SiteSettingsHandlerTest, IsolatedWebAppUsageInfo);
+  FRIEND_TEST_ALL_PREFIXES(SiteSettingsHandlerIsolatedWebAppTest, ZoomLevel);
+  FRIEND_TEST_ALL_PREFIXES(SiteSettingsHandlerIsolatedWebAppTest,
+                           ZoomLevelsSortedByAppName);
 
   // Rebuilds the BrowsingDataModel & CookiesTreeModel. Pending requests are
   // serviced when both models are built.
@@ -421,7 +425,7 @@
       observed_profiles_{this};
 
   // Keeps track of events related to zooming.
-  base::CallbackListSubscription host_zoom_map_subscription_;
+  std::vector<base::CallbackListSubscription> host_zoom_map_subscriptions_;
 
   // The origin for which to fetch usage.
   std::string usage_origin_;
diff --git a/chrome/browser/ui/webui/settings/site_settings_handler_unittest.cc b/chrome/browser/ui/webui/settings/site_settings_handler_unittest.cc
index b7ca779..f922bed 100644
--- a/chrome/browser/ui/webui/settings/site_settings_handler_unittest.cc
+++ b/chrome/browser/ui/webui/settings/site_settings_handler_unittest.cc
@@ -42,6 +42,8 @@
 #include "chrome/browser/extensions/test_extension_system.h"
 #include "chrome/browser/file_system_access/chrome_file_system_access_permission_context.h"
 #include "chrome/browser/file_system_access/file_system_access_permission_context_factory.h"
+#include "chrome/browser/hid/hid_chooser_context.h"
+#include "chrome/browser/hid/hid_chooser_context_factory.h"
 #include "chrome/browser/history/history_service_factory.h"
 #include "chrome/browser/permissions/notification_permission_review_service_factory.h"
 #include "chrome/browser/permissions/notifications_engagement_service_factory.h"
@@ -49,12 +51,18 @@
 #include "chrome/browser/prefs/browser_prefs.h"
 #include "chrome/browser/privacy_sandbox/mock_privacy_sandbox_service.h"
 #include "chrome/browser/privacy_sandbox/privacy_sandbox_service_factory.h"
+#include "chrome/browser/serial/serial_chooser_context.h"
+#include "chrome/browser/serial/serial_chooser_context_factory.h"
 #include "chrome/browser/ui/browser_window.h"
 #include "chrome/browser/ui/tabs/tab_strip_model.h"
+#include "chrome/browser/ui/web_applications/test/isolated_web_app_test_utils.h"
 #include "chrome/browser/ui/webui/settings/site_settings_helper.h"
 #include "chrome/browser/usb/usb_chooser_context.h"
 #include "chrome/browser/usb/usb_chooser_context_factory.h"
+#include "chrome/browser/web_applications/isolated_web_apps/isolated_web_app_url_info.h"
+#include "chrome/browser/web_applications/test/web_app_install_test_utils.h"
 #include "chrome/browser/web_applications/web_app_helpers.h"
+#include "chrome/browser/web_applications/web_app_id.h"
 #include "chrome/common/chrome_features.h"
 #include "chrome/common/pref_names.h"
 #include "chrome/grit/generated_resources.h"
@@ -93,6 +101,7 @@
 #include "components/sync_preferences/testing_pref_service_syncable.h"
 #include "components/ukm/test_ukm_recorder.h"
 #include "content/public/browser/browsing_data_remover.h"
+#include "content/public/browser/host_zoom_map.h"
 #include "content/public/browser/navigation_controller.h"
 #include "content/public/browser/storage_partition.h"
 #include "content/public/browser/web_ui_data_source.h"
@@ -106,7 +115,11 @@
 #include "extensions/common/extension_builder.h"
 #include "mojo/public/cpp/bindings/pending_remote.h"
 #include "ppapi/buildflags/buildflags.h"
+#include "services/device/public/cpp/test/fake_hid_manager.h"
+#include "services/device/public/cpp/test/fake_serial_port_manager.h"
 #include "services/device/public/cpp/test/fake_usb_device_manager.h"
+#include "services/device/public/mojom/hid.mojom.h"
+#include "services/device/public/mojom/serial.mojom.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
@@ -127,20 +140,6 @@
 #include "chrome/browser/plugins/chrome_plugin_service_filter.h"
 #endif
 
-#if !BUILDFLAG(IS_ANDROID)
-#include "chrome/browser/hid/hid_chooser_context.h"
-#include "chrome/browser/hid/hid_chooser_context_factory.h"
-#include "chrome/browser/serial/serial_chooser_context.h"
-#include "chrome/browser/serial/serial_chooser_context_factory.h"
-#include "chrome/browser/ui/web_applications/test/isolated_web_app_test_utils.h"
-#include "chrome/browser/web_applications/test/web_app_install_test_utils.h"
-#include "chrome/browser/web_applications/web_app_id.h"
-#include "services/device/public/cpp/test/fake_hid_manager.h"
-#include "services/device/public/cpp/test/fake_serial_port_manager.h"
-#include "services/device/public/mojom/hid.mojom.h"
-#include "services/device/public/mojom/serial.mojom.h"
-#endif
-
 namespace {
 
 using ::base::test::ParseJson;
@@ -544,8 +543,13 @@
     EXPECT_EQ(expected_incognito, data.arg2()->GetBool());
   }
 
-  void ValidateZoom(const std::string& expected_host,
-                    const std::string& expected_zoom,
+  struct ZoomLevel {
+    std::string host_or_spec;
+    std::string display_name;
+    std::string zoom;
+  };
+
+  void ValidateZoom(const std::vector<ZoomLevel>& zoom_levels,
                     size_t expected_total_calls) {
     EXPECT_EQ(expected_total_calls, web_ui()->call_data().size());
 
@@ -557,20 +561,22 @@
 
     ASSERT_TRUE(data.arg2()->is_list());
     const base::Value::List& exceptions = data.arg2()->GetList();
-    if (expected_host.empty()) {
-      EXPECT_EQ(0U, exceptions.size());
-    } else {
-      EXPECT_EQ(1U, exceptions.size());
+    ASSERT_EQ(zoom_levels.size(), exceptions.size());
+    for (size_t i = 0; i < zoom_levels.size(); i++) {
+      const ZoomLevel& zoom_level = zoom_levels[i];
+      const base::Value::Dict& exception = exceptions[i].GetDict();
 
-      const base::Value::Dict& exception = exceptions[0].GetDict();
+      const std::string* host_or_spec = exception.FindString("hostOrSpec");
+      ASSERT_TRUE(host_or_spec);
+      ASSERT_EQ(zoom_level.host_or_spec, *host_or_spec);
 
-      const std::string* host = exception.FindString("origin");
-      ASSERT_TRUE(host);
-      ASSERT_EQ(expected_host, *host);
+      const std::string* display_name = exception.FindString("displayName");
+      ASSERT_TRUE(display_name);
+      ASSERT_EQ(zoom_level.display_name, *display_name);
 
       const std::string* zoom = exception.FindString("zoom");
       ASSERT_TRUE(zoom);
-      ASSERT_EQ(expected_zoom, *zoom);
+      ASSERT_EQ(zoom_level.zoom, *zoom);
     }
   }
 
@@ -1545,52 +1551,6 @@
   }
 }
 
-#if !BUILDFLAG(IS_ANDROID)
-TEST_F(SiteSettingsHandlerTest, AllSitesDisplaysIsolatedWebAppName) {
-  std::string iwa_hostname =
-      "aerugqztij5biqquuk3mfwpsaibuegaqcitgfchwuosuofdjabzqaaic";
-  GURL iwa_url("isolated-app://" + iwa_hostname);
-  GURL https_url("https://" + iwa_hostname);
-
-  // Install an IWA at |iwa_url|.
-  web_app::test::AwaitStartWebAppProviderAndSubsystems(profile());
-  web_app::AppId app_id =
-      web_app::AddDummyIsolatedAppToRegistry(profile(), iwa_url, "IWA Name");
-  RegisterWebApp(profile(),
-                 MakeApp(app_id, apps::AppType::kWeb, iwa_url.spec(),
-                         apps::Readiness::kReady, apps::InstallReason::kUser));
-
-  SetupModelsWithIsolatedWebAppData(iwa_url.spec(), 50);
-  HostContentSettingsMap* map =
-      HostContentSettingsMapFactory::GetForProfile(profile());
-  map->SetContentSettingDefaultScope(iwa_url, iwa_url,
-                                     ContentSettingsType::NOTIFICATIONS,
-                                     CONTENT_SETTING_BLOCK);
-  map->SetContentSettingDefaultScope(https_url, https_url,
-                                     ContentSettingsType::NOTIFICATIONS,
-                                     CONTENT_SETTING_BLOCK);
-
-  base::Value::List site_groups = GetOnStorageFetchedSentList();
-
-  ASSERT_EQ(site_groups.size(), 2u);
-  const base::Value::Dict& group1 = site_groups[0].GetDict();
-  const base::Value::Dict& origin1 =
-      CHECK_DEREF(group1.FindList("origins"))[0].GetDict();
-  EXPECT_EQ(CHECK_DEREF(group1.FindString("etldPlus1")), iwa_url);
-  EXPECT_EQ(CHECK_DEREF(group1.FindString("displayName")), "IWA Name");
-  EXPECT_EQ(CHECK_DEREF(origin1.FindString("origin")), iwa_url);
-  EXPECT_EQ(origin1.FindDouble("usage").value(), 50.0);
-
-  const base::Value::Dict& group2 = site_groups[1].GetDict();
-  const base::Value::Dict& origin2 =
-      CHECK_DEREF(group2.FindList("origins"))[0].GetDict();
-  EXPECT_EQ(CHECK_DEREF(group2.FindString("etldPlus1")), iwa_hostname);
-  EXPECT_EQ(CHECK_DEREF(group2.FindString("displayName")), iwa_hostname);
-  EXPECT_EQ(CHECK_DEREF(origin2.FindString("origin")), https_url);
-  EXPECT_EQ(origin2.FindDouble("usage").value(), 0.0);
-}
-#endif  // !BUILDFLAG(IS_ANDROID)
-
 TEST_F(SiteSettingsHandlerTest, IncognitoExceptions) {
   constexpr char kOriginToBlock[] = "https://www.blocked.com:443";
 
@@ -2155,27 +2115,161 @@
 }
 
 TEST_F(SiteSettingsHandlerTest, ZoomLevels) {
-  std::string host("http://www.google.com");
+  std::string http_host("www.google.com");
+  std::string error_host("chromewebdata");
+  std::string data_url("data:text/plain;base64,SGVsbG8sIFdvcmxkIQ==");
   double zoom_level = 1.1;
 
   content::HostZoomMap* host_zoom_map =
       content::HostZoomMap::GetDefaultForBrowserContext(profile());
-  host_zoom_map->SetZoomLevelForHost(host, zoom_level);
-  ValidateZoom(host, "122%", 1U);
+  host_zoom_map->SetZoomLevelForHost(http_host, zoom_level);
+  host_zoom_map->SetZoomLevelForHost(error_host, zoom_level);
+  host_zoom_map->SetZoomLevelForHost(data_url, zoom_level);
+  ValidateZoom({{error_host, "(Chrome error pages)", "122%"},
+                {data_url, data_url, "122%"},
+                {http_host, http_host, "122%"}},
+               3U);
 
   base::Value::List args;
   handler()->HandleFetchZoomLevels(args);
-  ValidateZoom(host, "122%", 2U);
+  ValidateZoom({{error_host, "(Chrome error pages)", "122%"},
+                {data_url, data_url, "122%"},
+                {http_host, http_host, "122%"}},
+               4U);
 
-  args.Append("http://www.google.com");
+  args.Append(http_host);
   handler()->HandleRemoveZoomLevel(args);
-  ValidateZoom("", "", 3U);
+  args.front() = base::Value(error_host);
+  handler()->HandleRemoveZoomLevel(args);
+  args.front() = base::Value(data_url);
+  handler()->HandleRemoveZoomLevel(args);
+  ValidateZoom({}, 7U);
 
   double default_level = host_zoom_map->GetDefaultZoomLevel();
-  double level = host_zoom_map->GetZoomLevelForHostAndScheme("http", host);
+  double level = host_zoom_map->GetZoomLevelForHostAndScheme("http", http_host);
   EXPECT_EQ(default_level, level);
 }
 
+class SiteSettingsHandlerIsolatedWebAppTest : public SiteSettingsHandlerTest {
+ public:
+  void SetUp() override {
+    web_app::test::AwaitStartWebAppProviderAndSubsystems(profile());
+    InstallIsolatedWebApp(iwa_url(), "IWA Name");
+
+    SiteSettingsHandlerTest::SetUp();
+  }
+
+ protected:
+  GURL iwa_url() {
+    return GURL(
+        "isolated-app://"
+        "aerugqztij5biqquuk3mfwpsaibuegaqcitgfchwuosuofdjabzqaaic");
+  }
+
+  web_app::AppId InstallIsolatedWebApp(const GURL& iwa_url,
+                                       const std::string& name) {
+    web_app::AppId app_id =
+        web_app::AddDummyIsolatedAppToRegistry(profile(), iwa_url, name);
+    RegisterWebApp(profile(), MakeApp(app_id, apps::AppType::kWeb,
+                                      iwa_url.spec(), apps::Readiness::kReady,
+                                      apps::InstallReason::kUser));
+    return app_id;
+  }
+
+  content::HostZoomMap* GetIwaHostZoomMap(const GURL& url) {
+    auto url_info = *web_app::IsolatedWebAppUrlInfo::Create(url);
+    content::StoragePartition* iwa_partition = profile()->GetStoragePartition(
+        url_info.storage_partition_config(profile()));
+    return content::HostZoomMap::GetForStoragePartition(iwa_partition);
+  }
+};
+
+TEST_F(SiteSettingsHandlerIsolatedWebAppTest, AllSitesDisplaysAppName) {
+  GURL https_url("https://" + iwa_url().host());
+
+  SetupModelsWithIsolatedWebAppData(iwa_url().spec(), 50);
+  HostContentSettingsMap* map =
+      HostContentSettingsMapFactory::GetForProfile(profile());
+  map->SetContentSettingDefaultScope(iwa_url(), iwa_url(),
+                                     ContentSettingsType::NOTIFICATIONS,
+                                     CONTENT_SETTING_BLOCK);
+  map->SetContentSettingDefaultScope(https_url, https_url,
+                                     ContentSettingsType::NOTIFICATIONS,
+                                     CONTENT_SETTING_BLOCK);
+
+  base::Value::List site_groups = GetOnStorageFetchedSentList();
+
+  ASSERT_EQ(site_groups.size(), 2u);
+  const base::Value::Dict& group1 = site_groups[0].GetDict();
+  const base::Value::Dict& origin1 =
+      CHECK_DEREF(group1.FindList("origins"))[0].GetDict();
+  EXPECT_EQ(CHECK_DEREF(group1.FindString("etldPlus1")), iwa_url());
+  EXPECT_EQ(CHECK_DEREF(group1.FindString("displayName")), "IWA Name");
+  EXPECT_EQ(CHECK_DEREF(origin1.FindString("origin")), iwa_url());
+  EXPECT_EQ(origin1.FindDouble("usage").value(), 50.0);
+
+  const base::Value::Dict& group2 = site_groups[1].GetDict();
+  const base::Value::Dict& origin2 =
+      CHECK_DEREF(group2.FindList("origins"))[0].GetDict();
+  EXPECT_EQ(CHECK_DEREF(group2.FindString("etldPlus1")), iwa_url().host());
+  EXPECT_EQ(CHECK_DEREF(group2.FindString("displayName")), iwa_url().host());
+  EXPECT_EQ(CHECK_DEREF(origin2.FindString("origin")), https_url);
+  EXPECT_EQ(origin2.FindDouble("usage").value(), 0.0);
+}
+
+TEST_F(SiteSettingsHandlerIsolatedWebAppTest, ZoomLevel) {
+  content::HostZoomMap* iwa_host_zoom_map = GetIwaHostZoomMap(iwa_url());
+
+  std::string host_or_spec = url::Origin::Create(iwa_url()).Serialize();
+  iwa_host_zoom_map->SetZoomLevelForHost(iwa_url().host(), 1.1);
+  ValidateZoom({{host_or_spec, "IWA Name", "122%"}}, 1U);
+
+  base::Value::List args;
+  handler()->HandleFetchZoomLevels(args);
+  ValidateZoom({{host_or_spec, "IWA Name", "122%"}}, 2U);
+
+  args.Append(host_or_spec);
+  handler()->HandleRemoveZoomLevel(args);
+  ValidateZoom({}, 3U);
+
+  double default_level = iwa_host_zoom_map->GetDefaultZoomLevel();
+  double level = iwa_host_zoom_map->GetZoomLevelForHostAndScheme(
+      "isolated-app", iwa_url().host());
+  EXPECT_EQ(default_level, level);
+}
+
+TEST_F(SiteSettingsHandlerIsolatedWebAppTest, ZoomLevelsSortedByAppName) {
+  GetIwaHostZoomMap(iwa_url())->SetZoomLevelForHost(iwa_url().host(), 1.1);
+
+  // Install 3 more IWAs.
+  GURL iwa3_url(
+      "isolated-app://"
+      "cerugqztij5biqquuk3mfwpsaibuegaqcitgfchwuosuofdjabzqaaic");
+  InstallIsolatedWebApp(iwa3_url, "IWA Name 3");
+  GetIwaHostZoomMap(iwa3_url)->SetZoomLevelForHost(iwa3_url.host(), 1.1);
+
+  GURL iwa2_url(
+      "isolated-app://"
+      "berugqztij5biqquuk3mfwpsaibuegaqcitgfchwuosuofdjabzqaaic");
+  InstallIsolatedWebApp(iwa2_url, "IWA Name 2");
+  GetIwaHostZoomMap(iwa2_url)->SetZoomLevelForHost(iwa2_url.host(), 1.1);
+
+  // Don't set a zoom for this app to make sure it's not in the list.
+  GURL iwa4_url(
+      "isolated-app://"
+      "derugqztij5biqquuk3mfwpsaibuegaqcitgfchwuosuofdjabzqaaic");
+  InstallIsolatedWebApp(iwa4_url, "IWA Name 4");
+
+  base::Value::List args;
+  handler()->HandleFetchZoomLevels(args);
+
+  ValidateZoom(
+      {{url::Origin::Create(iwa_url()).Serialize(), "IWA Name", "122%"},
+       {url::Origin::Create(iwa2_url).Serialize(), "IWA Name 2", "122%"},
+       {url::Origin::Create(iwa3_url).Serialize(), "IWA Name 3", "122%"}},
+      2U);
+}
+
 class SiteSettingsHandlerInfobarTest : public BrowserWithTestWindowTest {
  public:
   SiteSettingsHandlerInfobarTest()
@@ -3939,7 +4033,6 @@
   TestHandleSetOriginPermissionsPolicyOnly();
 }
 
-#if !BUILDFLAG(IS_ANDROID)
 class SiteSettingsHandlerHidTest
     : public SiteSettingsHandlerChooserExceptionTest {
  protected:
@@ -4384,7 +4477,6 @@
 TEST_F(SiteSettingsHandlerSerialTest, HandleSetOriginPermissionsPolicyOnly) {
   TestHandleSetOriginPermissionsPolicyOnly();
 }
-#endif  // !BUILDFLAG(IS_ANDROID)
 
 class SiteSettingsHandlerUsbTest
     : public SiteSettingsHandlerChooserExceptionTest {
diff --git a/chrome/browser/ui/webui/settings/site_settings_helper.cc b/chrome/browser/ui/webui/settings/site_settings_helper.cc
index 3001dd05..f058536 100644
--- a/chrome/browser/ui/webui/settings/site_settings_helper.cc
+++ b/chrome/browser/ui/webui/settings/site_settings_helper.cc
@@ -34,6 +34,10 @@
 #include "chrome/browser/ui/url_identity.h"
 #include "chrome/browser/usb/usb_chooser_context.h"
 #include "chrome/browser/usb/usb_chooser_context_factory.h"
+#include "chrome/browser/web_applications/isolated_web_apps/isolated_web_app_url_info.h"
+#include "chrome/browser/web_applications/web_app.h"
+#include "chrome/browser/web_applications/web_app_provider.h"
+#include "chrome/browser/web_applications/web_app_registrar.h"
 #include "chrome/common/chrome_features.h"
 #include "chrome/common/pref_names.h"
 #include "chrome/common/url_constants.h"
@@ -1031,4 +1035,26 @@
   return exceptions;
 }
 
+std::vector<web_app::IsolatedWebAppUrlInfo> GetInstalledIsolatedWebApps(
+    Profile* profile) {
+  auto* web_app_provider = web_app::WebAppProvider::GetForWebApps(profile);
+  if (!web_app_provider) {
+    return {};
+  }
+
+  std::vector<web_app::IsolatedWebAppUrlInfo> iwas;
+  web_app::WebAppRegistrar& registrar = web_app_provider->registrar_unsafe();
+  for (const web_app::WebApp& web_app : registrar.GetApps()) {
+    if (!registrar.IsIsolated(web_app.app_id())) {
+      continue;
+    }
+    base::expected<web_app::IsolatedWebAppUrlInfo, std::string> url_info =
+        web_app::IsolatedWebAppUrlInfo::Create(web_app.scope());
+    if (url_info.has_value()) {
+      iwas.push_back(*url_info);
+    }
+  }
+  return iwas;
+}
+
 }  // namespace site_settings
diff --git a/chrome/browser/ui/webui/settings/site_settings_helper.h b/chrome/browser/ui/webui/settings/site_settings_helper.h
index cf0cca5..7885f6f 100644
--- a/chrome/browser/ui/webui/settings/site_settings_helper.h
+++ b/chrome/browser/ui/webui/settings/site_settings_helper.h
@@ -32,6 +32,10 @@
 class ObjectPermissionContextBase;
 }
 
+namespace web_app {
+class IsolatedWebAppUrlInfo;
+}  // namespace web_app
+
 namespace site_settings {
 
 // Maps from a secondary pattern to a setting.
@@ -47,29 +51,30 @@
 using ChooserExceptionDetails = std::set<std::tuple<GURL, std::string, bool>>;
 
 constexpr char kChooserType[] = "chooserType";
-constexpr char kDisplayName[] = "displayName";
-constexpr char kEmbeddingOrigin[] = "embeddingOrigin";
-constexpr char kIncognito[] = "incognito";
-constexpr char kObject[] = "object";
-constexpr char kDisabled[] = "disabled";
-constexpr char kOrigin[] = "origin";
-constexpr char kOriginForFavicon[] = "originForFavicon";
-constexpr char kRecentPermissions[] = "recentPermissions";
-constexpr char kSetting[] = "setting";
-constexpr char kSites[] = "sites";
-constexpr char kPolicyIndicator[] = "indicator";
-constexpr char kSource[] = "source";
-constexpr char kType[] = "type";
-constexpr char kIsDirectory[] = "isDirectory";
-constexpr char kIsEmbargoed[] = "isEmbargoed";
-constexpr char kIsWritable[] = "isWritable";
 constexpr char kDirectoryReadGrants[] = "directoryReadGrants";
 constexpr char kDirectoryWriteGrants[] = "directoryWriteGrants";
+constexpr char kDisabled[] = "disabled";
+constexpr char kDisplayName[] = "displayName";
+constexpr char kEmbeddingOrigin[] = "embeddingOrigin";
 constexpr char kFilePath[] = "filePath";
 constexpr char kFileReadGrants[] = "fileReadGrants";
 constexpr char kFileWriteGrants[] = "fileWriteGrants";
+constexpr char kHostOrSpec[] = "hostOrSpec";
+constexpr char kIncognito[] = "incognito";
+constexpr char kIsDirectory[] = "isDirectory";
+constexpr char kIsEmbargoed[] = "isEmbargoed";
+constexpr char kIsWritable[] = "isWritable";
 constexpr char kNotificationInfoString[] = "notificationInfoString";
+constexpr char kObject[] = "object";
+constexpr char kOrigin[] = "origin";
+constexpr char kOriginForFavicon[] = "originForFavicon";
 constexpr char kPermissions[] = "permissions";
+constexpr char kPolicyIndicator[] = "indicator";
+constexpr char kRecentPermissions[] = "recentPermissions";
+constexpr char kSetting[] = "setting";
+constexpr char kSites[] = "sites";
+constexpr char kSource[] = "source";
+constexpr char kType[] = "type";
 
 enum class SiteSettingSource {
   kAllowlist,
@@ -209,6 +214,10 @@
                                   const GURL& url,
                                   bool hostname_only);
 
+// Returns data about all currently installed Isolated Web Apps.
+std::vector<web_app::IsolatedWebAppUrlInfo> GetInstalledIsolatedWebApps(
+    Profile* profile);
+
 }  // namespace site_settings
 
 #endif  // CHROME_BROWSER_UI_WEBUI_SETTINGS_SITE_SETTINGS_HELPER_H_
diff --git a/chrome/build/linux.pgo.txt b/chrome/build/linux.pgo.txt
index afc036fe..0d65d6c 100644
--- a/chrome/build/linux.pgo.txt
+++ b/chrome/build/linux.pgo.txt
@@ -1 +1 @@
-chrome-linux-main-1683501802-be192477e10a0647affa28385c60734743cf52db.profdata
+chrome-linux-main-1683568773-59f250973d47b83c31e57d616cc535383d1091e9.profdata
diff --git a/chrome/build/mac-arm.pgo.txt b/chrome/build/mac-arm.pgo.txt
index c4b873bd..14e7e55a 100644
--- a/chrome/build/mac-arm.pgo.txt
+++ b/chrome/build/mac-arm.pgo.txt
@@ -1 +1 @@
-chrome-mac-arm-main-1683568773-a0a24e4eec93cc36e279e0b09a0ce067751292c3.profdata
+chrome-mac-arm-main-1683575964-3fd4c6a5f61418dcad11c59abd968a7747451027.profdata
diff --git a/chrome/build/mac.pgo.txt b/chrome/build/mac.pgo.txt
index a8395fe..d0b854d 100644
--- a/chrome/build/mac.pgo.txt
+++ b/chrome/build/mac.pgo.txt
@@ -1 +1 @@
-chrome-mac-main-1683546861-881df410f4cc8167c15282f0b54cf4f016d7291c.profdata
+chrome-mac-main-1683568773-a5d8aa6da29a2ada9329fdd8c4642021945df46d.profdata
diff --git a/chrome/build/win64.pgo.txt b/chrome/build/win64.pgo.txt
index 1c72a55..7867f8a6 100644
--- a/chrome/build/win64.pgo.txt
+++ b/chrome/build/win64.pgo.txt
@@ -1 +1 @@
-chrome-win64-main-1683557303-7b4ad0cf7939ed5e88558c9975ea60f4e493d300.profdata
+chrome-win64-main-1683579505-c7d03385e53d1debb9556705dd2dc0b71ca4e906.profdata
diff --git a/chrome/common/BUILD.gn b/chrome/common/BUILD.gn
index 3ec83e41..8e13f7a 100644
--- a/chrome/common/BUILD.gn
+++ b/chrome/common/BUILD.gn
@@ -73,6 +73,7 @@
     public_deps += [ "//chrome/install_static:install_static_util" ]
   } else if (is_mac) {
     sources += [ "channel_info_mac.mm" ]
+    configs += [ "//build/config/compiler:enable_arc" ]
   } else if (is_android) {
     sources += [ "channel_info_android.cc" ]
     public_deps += [ "//components/version_info/android:channel_getter" ]
@@ -643,6 +644,7 @@
   }
   if (is_mac) {
     sources += [ "chrome_paths_mac.mm" ]
+    configs += [ "//build/config/compiler:enable_arc" ]
   }
   if (is_win) {
     sources += [ "chrome_paths_win.cc" ]
@@ -697,7 +699,10 @@
       "mac/app_mode_common.mm",
     ]
 
-    configs += [ "//build/config/compiler:wexit_time_destructors" ]
+    configs += [
+      "//build/config/compiler:wexit_time_destructors",
+      "//build/config/compiler:enable_arc",
+    ]
 
     deps = [
       ":constants",
diff --git a/chrome/common/channel_info_mac.mm b/chrome/common/channel_info_mac.mm
index 9466a7c7..906139f 100644
--- a/chrome/common/channel_info_mac.mm
+++ b/chrome/common/channel_info_mac.mm
@@ -15,6 +15,10 @@
 #include "build/branding_buildflags.h"
 #include "components/version_info/version_info.h"
 
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
 namespace chrome {
 
 namespace {
diff --git a/chrome/common/chrome_features.cc b/chrome/common/chrome_features.cc
index a745aa7e..d51041d 100644
--- a/chrome/common/chrome_features.cc
+++ b/chrome/common/chrome_features.cc
@@ -644,6 +644,12 @@
              "HttpsFirstModeV2",
              base::FEATURE_ENABLED_BY_DEFAULT);
 
+// Enables HTTPS-First Mode for engaged sites. No-op if HttpsFirstModeV2 or
+// HTTPS-Upgrades is disabled.
+BASE_FEATURE(kHttpsFirstModeV2ForEngagedSites,
+             "HttpsFirstModeV2ForEngagedSites",
+             base::FEATURE_DISABLED_BY_DEFAULT);
+
 // Enables automatically upgrading main frame navigations to HTTPS.
 BASE_FEATURE(kHttpsUpgrades,
              "HttpsUpgrades",
diff --git a/chrome/common/chrome_features.h b/chrome/common/chrome_features.h
index a801268..1be0f7d 100644
--- a/chrome/common/chrome_features.h
+++ b/chrome/common/chrome_features.h
@@ -396,6 +396,8 @@
 COMPONENT_EXPORT(CHROME_FEATURES)
 BASE_DECLARE_FEATURE(kHttpsFirstModeForAdvancedProtectionUsers);
 COMPONENT_EXPORT(CHROME_FEATURES) BASE_DECLARE_FEATURE(kHttpsFirstModeV2);
+COMPONENT_EXPORT(CHROME_FEATURES)
+BASE_DECLARE_FEATURE(kHttpsFirstModeV2ForEngagedSites);
 COMPONENT_EXPORT(CHROME_FEATURES) BASE_DECLARE_FEATURE(kHttpsUpgrades);
 
 #if BUILDFLAG(IS_MAC)
diff --git a/chrome/common/chrome_paths_mac.mm b/chrome/common/chrome_paths_mac.mm
index 2a03264..d8ee646 100644
--- a/chrome/common/chrome_paths_mac.mm
+++ b/chrome/common/chrome_paths_mac.mm
@@ -19,11 +19,14 @@
 #include "chrome/common/chrome_constants.h"
 #include "chrome/common/chrome_paths_internal.h"
 
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
 namespace {
 
-// Return a retained (NOT autoreleased) NSBundle* as the internal
-// implementation of chrome::OuterAppBundle(), which should be the only
-// caller.
+// Return an NSBundle* as the internal implementation of
+// chrome::OuterAppBundle(), which should be the only caller.
 NSBundle* OuterAppBundleInternal() {
   @autoreleasepool {
     if (!base::mac::AmIBundled()) {
@@ -33,7 +36,7 @@
 
     if (!base::mac::IsBackgroundOnlyProcess()) {
       // Shortcut: in the browser process, just return the main app bundle.
-      return [[NSBundle mainBundle] retain];
+      return NSBundle.mainBundle;
     }
 
     // From C.app/Contents/Frameworks/C.framework/Versions/1.2.3.4, go up five
@@ -43,13 +46,13 @@
         framework_path.DirName().DirName().DirName().DirName().DirName();
     NSString* outer_app_dir_ns = base::SysUTF8ToNSString(outer_app_dir.value());
 
-    return [[NSBundle bundleWithPath:outer_app_dir_ns] retain];
+    return [NSBundle bundleWithPath:outer_app_dir_ns];
   }
 }
 
 char* ProductDirNameForBundle(NSBundle* chrome_bundle) {
   @autoreleasepool {
-    const char* product_dir_name = NULL;
+    const char* product_dir_name = nullptr;
 
     NSString* product_dir_name_ns =
         [chrome_bundle objectForInfoDictionaryKey:@"CrProductDirName"];
@@ -209,14 +212,12 @@
 }
 
 NSBundle* OuterAppBundle() {
-  // Cache this. Foundation leaks it anyway, and this should be the only call
-  // to OuterAppBundleInternal().
   static NSBundle* bundle = OuterAppBundleInternal();
   return bundle;
 }
 
 bool ProcessNeedsProfileDir(const std::string& process_type) {
-  // For now we have no reason to forbid this on other MacOS as we don't
+  // For now we have no reason to forbid this on other macOS as we don't
   // have the roaming profile troubles there.
   return true;
 }
diff --git a/chrome/common/importer/BUILD.gn b/chrome/common/importer/BUILD.gn
index 2531a4d..5872531 100644
--- a/chrome/common/importer/BUILD.gn
+++ b/chrome/common/importer/BUILD.gn
@@ -100,6 +100,7 @@
       "safari_importer_utils.h",
       "safari_importer_utils.mm",
     ]
+    configs += [ "//build/config/compiler:enable_arc" ]
   } else if (is_win) {
     sources += [
       "edge_importer_utils_win.cc",
diff --git a/chrome/common/importer/firefox_importer_utils_mac.mm b/chrome/common/importer/firefox_importer_utils_mac.mm
index ffdf6588..1050838 100644
--- a/chrome/common/importer/firefox_importer_utils_mac.mm
+++ b/chrome/common/importer/firefox_importer_utils_mac.mm
@@ -7,6 +7,10 @@
 #include "base/files/file_util.h"
 #include "base/path_service.h"
 
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
 base::FilePath GetProfilesINI() {
   base::FilePath app_data_path;
   if (!base::PathService::Get(base::DIR_APP_DATA, &app_data_path)) {
diff --git a/chrome/common/importer/safari_importer_utils.mm b/chrome/common/importer/safari_importer_utils.mm
index 6e70639..0daa2b7 100644
--- a/chrome/common/importer/safari_importer_utils.mm
+++ b/chrome/common/importer/safari_importer_utils.mm
@@ -8,6 +8,10 @@
 #include "base/files/file_util.h"
 #include "chrome/common/importer/importer_data_types.h"
 
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
 bool SafariImporterCanImport(const base::FilePath& library_dir,
                              uint16_t* services_supported) {
   DCHECK(services_supported);
diff --git a/chrome/common/mac/app_mode_chrome_locator.mm b/chrome/common/mac/app_mode_chrome_locator.mm
index a3e3964..d5cb9ba 100644
--- a/chrome/common/mac/app_mode_chrome_locator.mm
+++ b/chrome/common/mac/app_mode_chrome_locator.mm
@@ -11,18 +11,23 @@
 
 #include "base/files/file_path.h"
 #include "base/files/file_util.h"
+#include "base/mac/bridging.h"
 #include "base/mac/foundation_util.h"
 #include "base/strings/sys_string_conversions.h"
 #include "chrome/common/chrome_constants.h"
 #include "chrome/common/mac/app_mode_common.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
 
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
 namespace app_mode {
 
 namespace {
 
 struct PathAndStructure {
-  NSString* framework_dylib_path;  // weak
+  NSString* __strong framework_dylib_path;
   bool is_new_app_structure;
 };
 
@@ -37,8 +42,9 @@
     @"Versions", version, @(chrome::kFrameworkExecutableName)
   ]];
 
-  if ([[NSFileManager defaultManager] fileExistsAtPath:path])
+  if ([NSFileManager.defaultManager fileExistsAtPath:path]) {
     return PathAndStructure{path, true};
+  }
 
   // OLD STYLE:
   // Chromium.app/Contents/Versions/<version>/Chromium Framework.framework/
@@ -48,8 +54,9 @@
     @"Versions", @"A", @(chrome::kFrameworkExecutableName)
   ]];
 
-  if ([[NSFileManager defaultManager] fileExistsAtPath:path])
+  if ([NSFileManager.defaultManager fileExistsAtPath:path]) {
     return PathAndStructure{path, false};
+  }
 
   return absl::nullopt;
 }
@@ -64,7 +71,7 @@
 
   NSString* ns_bundle_path = base::SysUTF8ToNSString(bundle_path.value());
   NSBundle* bundle = [NSBundle bundleWithPath:ns_bundle_path];
-  if (!bundle || ![bundle_id isEqualToString:[bundle bundleIdentifier]]) {
+  if (!bundle || ![bundle_id isEqualToString:bundle.bundleIdentifier]) {
     return false;
   }
 
@@ -77,13 +84,10 @@
   // Retrieve the last-run Chrome bundle location.
   base::FilePath last_run_bundle_path;
   {
-    using base::mac::CFToNSCast;
-    using base::mac::CFCastStrict;
-    using base::mac::NSToCFCast;
-    NSString* cr_bundle_path_ns =
-        [CFToNSCast(CFCastStrict<CFStringRef>(CFPreferencesCopyAppValue(
-            NSToCFCast(app_mode::kLastRunAppBundlePathPrefsKey),
-            NSToCFCast(bundle_id)))) autorelease];
+    NSString* cr_bundle_path_ns = base::mac::CFToNSOwnershipCast(
+        base::mac::CFCastStrict<CFStringRef>(CFPreferencesCopyAppValue(
+            base::mac::NSToCFPtrCast(app_mode::kLastRunAppBundlePathPrefsKey),
+            base::mac::NSToCFPtrCast(bundle_id))));
     last_run_bundle_path = base::mac::NSStringToFilePath(cr_bundle_path_ns);
   }
 
@@ -97,7 +101,7 @@
         runningApplicationsWithBundleIdentifier:bundle_id];
     for (NSRunningApplication* running_application : running_applications) {
       base::FilePath bundle_path =
-          base::mac::NSURLToFilePath([running_application bundleURL]);
+          base::mac::NSURLToFilePath(running_application.bundleURL);
       DCHECK(!bundle_path.empty());
       running_bundle_paths.insert(bundle_path);
     }
@@ -192,7 +196,7 @@
 
   // A few sanity checks.
   BOOL is_directory;
-  BOOL exists = [[NSFileManager defaultManager]
+  BOOL exists = [NSFileManager.defaultManager
       fileExistsAtPath:framework_path_and_structure->framework_dylib_path
            isDirectory:&is_directory];
   if (!exists || is_directory)
@@ -217,7 +221,7 @@
   }
 
   // Everything is OK; copy the output parameters.
-  *executable_path = base::mac::NSStringToFilePath([cr_bundle executablePath]);
+  *executable_path = base::mac::NSStringToFilePath(cr_bundle.executablePath);
   *framework_path = base::mac::NSStringToFilePath(cr_framework_path);
   *framework_dylib_path = base::mac::NSStringToFilePath(
       framework_path_and_structure->framework_dylib_path);
diff --git a/chrome/common/mac/app_mode_common.h b/chrome/common/mac/app_mode_common.h
index a2747f0..ca09b48c 100644
--- a/chrome/common/mac/app_mode_common.h
+++ b/chrome/common/mac/app_mode_common.h
@@ -13,8 +13,6 @@
 
 #ifdef __OBJC__
 @class NSString;
-#else
-class NSString;
 #endif
 
 // This file contains constants, interfaces, etc. which are common to the
@@ -77,6 +75,8 @@
 // process.
 extern const char kIsNormalLaunch[];
 
+#ifdef __OBJC__
+
 // Keys for specifying the file types handled by an app.
 extern NSString* const kCFBundleDocumentTypesKey;
 extern NSString* const kCFBundleTypeExtensionsKey;
@@ -152,6 +152,8 @@
 // Bundle ID of the Chrome browser bundle.
 extern NSString* const kShortcutBrowserBundleIDPlaceholder;
 
+#endif  // __OBJC__
+
 // Indicates the MojoIpcz feature configuration for a launched shim process.
 enum class MojoIpczConfig {
   // MojoIpcz is enabled.
diff --git a/chrome/common/mac/app_mode_common.mm b/chrome/common/mac/app_mode_common.mm
index 022bd28..90956df 100644
--- a/chrome/common/mac/app_mode_common.mm
+++ b/chrome/common/mac/app_mode_common.mm
@@ -5,6 +5,7 @@
 #include "chrome/common/mac/app_mode_common.h"
 
 #import <Foundation/Foundation.h>
+
 #include <type_traits>
 
 #include "base/check.h"
@@ -13,6 +14,10 @@
 #include "components/version_info/version_info.h"
 #include "mojo/core/embedder/embedder.h"
 
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
 namespace app_mode {
 
 const char kAppShimBootstrapNameFragment[] = "apps";
@@ -63,7 +68,8 @@
 NSString* const kShortcutBrowserBundleIDPlaceholder =
                     @"APP_MODE_BROWSER_BUNDLE_ID";
 
-static_assert(std::is_pod<ChromeAppModeInfo>::value == true,
+static_assert(std::is_standard_layout_v<ChromeAppModeInfo> &&
+                  std::is_trivial_v<ChromeAppModeInfo>,
               "ChromeAppModeInfo must be a POD type");
 
 // ChromeAppModeInfo is built into the app_shim_loader binary that is not
diff --git a/chrome/credential_provider/gaiacp/dllmain.cc b/chrome/credential_provider/gaiacp/dllmain.cc
index d7d399d78..d4cf4ba0 100644
--- a/chrome/credential_provider/gaiacp/dllmain.cc
+++ b/chrome/credential_provider/gaiacp/dllmain.cc
@@ -197,12 +197,12 @@
   // Don't log |buffer| since it contains sensitive info like password.
 
   HRESULT hr = S_OK;
-  absl::optional<base::Value> properties =
-      base::JSONReader::Read(buffer.data(), base::JSON_ALLOW_TRAILING_COMMAS);
+  absl::optional<base::Value::Dict> properties = base::JSONReader::ReadDict(
+      buffer.data(), base::JSON_ALLOW_TRAILING_COMMAS);
 
   credential_provider::SecurelyClearBuffer(buffer.data(), buffer.size());
 
-  if (!properties || !properties->is_dict()) {
+  if (!properties) {
     LOGFN(ERROR) << "base::JSONReader::Read failed length=" << buffer.size();
     return;
   }
@@ -221,7 +221,7 @@
   if (FAILED(hr))
     LOGFN(ERROR) << "PerformPostSigninActions hr=" << putHR(hr);
 
-  credential_provider::SecurelyClearDictionaryValue(&properties);
+  credential_provider::SecurelyClearDictionaryValue(properties);
 
   // Clear the sentinel when it's clear that the user has successfully logged
   // in. This is done to catch edge cases where existing sentinel deletion might
diff --git a/chrome/credential_provider/gaiacp/experiments_manager.cc b/chrome/credential_provider/gaiacp/experiments_manager.cc
index 6f428bb..7de3f74 100644
--- a/chrome/credential_provider/gaiacp/experiments_manager.cc
+++ b/chrome/credential_provider/gaiacp/experiments_manager.cc
@@ -87,8 +87,9 @@
   }
 
   for (const auto& item : *experiments_value) {
-    auto* f = item.FindStringKey(kResponseFeatureKeyName);
-    auto* v = item.FindStringKey(kResponseValueKeyName);
+    const auto& item_dict = item.GetDict();
+    auto* f = item_dict.FindString(kResponseFeatureKeyName);
+    auto* v = item_dict.FindString(kResponseValueKeyName);
     if (!f || !v) {
       LOGFN(WARNING) << "Either feature or value are not found!";
     }
diff --git a/chrome/credential_provider/gaiacp/gaia_credential_base.cc b/chrome/credential_provider/gaiacp/gaia_credential_base.cc
index f627557..179126c 100644
--- a/chrome/credential_provider/gaiacp/gaia_credential_base.cc
+++ b/chrome/credential_provider/gaiacp/gaia_credential_base.cc
@@ -459,7 +459,7 @@
 // since only local users can be created. |sid| will be empty until the user is
 // created later on. |is_consumer_account| will be set to true if the email used
 // to sign in is gmail or googlemail.
-HRESULT MakeUsernameForAccount(const base::Value& result,
+HRESULT MakeUsernameForAccount(const base::Value::Dict& result,
                                std::wstring* gaia_id,
                                wchar_t* username,
                                DWORD username_length,
@@ -659,11 +659,11 @@
 // This function validates the response from GLS and makes sure it contained
 // all the fields required to proceed with logon.  This does not necessarily
 // guarantee that the logon will succeed, only that GLS response seems correct.
-HRESULT ValidateResult(const base::Value& result, BSTR* status_text) {
+HRESULT ValidateResult(const base::Value::Dict& result, BSTR* status_text) {
   DCHECK(status_text);
 
   // Check the exit_code to see if any errors were detected by the GLS.
-  absl::optional<int> exit_code = result.FindIntKey(kKeyExitCode);
+  absl::optional<int> exit_code = result.FindInt(kKeyExitCode);
   if (exit_code.value() != kUiecSuccess) {
     switch (exit_code.value()) {
       case kUiecAbort:
@@ -1130,7 +1130,7 @@
 
   current_windows_password_.Empty();
 
-  SecurelyClearDictionaryValue(&authentication_results_);
+  SecurelyClearDictionaryValue(authentication_results_);
   needs_windows_password_ = false;
   request_force_password_change_ = false;
   result_status_ = STATUS_SUCCESS;
@@ -1933,7 +1933,7 @@
 }
 
 HRESULT CGaiaCredentialBase::ForkPerformPostSigninActionsStub(
-    const base::Value& dict,
+    const base::Value::Dict& dict,
     BSTR* status_text) {
   LOGFN(VERBOSE);
   DCHECK(status_text);
@@ -2071,7 +2071,8 @@
 }
 
 // static
-HRESULT CGaiaCredentialBase::PerformActions(const base::Value& properties) {
+HRESULT CGaiaCredentialBase::PerformActions(
+    const base::Value::Dict& properties) {
   LOGFN(VERBOSE);
 
   std::wstring sid = GetDictString(properties, kKeySID);
@@ -2149,7 +2150,7 @@
 
 // static
 HRESULT CGaiaCredentialBase::PerformPostSigninActions(
-    const base::Value& properties,
+    const base::Value::Dict& properties,
     bool com_initialized) {
   LOGFN(VERBOSE);
   HRESULT hr = S_OK;
@@ -2254,13 +2255,13 @@
     // |authentication_results_| with the real Windows information for the user
     // so that the PerformPostSigninActions process can correctly sign in to the
     // user account.
-    authentication_results_->SetKey(
+    authentication_results_->Set(
         kKeySID, base::Value(base::WideToUTF8((BSTR)user_sid_)));
-    authentication_results_->SetKey(
-        kKeyDomain, base::Value(base::WideToUTF8((BSTR)domain_)));
-    authentication_results_->SetKey(
+    authentication_results_->Set(kKeyDomain,
+                                 base::Value(base::WideToUTF8((BSTR)domain_)));
+    authentication_results_->Set(
         kKeyUsername, base::Value(base::WideToUTF8((BSTR)username_)));
-    authentication_results_->SetKey(
+    authentication_results_->Set(
         kKeyPassword, base::Value(base::WideToUTF8((BSTR)password_)));
 
     std::wstring gaia_id = GetDictString(*authentication_results_, kKeyId);
@@ -2333,11 +2334,12 @@
   }
 }
 
-HRESULT CGaiaCredentialBase::ValidateOrCreateUser(const base::Value& result,
-                                                  BSTR* domain,
-                                                  BSTR* username,
-                                                  BSTR* sid,
-                                                  BSTR* error_text) {
+HRESULT CGaiaCredentialBase::ValidateOrCreateUser(
+    const base::Value::Dict& result,
+    BSTR* domain,
+    BSTR* username,
+    BSTR* sid,
+    BSTR* error_text) {
   LOGFN(VERBOSE);
   DCHECK(domain);
   DCHECK(username);
@@ -2505,25 +2507,23 @@
   base::WideToUTF8(OLE2CW(authentication_info),
                    ::SysStringLen(authentication_info), &json_string);
 
-  absl::optional<base::Value> properties =
-      base::JSONReader::Read(json_string, base::JSON_ALLOW_TRAILING_COMMAS);
+  absl::optional<base::Value::Dict> properties =
+      base::JSONReader::ReadDict(json_string, base::JSON_ALLOW_TRAILING_COMMAS);
 
   SecurelyClearString(json_string);
   json_string.clear();
 
-  if (!properties || !properties->is_dict()) {
+  if (!properties) {
     LOGFN(ERROR) << "base::JSONReader::Read failed to translate to JSON";
     *status_text = AllocErrorString(IDS_INVALID_UI_RESPONSE_BASE);
     return E_FAIL;
   }
 
   {
-    base::ScopedClosureRunner zero_dict_on_exit(base::BindOnce(
-        &SecurelyClearDictionaryValue, base::Unretained(&properties)));
-
     HRESULT hr = ValidateResult(*properties, status_text);
     if (FAILED(hr)) {
       LOGFN(ERROR) << "ValidateResult hr=" << putHR(hr);
+      SecurelyClearDictionaryValue(properties);
       return hr;
     }
 
@@ -2536,6 +2536,7 @@
                      << " isn't in a domain from allowed domains.";
       *status_text =
           CGaiaCredentialBase::AllocErrorString(IDS_INVALID_EMAIL_DOMAIN_BASE);
+      SecurelyClearDictionaryValue(properties);
       return E_FAIL;
     }
 
@@ -2543,6 +2544,7 @@
     if (!permitted_accounts.empty() &&
         !base::Contains(permitted_accounts, email)) {
       *status_text = AllocErrorString(IDS_EMAIL_MISMATCH_BASE);
+      SecurelyClearDictionaryValue(properties);
       return E_FAIL;
     }
 
@@ -2557,14 +2559,14 @@
       if (*status_text == nullptr)
         *status_text = AllocErrorString(IDS_INVALID_UI_RESPONSE_BASE);
       LOGFN(ERROR) << "ValidateOrCreateUser hr=" << putHR(hr);
+      SecurelyClearDictionaryValue(properties);
       return hr;
     }
 
-    base::IgnoreResult(zero_dict_on_exit.Release());
     authentication_results_ = std::move(properties);
     // Update the info whether the user is an AD joined user or local user.
     std::wstring sid = OLE2CW(user_sid_);
-    authentication_results_->SetKey(
+    authentication_results_->Set(
         kKeyIsAdJoinedUser,
         base::Value(OSUserManager::Get()->IsUserDomainJoined(sid) ? "true"
                                                                   : "false"));
@@ -2718,7 +2720,7 @@
   }
 
   const std::string* access_token =
-      authentication_results_->FindStringKey(kKeyAccessToken);
+      authentication_results_->FindString(kKeyAccessToken);
   if (!access_token) {
     LOGFN(ERROR) << "No access token found in authentication results";
     return E_FAIL;
diff --git a/chrome/credential_provider/gaiacp/gaia_credential_base.h b/chrome/credential_provider/gaiacp/gaia_credential_base.h
index cacd051..d1eaca4 100644
--- a/chrome/credential_provider/gaiacp/gaia_credential_base.h
+++ b/chrome/credential_provider/gaiacp/gaia_credential_base.h
@@ -51,7 +51,7 @@
   static HRESULT OnDllUnregisterServer();
 
   // Perform non-critical post-sign operations after everything is setup here.
-  static HRESULT PerformPostSigninActions(const base::Value& properties,
+  static HRESULT PerformPostSigninActions(const base::Value::Dict& properties,
                                           bool com_initialized);
 
   // Allocates a BSTR from a DLL string resource given by |id|.
@@ -93,13 +93,13 @@
   const CComBSTR& get_current_windows_password() const {
     return current_windows_password_;
   }
-  const absl::optional<base::Value>& get_authentication_results() const {
+  const absl::optional<base::Value::Dict>& get_authentication_results() const {
     return authentication_results_;
   }
 
   // Saves account association and user profile information. Makes various HTTP
   // calls regarding device provisioning and password management.
-  static HRESULT PerformActions(const base::Value& properties);
+  static HRESULT PerformActions(const base::Value::Dict& properties);
 
   // Returns true if the current credentials stored in |username_| and
   // |password_| are valid and should succeed a local Windows logon. This
@@ -161,8 +161,9 @@
   virtual void DisplayErrorInUI(LONG status, LONG substatus, BSTR status_text);
 
   // Forks a stub process to perform all post sign-in actions for a user.
-  virtual HRESULT ForkPerformPostSigninActionsStub(const base::Value& dict,
-                                                   BSTR* status_text);
+  virtual HRESULT ForkPerformPostSigninActionsStub(
+      const base::Value::Dict& dict,
+      BSTR* status_text);
 
   // Forks the logon stub process and waits for it to start.
   virtual HRESULT ForkGaiaLogonStub(OSProcessManager* process_manager,
@@ -275,7 +276,7 @@
   // The caller must take ownership of this memory.
   // On failure |error_text| will be allocated and filled with an error message.
   // The caller must take ownership of this memory.
-  HRESULT ValidateOrCreateUser(const base::Value& result,
+  HRESULT ValidateOrCreateUser(const base::Value::Dict& result,
                                BSTR* domain,
                                BSTR* username,
                                BSTR* sid,
@@ -325,7 +326,7 @@
 
   // Contains the information about the Gaia account that signed in.  See the
   // kKeyXXX constants for the data that is stored here.
-  absl::optional<base::Value> authentication_results_;
+  absl::optional<base::Value::Dict> authentication_results_;
 
   // Holds information about the success or failure of the sign in.
   NTSTATUS result_status_ = STATUS_SUCCESS;
diff --git a/chrome/credential_provider/gaiacp/gcp_utils.cc b/chrome/credential_provider/gaiacp/gcp_utils.cc
index ae0add86..70d3b4db 100644
--- a/chrome/credential_provider/gaiacp/gcp_utils.cc
+++ b/chrome/credential_provider/gaiacp/gcp_utils.cc
@@ -949,21 +949,22 @@
   return GetLanguageSelector().matched_candidate();
 }
 
-void SecurelyClearDictionaryValue(absl::optional<base::Value>* value) {
-  SecurelyClearDictionaryValueWithKey(value, kKeyPassword);
+void SecurelyClearDictionaryValue(base::optional_ref<base::Value::Dict> dict) {
+  SecurelyClearDictionaryValueWithKey(dict, kKeyPassword);
 }
 
-void SecurelyClearDictionaryValueWithKey(absl::optional<base::Value>* value,
-                                         const std::string& password_key) {
-  if (!value || !(*value) || !((*value)->is_dict()))
+void SecurelyClearDictionaryValueWithKey(
+    base::optional_ref<base::Value::Dict> dict,
+    const std::string& password_key) {
+  if (!dict.has_value()) {
     return;
-
-  const std::string* password_value = (*value)->FindStringKey(password_key);
-  if (password_value) {
-    SecurelyClearString(*const_cast<std::string*>(password_value));
   }
 
-  (*value).reset();
+  if (auto* password_value = dict->FindString(password_key)) {
+    SecurelyClearString(*password_value);
+  }
+
+  dict->clear();
 }
 
 void SecurelyClearString(std::wstring& str) {
@@ -985,33 +986,26 @@
     const std::initializer_list<base::StringPiece>& path) {
   DCHECK_GT(path.size(), 0UL);
 
-  absl::optional<base::Value> json_obj =
-      base::JSONReader::Read(json_string, base::JSON_ALLOW_TRAILING_COMMAS);
-  if (!json_obj || !json_obj->is_dict()) {
+  absl::optional<base::Value::Dict> json_obj =
+      base::JSONReader::ReadDict(json_string, base::JSON_ALLOW_TRAILING_COMMAS);
+  if (!json_obj) {
     LOGFN(ERROR) << "base::JSONReader::Read failed to translate to JSON";
     return std::string();
   }
   const std::string* value =
-      json_obj->GetDict().FindStringByDottedPath(base::JoinString(path, "."));
+      json_obj->FindStringByDottedPath(base::JoinString(path, "."));
   return value ? *value : std::string();
 }
 
-std::wstring GetDictString(const base::Value& dict, const char* name) {
+std::wstring GetDictString(const base::Value::Dict& dict, const char* name) {
   DCHECK(name);
-  DCHECK(dict.is_dict());
-  const std::string* value = dict.GetDict().FindString(name);
+  const std::string* value = dict.FindString(name);
   return value ? base::UTF8ToWide(*value) : std::wstring();
 }
 
-std::wstring GetDictString(const std::unique_ptr<base::Value>& dict,
-                           const char* name) {
-  return GetDictString(*dict, name);
-}
-
-std::string GetDictStringUTF8(const base::Value& dict, const char* name) {
+std::string GetDictStringUTF8(const base::Value::Dict& dict, const char* name) {
   DCHECK(name);
-  DCHECK(dict.is_dict());
-  const std::string* value = dict.GetDict().FindString(name);
+  const std::string* value = dict.FindString(name);
   return value ? *value : std::string();
 }
 
@@ -1022,15 +1016,14 @@
     std::vector<std::string>* output) {
   DCHECK_GT(path.size(), 0UL);
 
-  absl::optional<base::Value> json_obj =
-      base::JSONReader::Read(json_string, base::JSON_ALLOW_TRAILING_COMMAS);
-  if (!json_obj || !json_obj->is_dict()) {
+  absl::optional<base::Value::Dict> json_obj =
+      base::JSONReader::ReadDict(json_string, base::JSON_ALLOW_TRAILING_COMMAS);
+  if (!json_obj) {
     LOGFN(ERROR) << "base::JSONReader::Read failed to translate to JSON";
     return E_FAIL;
   }
 
-  auto* value =
-      json_obj->GetDict().FindListByDottedPath(base::JoinString(path, "."));
+  auto* value = json_obj->FindListByDottedPath(base::JoinString(path, "."));
   if (value) {
     for (const base::Value& entry_val : *value) {
       const base::Value::Dict& entry = entry_val.GetDict();
@@ -1045,11 +1038,6 @@
   return S_OK;
 }
 
-std::string GetDictStringUTF8(const std::unique_ptr<base::Value>& dict,
-                              const char* name) {
-  return GetDictStringUTF8(*dict, name);
-}
-
 base::FilePath::StringType GetInstallParentDirectoryName() {
 #if BUILDFLAG(GOOGLE_CHROME_BRANDING)
   return FILE_PATH_LITERAL("Google");
@@ -1075,13 +1063,10 @@
 }
 
 bool ExtractKeysFromDict(
-    const base::Value& dict,
+    const base::Value::Dict& dict,
     const std::vector<std::pair<std::string, std::string*>>& needed_outputs) {
-  if (!dict.is_dict())
-    return false;
-
   for (const std::pair<std::string, std::string*>& output : needed_outputs) {
-    const std::string* output_value = dict.FindStringKey(output.first);
+    const std::string* output_value = dict.FindString(output.first);
     if (!output_value) {
       LOGFN(ERROR) << "Could not extract value '" << output.first
                    << "' from server response";
diff --git a/chrome/credential_provider/gaiacp/gcp_utils.h b/chrome/credential_provider/gaiacp/gcp_utils.h
index 2fc130a..f312ccde 100644
--- a/chrome/credential_provider/gaiacp/gcp_utils.h
+++ b/chrome/credential_provider/gaiacp/gcp_utils.h
@@ -15,6 +15,7 @@
 #include "base/functional/callback.h"
 #include "base/memory/raw_ptr.h"
 #include "base/time/time.h"
+#include "base/types/optional_ref.h"
 #include "base/values.h"
 #include "base/version.h"
 #include "base/win/scoped_handle.h"
@@ -276,11 +277,11 @@
 // Gets the language selected by the base::win::i18n::LanguageSelector.
 std::wstring GetSelectedLanguage();
 
-// Securely clear a base::Value that may be a dictionary value that may
-// have a password field.
-void SecurelyClearDictionaryValue(absl::optional<base::Value>* value);
-void SecurelyClearDictionaryValueWithKey(absl::optional<base::Value>* value,
-                                         const std::string& password_key);
+// Securely clear a base::Value::Dict that may have a password field.
+void SecurelyClearDictionaryValue(base::optional_ref<base::Value::Dict> dict);
+void SecurelyClearDictionaryValueWithKey(
+    base::optional_ref<base::Value::Dict> dict,
+    const std::string& password_key);
 
 // Securely clear std::wstring and std::string.
 void SecurelyClearString(std::wstring& str);
@@ -289,12 +290,10 @@
 // Securely clear a given |buffer| with size |length|.
 void SecurelyClearBuffer(void* buffer, size_t length);
 
-// Helpers to get strings from base::Values that are expected to be
-// DictionaryValues.
+// Helpers to get strings from base::Value::Dict.
+std::wstring GetDictString(const base::Value::Dict& dict, const char* name);
+std::string GetDictStringUTF8(const base::Value::Dict& dict, const char* name);
 
-std::wstring GetDictString(const base::Value& dict, const char* name);
-std::wstring GetDictString(const std::unique_ptr<base::Value>& dict,
-                           const char* name);
 // Perform a recursive search on a nested dictionary object. Note that the
 // names provided in the input should be in order. Below is an example : Lets
 // say the json object is {"key1": {"key2": {"key3": "value1"}}, "key4":
@@ -316,9 +315,6 @@
     const std::string& json_string,
     const std::initializer_list<base::StringPiece>& path,
     std::vector<std::string>* output);
-std::string GetDictStringUTF8(const base::Value& dict, const char* name);
-std::string GetDictStringUTF8(const std::unique_ptr<base::Value>& dict,
-                              const char* name);
 
 // Returns the major build version of Windows by reading the registry.
 // See:
@@ -366,7 +362,7 @@
 // Extracts the provided keys from the given dictionary. Returns true if all
 // keys are found. If any of the key isn't found, returns false.
 bool ExtractKeysFromDict(
-    const base::Value& dict,
+    const base::Value::Dict& dict,
     const std::vector<std::pair<std::string, std::string*>>& needed_outputs);
 
 // Gets the bios serial number of the windows device.
diff --git a/chrome/credential_provider/gaiacp/gcp_utils_unittests.cc b/chrome/credential_provider/gaiacp/gcp_utils_unittests.cc
index b6e8a81..8cc9f2c 100644
--- a/chrome/credential_provider/gaiacp/gcp_utils_unittests.cc
+++ b/chrome/credential_provider/gaiacp/gcp_utils_unittests.cc
@@ -453,8 +453,7 @@
   InitializeRegistryOverrideForTesting(&registry_override);
 
   // EnrollToGoogleMdmIfNeeded() should be a noop.
-  base::Value properties(base::Value::Type::DICT);
-  ASSERT_EQ(S_OK, EnrollToGoogleMdmIfNeeded(properties));
+  ASSERT_EQ(S_OK, EnrollToGoogleMdmIfNeeded(base::Value::Dict()));
 }
 
 // Tests all possible data combinations sent to EnrollToGoogleMdmIfNeeded to
@@ -514,23 +513,23 @@
                         (serial_number && serial_number[0]) &&
                         (is_user_ad_joined && is_user_ad_joined[0]);
 
-  base::Value properties(base::Value::Type::DICT);
+  base::Value::Dict properties;
   if (email)
-    properties.SetStringKey(kKeyEmail, email);
+    properties.Set(kKeyEmail, email);
   if (id_token)
-    properties.SetStringKey(kKeyMdmIdToken, id_token);
+    properties.Set(kKeyMdmIdToken, id_token);
   if (access_token)
-    properties.SetStringKey(kKeyAccessToken, access_token);
+    properties.Set(kKeyAccessToken, access_token);
   if (sid)
-    properties.SetStringKey(kKeySID, sid);
+    properties.Set(kKeySID, sid);
   if (username)
-    properties.SetStringKey(kKeyUsername, username);
+    properties.Set(kKeyUsername, username);
   if (domain)
-    properties.SetStringKey(kKeyDomain, domain);
+    properties.Set(kKeyDomain, domain);
   if (sid)
-    properties.SetStringKey(kKeySID, sid);
+    properties.Set(kKeySID, sid);
   if (is_user_ad_joined)
-    properties.SetStringKey(kKeyIsAdJoinedUser, is_user_ad_joined);
+    properties.Set(kKeyIsAdJoinedUser, is_user_ad_joined);
 
   SetMachineGuidForTesting(machine_guid16);
   GoogleRegistrationDataForTesting g_registration_data(serial_number16);
diff --git a/chrome/credential_provider/gaiacp/gem_device_details_manager.cc b/chrome/credential_provider/gaiacp/gem_device_details_manager.cc
index d90d2218..bbdf296 100644
--- a/chrome/credential_provider/gaiacp/gem_device_details_manager.cc
+++ b/chrome/credential_provider/gaiacp/gem_device_details_manager.cc
@@ -302,7 +302,7 @@
     return E_FAIL;
   }
 
-  std::string* resource_id = request_result->FindStringKey(
+  auto* resource_id = request_result->GetDict().FindString(
       kUploadDeviceDetailsResponseDeviceResourceIdParameterName);
   if (resource_id) {
     hr = SetUserProperty(sid, kRegUserDeviceResourceId,
diff --git a/chrome/credential_provider/gaiacp/gem_device_details_manager_unittests.cc b/chrome/credential_provider/gaiacp/gem_device_details_manager_unittests.cc
index ba55bc1..77aab30 100644
--- a/chrome/credential_provider/gaiacp/gem_device_details_manager_unittests.cc
+++ b/chrome/credential_provider/gaiacp/gem_device_details_manager_unittests.cc
@@ -101,15 +101,15 @@
     ASSERT_EQ(1UL, fake_http_url_fetcher_factory()->requests_created());
     FakeWinHttpUrlFetcherFactory::RequestData request_data =
         fake_http_url_fetcher_factory()->GetRequestData(0);
-    base::Value body_value = base::JSONReader::Read(request_data.body).value();
+    base::Value::Dict body_dict =
+        base::JSONReader::ReadDict(request_data.body).value();
 
-    std::string uploaded_dm_token = GetDictStringUTF8(body_value, "dm_token");
+    std::string uploaded_dm_token = GetDictStringUTF8(body_dict, "dm_token");
     ASSERT_EQ(uploaded_dm_token, base::WideToUTF8(dm_token));
 
     std::string uploaded_username =
-        GetDictStringUTF8(body_value, "account_username");
-    std::string uploaded_domain =
-        GetDictStringUTF8(body_value, "device_domain");
+        GetDictStringUTF8(body_dict, "account_username");
+    std::string uploaded_domain = GetDictStringUTF8(body_dict, "device_domain");
     if (!fail_sid_lookup) {
       ASSERT_EQ(uploaded_username, base::WideToUTF8(kDefaultUsername));
       ASSERT_EQ(uploaded_domain, base::WideToUTF8(domain_name));
diff --git a/chrome/credential_provider/gaiacp/mdm_utils.cc b/chrome/credential_provider/gaiacp/mdm_utils.cc
index 35d2d883..447acd1 100644
--- a/chrome/credential_provider/gaiacp/mdm_utils.cc
+++ b/chrome/credential_provider/gaiacp/mdm_utils.cc
@@ -139,7 +139,7 @@
   return is_enrolled;
 }
 
-HRESULT ExtractRegistrationData(const base::Value& registration_data,
+HRESULT ExtractRegistrationData(const base::Value::Dict& registration_data,
                                 std::wstring* out_email,
                                 std::wstring* out_id_token,
                                 std::wstring* out_access_token,
@@ -154,10 +154,6 @@
   DCHECK(out_username);
   DCHECK(out_domain);
   DCHECK(out_is_ad_user_joined);
-  if (!registration_data.is_dict()) {
-    LOGFN(ERROR) << "Registration data is not a dictionary";
-    return E_INVALIDARG;
-  }
 
   *out_email = GetDictString(registration_data, kKeyEmail);
   *out_id_token = GetDictString(registration_data, kKeyMdmIdToken);
@@ -205,8 +201,9 @@
   return S_OK;
 }
 
-HRESULT RegisterWithGoogleDeviceManagement(const std::wstring& mdm_url,
-                                           const base::Value& properties) {
+HRESULT RegisterWithGoogleDeviceManagement(
+    const std::wstring& mdm_url,
+    const base::Value::Dict& properties) {
   // Make sure all the needed data is present in the dictionary.
   std::wstring email;
   std::wstring id_token;
@@ -460,7 +457,7 @@
   return is_online_login_enforced_for_user;
 }
 
-HRESULT EnrollToGoogleMdmIfNeeded(const base::Value& properties) {
+HRESULT EnrollToGoogleMdmIfNeeded(const base::Value::Dict& properties) {
   LOGFN(VERBOSE);
 
   if (UserPoliciesManager::Get()->CloudPoliciesEnabled()) {
diff --git a/chrome/credential_provider/gaiacp/mdm_utils.h b/chrome/credential_provider/gaiacp/mdm_utils.h
index 0d5a209..52d69c4 100644
--- a/chrome/credential_provider/gaiacp/mdm_utils.h
+++ b/chrome/credential_provider/gaiacp/mdm_utils.h
@@ -70,7 +70,7 @@
 GURL EscrowServiceUrl();
 
 // Enrolls the machine to with the Google MDM server if not already.
-HRESULT EnrollToGoogleMdmIfNeeded(const base::Value& properties);
+HRESULT EnrollToGoogleMdmIfNeeded(const base::Value::Dict& properties);
 
 // Constructs the password lsa store key for the given |sid|.
 std::wstring GetUserPasswordLsaStoreKey(const std::wstring& sid);
diff --git a/chrome/credential_provider/gaiacp/password_recovery_manager.cc b/chrome/credential_provider/gaiacp/password_recovery_manager.cc
index ae928202..f567a1e 100644
--- a/chrome/credential_provider/gaiacp/password_recovery_manager.cc
+++ b/chrome/credential_provider/gaiacp/password_recovery_manager.cc
@@ -118,11 +118,9 @@
   SecurelyClearString(padded_secret);
 
   auto result = base::JSONWriter::Write(pwd_padding_dict, out);
-  const std::string* password_value =
-      pwd_padding_dict.FindString(kPaddedPassword);
-  if (password_value)
-    SecurelyClearString(*const_cast<std::string*>(password_value));
-
+  if (auto* password_value = pwd_padding_dict.FindString(kPaddedPassword)) {
+    SecurelyClearString(*password_value);
+  }
   return result;
 }
 
@@ -130,16 +128,15 @@
 // find padded secret. It then removes the padding and returns original secret.
 bool UnpadSecret(const std::string& serialized_padded_secret,
                  std::string* out) {
-  absl::optional<base::Value> pwd_padding = base::JSONReader::Read(
+  absl::optional<base::Value::Dict> pwd_padding = base::JSONReader::ReadDict(
       serialized_padded_secret, base::JSON_ALLOW_TRAILING_COMMAS);
-  if (!pwd_padding.has_value() || !pwd_padding->is_dict()) {
+  if (!pwd_padding) {
     LOGFN(ERROR) << "Failed to deserialize given secret from json.";
     return false;
   }
 
-  const auto& pwd_padding_dict = pwd_padding->GetDict();
-  auto* padded_secret = pwd_padding_dict.FindString(kPaddedPassword);
-  auto pwd_length = pwd_padding_dict.FindInt(kPasswordLength);
+  auto* padded_secret = pwd_padding->FindString(kPaddedPassword);
+  auto pwd_length = pwd_padding->FindInt(kPasswordLength);
 
   auto result = true;
   if (!padded_secret || !pwd_length.has_value()) {
@@ -148,7 +145,7 @@
     out->assign(&(*padded_secret)[padded_secret->size() - *pwd_length],
                 *pwd_length);
   }
-  SecurelyClearDictionaryValueWithKey(&pwd_padding, kPaddedPassword);
+  SecurelyClearDictionaryValueWithKey(pwd_padding, kPaddedPassword);
 
   return result;
 }
@@ -267,9 +264,8 @@
     const std::string& device_id,
     const std::wstring& password,
     const base::TimeDelta& request_timeout,
-    absl::optional<base::Value>* encrypted_data) {
-  DCHECK(encrypted_data);
-  DCHECK(!(*encrypted_data));
+    absl::optional<base::Value::Dict>& encrypted_data) {
+  DCHECK(!encrypted_data);
 
   std::string resource_id;
   std::string public_key;
@@ -290,9 +286,9 @@
     return E_FAIL;
   }
 
-  if (!request_result.has_value() ||
+  if (!request_result.has_value() || !request_result->is_dict() ||
       !ExtractKeysFromDict(
-          *request_result,
+          request_result->GetDict(),
           {
               {kGenerateKeyPairResponseResourceIdParameterName, &resource_id},
               {kGenerateKeyPairResponsePublicKeyParameterName, &public_key},
@@ -320,38 +316,35 @@
   if (opt == absl::nullopt)
     return E_FAIL;
 
-  encrypted_data->emplace(base::Value(base::Value::Type::DICT));
-  (*encrypted_data)->SetStringKey(kUserPasswordLsaStoreIdKey, resource_id);
-
   std::string cipher_text;
   base::Base64Encode(
       base::StringPiece(reinterpret_cast<const char*>(opt->data()),
                         opt->size()),
       &cipher_text);
-  (*encrypted_data)
-      ->SetStringKey(kUserPasswordLsaStoreEncryptedPasswordKey, cipher_text);
+
+  encrypted_data =
+      base::Value::Dict()
+          .Set(kUserPasswordLsaStoreIdKey, resource_id)
+          .Set(kUserPasswordLsaStoreEncryptedPasswordKey, cipher_text);
 
   return hr;
 }
 
-// Given the |encrypted_data| which would contain the resource id of the
+// Given the |encrypted_data_dict| which would contain the resource id of the
 // encryption key and the encrypted password, recovers the |decrypted_password|
 // by getting the private key from the escrow service and decrypting the
 // password. |access_token| is used to authorize the request on the escrow
 // service.
 HRESULT DecryptUserPasswordUsingEscrowService(
     const std::string& access_token,
-    const absl::optional<base::Value>& encrypted_data,
+    const base::Value::Dict& encrypted_data_dict,
     const base::TimeDelta& request_timeout,
     std::wstring* decrypted_password) {
-  if (!encrypted_data)
-    return E_FAIL;
   DCHECK(decrypted_password);
-  DCHECK(encrypted_data && encrypted_data->is_dict());
   const std::string* resource_id =
-      encrypted_data->FindStringKey(kUserPasswordLsaStoreIdKey);
+      encrypted_data_dict.FindString(kUserPasswordLsaStoreIdKey);
   const std::string* encoded_cipher_text =
-      encrypted_data->FindStringKey(kUserPasswordLsaStoreEncryptedPasswordKey);
+      encrypted_data_dict.FindString(kUserPasswordLsaStoreEncryptedPasswordKey);
 
   if (!resource_id) {
     LOGFN(ERROR) << "No password resource id found to restore";
@@ -379,9 +372,9 @@
     return E_FAIL;
   }
 
-  if (!request_result.has_value() ||
+  if (!request_result.has_value() || !request_result->is_dict() ||
       !ExtractKeysFromDict(
-          *request_result,
+          request_result->GetDict(),
           {
               {kGetPrivateKeyResponsePrivateKeyParameterName, &private_key},
           })) {
@@ -487,13 +480,13 @@
     return S_OK;
   }
 
-  absl::optional<base::Value> encrypted_dict;
+  absl::optional<base::Value::Dict> encrypted_dict;
   hr = EncryptUserPasswordUsingEscrowService(access_token, device_id, password,
                                              encryption_key_request_timeout_,
-                                             &encrypted_dict);
+                                             encrypted_dict);
   if (SUCCEEDED(hr)) {
     std::string lsa_value;
-    if (base::JSONWriter::Write(encrypted_dict.value(), &lsa_value)) {
+    if (base::JSONWriter::Write(*encrypted_dict, &lsa_value)) {
       std::wstring lsa_value16 = base::UTF8ToWide(lsa_value);
       hr = policy->StorePrivateData(store_key.c_str(), lsa_value16.c_str());
       SecurelyClearString(lsa_value16);
@@ -511,7 +504,7 @@
     }
 
     SecurelyClearDictionaryValueWithKey(
-        &encrypted_dict, kUserPasswordLsaStoreEncryptedPasswordKey);
+        encrypted_dict, kUserPasswordLsaStoreEncryptedPasswordKey);
   } else {
     LOGFN(ERROR) << "EncryptUserPasswordUsingEscrowService hr=" << putHR(hr);
     return E_FAIL;
@@ -546,24 +539,25 @@
     LOGFN(ERROR) << "RetrievePrivateData hr=" << putHR(hr);
 
   std::string json_string = base::WideToUTF8(password_lsa_data);
-  absl::optional<base::Value> encrypted_dict =
-      base::JSONReader::Read(json_string, base::JSON_ALLOW_TRAILING_COMMAS);
+  absl::optional<base::Value::Dict> encrypted_dict =
+      base::JSONReader::ReadDict(json_string, base::JSON_ALLOW_TRAILING_COMMAS);
   SecurelyClearString(json_string);
   SecurelyClearBuffer(password_lsa_data, sizeof(password_lsa_data));
 
-  std::wstring decrypted_password;
-  hr = DecryptUserPasswordUsingEscrowService(access_token, encrypted_dict,
-                                             decryption_key_request_timeout_,
-                                             &decrypted_password);
-
   if (encrypted_dict) {
-    SecurelyClearDictionaryValueWithKey(
-        &encrypted_dict, kUserPasswordLsaStoreEncryptedPasswordKey);
-  }
+    std::wstring decrypted_password;
+    hr = DecryptUserPasswordUsingEscrowService(access_token, *encrypted_dict,
+                                               decryption_key_request_timeout_,
+                                               &decrypted_password);
 
-  if (SUCCEEDED(hr))
-    *recovered_password = decrypted_password;
-  SecurelyClearString(decrypted_password);
+    SecurelyClearDictionaryValueWithKey(
+        encrypted_dict, kUserPasswordLsaStoreEncryptedPasswordKey);
+
+    if (SUCCEEDED(hr)) {
+      *recovered_password = decrypted_password;
+    }
+    SecurelyClearString(decrypted_password);
+  }
 
   LOGFN(VERBOSE) << "Decrypted the secret for sid=" << sid;
 
diff --git a/chrome/credential_provider/gaiacp/scoped_user_profile.cc b/chrome/credential_provider/gaiacp/scoped_user_profile.cc
index 4428d284b5..a279a11 100644
--- a/chrome/credential_provider/gaiacp/scoped_user_profile.cc
+++ b/chrome/credential_provider/gaiacp/scoped_user_profile.cc
@@ -50,7 +50,7 @@
 
 std::string GetEncryptedRefreshToken(
     base::win::ScopedHandle::Handle logon_handle,
-    const base::Value& properties) {
+    const base::Value::Dict& properties) {
   std::string refresh_token = GetDictStringUTF8(properties, kKeyRefreshToken);
   if (refresh_token.empty()) {
     LOGFN(ERROR) << "Refresh token is empty";
@@ -433,7 +433,7 @@
 }
 
 HRESULT ScopedUserProfile::ExtractAssociationInformation(
-    const base::Value& properties,
+    const base::Value::Dict& properties,
     std::wstring* sid,
     std::wstring* id,
     std::wstring* email,
@@ -507,7 +507,8 @@
   return S_OK;
 }
 
-HRESULT ScopedUserProfile::SaveAccountInfo(const base::Value& properties) {
+HRESULT ScopedUserProfile::SaveAccountInfo(
+    const base::Value::Dict& properties) {
   LOGFN(VERBOSE);
 
   std::wstring sid;
diff --git a/chrome/credential_provider/gaiacp/scoped_user_profile.h b/chrome/credential_provider/gaiacp/scoped_user_profile.h
index 3039f39..db94a6cb3 100644
--- a/chrome/credential_provider/gaiacp/scoped_user_profile.h
+++ b/chrome/credential_provider/gaiacp/scoped_user_profile.h
@@ -28,7 +28,7 @@
   virtual ~ScopedUserProfile();
 
   // Saves Gaia information to the account's KHCU registry hive.
-  virtual HRESULT SaveAccountInfo(const base::Value& properties);
+  virtual HRESULT SaveAccountInfo(const base::Value::Dict& properties);
 
  protected:
   // This constructor is used by the derived fake class to bypass the
@@ -36,7 +36,7 @@
   // tests are not running elevated.
   ScopedUserProfile();
 
-  HRESULT ExtractAssociationInformation(const base::Value& properties,
+  HRESULT ExtractAssociationInformation(const base::Value::Dict& properties,
                                         std::wstring* sid,
                                         std::wstring* id,
                                         std::wstring* email,
diff --git a/chrome/credential_provider/test/gcp_fakes.cc b/chrome/credential_provider/test/gcp_fakes.cc
index beb466e6..568525c 100644
--- a/chrome/credential_provider/test/gcp_fakes.cc
+++ b/chrome/credential_provider/test/gcp_fakes.cc
@@ -607,7 +607,8 @@
 
 FakeScopedUserProfile::~FakeScopedUserProfile() {}
 
-HRESULT FakeScopedUserProfile::SaveAccountInfo(const base::Value& properties) {
+HRESULT FakeScopedUserProfile::SaveAccountInfo(
+    const base::Value::Dict& properties) {
   if (!is_valid_)
     return E_INVALIDARG;
 
diff --git a/chrome/credential_provider/test/gcp_fakes.h b/chrome/credential_provider/test/gcp_fakes.h
index 7935dbc..1b16feda 100644
--- a/chrome/credential_provider/test/gcp_fakes.h
+++ b/chrome/credential_provider/test/gcp_fakes.h
@@ -296,7 +296,7 @@
 
 class FakeScopedUserProfile : public ScopedUserProfile {
  public:
-  HRESULT SaveAccountInfo(const base::Value& properties) override;
+  HRESULT SaveAccountInfo(const base::Value::Dict& properties) override;
 
  private:
   friend class FakeScopedUserProfileFactory;
diff --git a/chrome/credential_provider/test/test_credential.h b/chrome/credential_provider/test/test_credential.h
index 349d3f9..0fb6482d 100644
--- a/chrome/credential_provider/test/test_credential.h
+++ b/chrome/credential_provider/test/test_credential.h
@@ -125,7 +125,7 @@
       CGaiaCredentialBase::UIProcessInfo* uiprocinfo) override;
 
   // Overrides to directly save to a fake scoped user profile.
-  HRESULT ForkPerformPostSigninActionsStub(const base::Value& dict,
+  HRESULT ForkPerformPostSigninActionsStub(const base::Value::Dict& dict,
                                            BSTR* status_text) override;
 
   UiExitCodes default_exit_code_ = kUiecSuccess;
@@ -218,19 +218,19 @@
 
 template <class T>
 bool CTestCredentialBase<T>::IsAuthenticationResultsEmpty() {
-  auto& results = this->get_authentication_results();
+  const auto& results = this->get_authentication_results();
 
-  return !results || (results->is_dict() && results->GetDict().empty());
+  return !results || results->empty();
 }
 
 template <class T>
 std::string CTestCredentialBase<T>::GetFinalEmail() {
-  auto& results = this->get_authentication_results();
+  const auto& results = this->get_authentication_results();
 
   if (!results)
     return std::string();
 
-  const std::string* email_value = results->FindStringKey(kKeyEmail);
+  const std::string* email_value = results->FindString(kKeyEmail);
 
   if (!email_value)
     return std::string();
@@ -239,13 +239,13 @@
 
 template <class T>
 bool CTestCredentialBase<T>::IsAdJoinedUser() {
-  auto& results = this->get_authentication_results();
+  const auto& results = this->get_authentication_results();
 
   if (!results)
     return false;
 
   const std::string* is_ad_joined_user =
-      results->FindStringKey(kKeyIsAdJoinedUser);
+      results->FindString(kKeyIsAdJoinedUser);
 
   if (!is_ad_joined_user)
     return false;
@@ -254,13 +254,13 @@
 
 template <class T>
 bool CTestCredentialBase<T>::ContainsIsAdJoinedUser() {
-  auto& results = this->get_authentication_results();
+  const auto& results = this->get_authentication_results();
 
   if (!results)
     return false;
 
   const std::string* is_ad_joined_user =
-      results->FindStringKey(kKeyIsAdJoinedUser);
+      results->FindString(kKeyIsAdJoinedUser);
 
   if (!is_ad_joined_user)
     return false;
@@ -356,7 +356,7 @@
 
 template <class T>
 HRESULT CTestCredentialBase<T>::ForkPerformPostSigninActionsStub(
-    const base::Value& dict,
+    const base::Value::Dict& dict,
     BSTR* status_text) {
   return CGaiaCredentialBase::PerformPostSigninActions(
       dict, /* com_initialized */ true);
diff --git a/chrome/installer/gcapi_mac/BUILD.gn b/chrome/installer/gcapi_mac/BUILD.gn
index 95b0357..d949ea1f 100644
--- a/chrome/installer/gcapi_mac/BUILD.gn
+++ b/chrome/installer/gcapi_mac/BUILD.gn
@@ -11,18 +11,16 @@
   # on older macOS versions than chrome itself. To not making a dependency on
   # gcapi a problem for these installers, make it target a very old version
   # of macOS.
-  min_version = "10.8"
+  min_version = "10.12"
   cflags = [
     "-isysroot",
     rebase_path(sysroot, root_build_dir),
     "-mmacos-version-min=$min_version",
-    "-stdlib=libc++",  # TODO(thakis): Remove once min_version >= 10.9.
   ]
   ldflags = [
     "-isysroot",
     rebase_path(sysroot, root_build_dir),
     "-mmacos-version-min=$min_version",
-    "-stdlib=libc++",  # TODO(thakis): Remove once min_version >= 10.9.
   ]
 }
 
@@ -39,6 +37,7 @@
   # Don't use runtime_library, to be able to pick a custom mmacos-version-min.
   configs -= [ "//build/config/compiler:runtime_library" ]
   configs += [ ":gcapi_config" ]
+  configs += [ "//build/config/compiler:enable_arc" ]
 
   # no_default_deps has no effect in static_libraries, so set it only below.
 }
@@ -51,6 +50,7 @@
   # Don't use runtime_library, to be able to pick a custom mmacos-version-min.
   configs -= [ "//build/config/compiler:runtime_library" ]
   configs += [ ":gcapi_config" ]
+  configs += [ "//build/config/compiler:enable_arc" ]
 
   # Every executable by default links to a hermetic libc++, so that we can
   # guarantee the capability of the standard library. Neither gcapi_lib
diff --git a/chrome/installer/gcapi_mac/gcapi.mm b/chrome/installer/gcapi_mac/gcapi.mm
index 520daa92..ab2b41c6 100644
--- a/chrome/installer/gcapi_mac/gcapi.mm
+++ b/chrome/installer/gcapi_mac/gcapi.mm
@@ -12,6 +12,10 @@
 #include <sys/types.h>
 #include <sys/utsname.h>
 
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
 namespace {
 
 // The "~~" prefixes are replaced with the home directory of the
@@ -44,50 +48,73 @@
      "Google Chrome Master Preferences";
 
 // Condensed from chromium's base/mac/mac_util.mm.
-bool IsOSXVersionSupported() {
-  // On 10.6, Gestalt() was observed to be able to spawn threads (see
-  // http://crbug.com/53200). Don't call Gestalt().
+bool IsMacOSVersionSupported() {
+  // base::OperatingSystemVersionNumbers() at one time called Gestalt(), which
+  // was observed to be able to spawn threads (see https://crbug.com/53200).
+  // Nowadays that function calls -[NSProcessInfo operatingSystemVersion], whose
+  // current implementation does things like hit the file system, which is
+  // possibly a blocking operation. Either way, it's overkill for what needs to
+  // be done here.
+  //
+  // uname, on the other hand, is implemented as a simple series of sysctl
+  // system calls to obtain the relevant data from the kernel. The data is
+  // compiled right into the kernel, so no threads or blocking or other
+  // funny business is necessary.
+
   struct utsname uname_info;
-  if (uname(&uname_info) != 0)
+  if (uname(&uname_info) != 0) {
     return false;
-  if (strcmp(uname_info.sysname, "Darwin") != 0)
+  }
+  if (strcmp(uname_info.sysname, "Darwin") != 0) {
     return false;
+  }
 
   char* dot = strchr(uname_info.release, '.');
-  if (!dot)
+  if (!dot) {
     return false;
+  }
 
   int darwin_major_version = atoi(uname_info.release);
-  if (darwin_major_version < 6)
+  if (darwin_major_version < 6) {
     return false;
+  }
 
-  // The Darwin major version is always 4 greater than the Mac OS X minor
-  // version for Darwin versions beginning with 6, corresponding to Mac OS X
-  // 10.2.
-  int mac_os_x_minor_version = darwin_major_version - 4;
+  int macos_version;
+  // Darwin major versions 6 through 19 corresponded to macOS versions 10.2
+  // through 10.15. Darwin major version 20 corresponds to macOS version 11.0.
+  // Assume a correspondence between Darwin's major version numbers and macOS
+  // major version numbers.
+  if (darwin_major_version <= 19) {
+    macos_version = 1000 + darwin_major_version - 4;
+  } else {
+    macos_version = 100 * (darwin_major_version - 9);
+  }
 
-  // Chrome is known to work on 10.11 - 10.15.
-  return mac_os_x_minor_version >= 11 && mac_os_x_minor_version <= 15;
+  // Chrome is known to work on 10.13 - 13.x.
+  return macos_version >= 1013 && macos_version < 1400;
 }
 
 // Returns the pid/gid of the logged-in user, even if getuid() claims that the
 // current user is root.
 // Returns nullptr on error.
 passwd* GetRealUserId() {
-  CFDictionaryRef session_info_dict = CGSessionCopyCurrentDictionary();
-  [NSMakeCollectable(session_info_dict) autorelease];
-  if (!session_info_dict)
+  CFDictionaryRef session_info = CGSessionCopyCurrentDictionary();
+  CFAutorelease(session_info);
+  if (!session_info) {
     return nullptr;  // Possibly no screen plugged in.
+  }
 
   CFNumberRef ns_uid =
-      (CFNumberRef)CFDictionaryGetValue(session_info_dict, kCGSessionUserIDKey);
-  if (CFGetTypeID(ns_uid) != CFNumberGetTypeID())
+      (CFNumberRef)CFDictionaryGetValue(session_info, kCGSessionUserIDKey);
+  if (CFGetTypeID(ns_uid) != CFNumberGetTypeID()) {
     return nullptr;
+  }
 
   uid_t uid;
   BOOL success = CFNumberGetValue(ns_uid, kCFNumberSInt32Type, &uid);
-  if (!success)
+  if (!success) {
     return nullptr;
+  }
 
   return getpwuid(uid);
 }
@@ -96,8 +123,9 @@
 
 // Replaces "~~" with |home_dir|.
 NSString* AdjustHomedir(NSString* s, const char* home_dir) {
-  if (![s hasPrefix:@"~~"])
+  if (![s hasPrefix:@"~~"]) {
     return s;
+  }
   NSString* ns_home_dir = @(home_dir);
   return [ns_home_dir stringByAppendingString:[s substringFromIndex:2]];
 }
@@ -107,11 +135,11 @@
 BOOL FindChromeTicket(TicketKind kind,
                       const passwd* user,
                       NSString** chrome_path) {
-  if (chrome_path)
+  if (chrome_path) {
     *chrome_path = nil;
+  }
 
-  // Don't use Objective-C 2 loop syntax, in case an installer runs on 10.4.
-  NSMutableArray* keystone_paths = [NSMutableArray
+  NSMutableArray<NSString*>* keystone_paths = [NSMutableArray
       arrayWithObjects:kSystemKsadminPath, kSystemKsadminPathOld, nil];
   if (kind == kUserTicket) {
     [keystone_paths insertObject:AdjustHomedir(kUserKsadminPath, user->pw_dir)
@@ -120,19 +148,18 @@
         insertObject:AdjustHomedir(kUserKsadminPathOld, user->pw_dir)
              atIndex:1];
   }
-  NSEnumerator* e = [keystone_paths objectEnumerator];
-  id ks_path;
-  while ((ks_path = [e nextObject])) {
-    if (![[NSFileManager defaultManager] fileExistsAtPath:ks_path])
-      continue;
 
-    NSTask* task = nil;
+  for (NSString* path in keystone_paths) {
+    if (![NSFileManager.defaultManager fileExistsAtPath:path]) {
+      continue;
+    }
+
     NSString* string = nil;
     bool ksadmin_ran_successfully = false;
 
     @try {
-      task = [[NSTask alloc] init];
-      [task setLaunchPath:ks_path];
+      NSTask* task = [[NSTask alloc] init];
+      task.launchPath = path;
 
       NSArray* arguments = @[
         kind == kUserTicket ? @"--user-store" : @"--system-store",
@@ -142,32 +169,30 @@
       ];
       if (geteuid() == 0 && kind == kUserTicket) {
         NSString* run_as = @(user->pw_name);
-        [task setLaunchPath:@"/usr/bin/sudo"];
-        arguments = [@[ @"-u", run_as, ks_path ]
-            arrayByAddingObjectsFromArray:arguments];
+        task.launchPath = @"/usr/bin/sudo";
+        arguments =
+            [@[ @"-u", run_as, path ] arrayByAddingObjectsFromArray:arguments];
       }
-      [task setArguments:arguments];
+      task.arguments = arguments;
 
       NSPipe* pipe = [NSPipe pipe];
-      [task setStandardOutput:pipe];
+      task.standardOutput = pipe;
 
-      NSFileHandle* file = [pipe fileHandleForReading];
+      NSFileHandle* file = pipe.fileHandleForReading;
 
       [task launch];
 
       NSData* data = [file readDataToEndOfFile];
       [task waitUntilExit];
 
-      ksadmin_ran_successfully = [task terminationStatus] == 0;
-      string =
-          [[[NSString alloc] initWithData:data
-                                 encoding:NSUTF8StringEncoding] autorelease];
+      ksadmin_ran_successfully = task.terminationStatus == 0;
+      string = [[NSString alloc] initWithData:data
+                                     encoding:NSUTF8StringEncoding];
     } @catch (id exception) {
       // Most likely, ks_path didn't exist.
     }
-    [task release];
 
-    if (ksadmin_ran_successfully && [string length] > 0) {
+    if (ksadmin_ran_successfully && string.length > 0) {
       // If the user deleted chrome, it doesn't get unregistered in keystone.
       // Check if the path keystone thinks chrome is at still exists, and if not
       // treat this as "chrome isn't installed". Sniff for
@@ -176,29 +201,34 @@
       // a user chrome on top of a system ticket produces a non-autoupdating
       // chrome.
       NSRange start = [string rangeOfString:@"\n\txc=<KSPathExistenceChecker:"];
-      if (start.location == NSNotFound && start.length == 0)
+      if (start.location == NSNotFound && start.length == 0) {
         return YES;  // Err on the cautious side.
+      }
       string = [string substringFromIndex:start.location];
 
       start = [string rangeOfString:@"path="];
-      if (start.location == NSNotFound && start.length == 0)
+      if (start.location == NSNotFound && start.length == 0) {
         return YES;  // Err on the cautious side.
+      }
       string = [string substringFromIndex:start.location];
 
       NSRange end = [string rangeOfString:@".app>\n\t"];
-      if (end.location == NSNotFound && end.length == 0)
+      if (end.location == NSNotFound && end.length == 0) {
         return YES;
+      }
 
       string = [string substringToIndex:NSMaxRange(end) - [@">\n\t" length]];
       string = [string substringFromIndex:start.length];
 
-      BOOL exists = [[NSFileManager defaultManager] fileExistsAtPath:string];
-      if (exists && chrome_path)
+      BOOL exists = [NSFileManager.defaultManager fileExistsAtPath:string];
+      if (exists && chrome_path) {
         *chrome_path = string;
+      }
       // Don't allow reinstallation over a system ticket, even if chrome doesn't
       // exist on disk.
-      if (kind == kSystemTicket)
+      if (kind == kSystemTicket) {
         return YES;
+      }
       return exists;
     }
   }
@@ -224,20 +254,17 @@
   // user paths just the owner may.
   NSMutableDictionary* attributes = [NSMutableDictionary dictionary];
   if (user) {
-    attributes[NSFilePosixPermissions] =
-        [NSNumber numberWithShort:kUserPermissions];
-    attributes[NSFileOwnerAccountID] = [NSNumber numberWithInt:user->pw_uid];
+    attributes[NSFilePosixPermissions] = @(kUserPermissions);
+    attributes[NSFileOwnerAccountID] = @(user->pw_uid);
   } else {
-    attributes[NSFilePosixPermissions] =
-        [NSNumber numberWithShort:kAdminPermissions];
+    attributes[NSFilePosixPermissions] = @(kAdminPermissions);
     attributes[NSFileGroupOwnerAccountName] = @"admin";
   }
 
-  NSFileManager* manager = [NSFileManager defaultManager];
-  return [manager createDirectoryAtPath:path
-            withIntermediateDirectories:YES
-                             attributes:attributes
-                                  error:nil];
+  return [NSFileManager.defaultManager createDirectoryAtPath:path
+                                 withIntermediateDirectories:YES
+                                                  attributes:attributes
+                                                       error:nil];
 }
 
 // Tries to write |data| at |user_path|.
@@ -246,8 +273,8 @@
   user_path = AdjustHomedir(user_path, user->pw_dir);
   if (CreatePathToFile(user_path, user) && [data writeToFile:user_path
                                                   atomically:YES]) {
-    chmod([user_path fileSystemRepresentation], kUserPermissions & ~0111);
-    chown([user_path fileSystemRepresentation], user -> pw_uid, user -> pw_gid);
+    chmod(user_path.fileSystemRepresentation, kUserPermissions & ~0111);
+    chown(user_path.fileSystemRepresentation, user->pw_uid, user->pw_gid);
     return user_path;
   }
   return nil;
@@ -262,10 +289,11 @@
   // Try system first.
   if (CreatePathToFile(system_path, nullptr) && [data writeToFile:system_path
                                                        atomically:YES]) {
-    chmod([system_path fileSystemRepresentation], kAdminPermissions & ~0111);
+    chmod(system_path.fileSystemRepresentation, kAdminPermissions & ~0111);
     // Make sure the file is owned by group admin.
-    if (group* group = getgrnam("admin"))
-      chown([system_path fileSystemRepresentation], 0, group -> gr_gid);
+    if (group* group = getgrnam("admin")) {
+      chown(system_path.fileSystemRepresentation, 0, group->gr_gid);
+    }
     return system_path;
   }
 
@@ -278,9 +306,10 @@
     kBrandKey : @(brand_code),
   };
   NSData* contents = [NSPropertyListSerialization
-      dataFromPropertyList:brand_dict
+      dataWithPropertyList:brand_dict
                     format:NSPropertyListBinaryFormat_v1_0
-          errorDescription:nil];
+                   options:0
+                     error:nil];
 
   return WriteUserData(contents, kUserBrandPath, user);
 }
@@ -296,8 +325,9 @@
 
 NSString* PathToFramework(NSString* app_path, NSDictionary* info_plist) {
   NSString* version = info_plist[@"CFBundleShortVersionString"];
-  if (!version)
+  if (!version) {
     return nil;
+  }
   return [NSString pathWithComponents:@[
     app_path, @"Contents", @"Frameworks", @"Google Chrome Framework.framework",
     @"Versions", version
@@ -319,34 +349,40 @@
 int GoogleChromeCompatibilityCheck(unsigned* reasons) {
   unsigned local_reasons = 0;
   @autoreleasepool {
-    if (!IsOSXVersionSupported())
+    if (!IsMacOSVersionSupported()) {
       local_reasons |= GCCC_ERROR_OSNOTSUPPORTED;
+    }
 
     NSString* path;
     if (FindChromeTicket(kSystemTicket, nullptr, &path)) {
       local_reasons |= GCCC_ERROR_ALREADYPRESENT;
-      if (!path)  // Ticket points to nothingness.
+      if (!path) {  // Ticket points to nothingness.
         local_reasons |= GCCC_ERROR_ACCESSDENIED;
+      }
     }
 
     passwd* user = GetRealUserId();
-    if (!user)
+    if (!user) {
       local_reasons |= GCCC_ERROR_ACCESSDENIED;
-    else if (FindChromeTicket(kUserTicket, user, nullptr))
+    } else if (FindChromeTicket(kUserTicket, user, nullptr)) {
       local_reasons |= GCCC_ERROR_ALREADYPRESENT;
+    }
 
-    if ([[NSFileManager defaultManager] fileExistsAtPath:kChromeInstallPath])
+    if ([NSFileManager.defaultManager fileExistsAtPath:kChromeInstallPath]) {
       local_reasons |= GCCC_ERROR_ALREADYPRESENT;
+    }
 
     if ((local_reasons & GCCC_ERROR_ALREADYPRESENT) == 0) {
-      if (![[NSFileManager defaultManager]
-              isWritableFileAtPath:@"/Applications"])
+      if (![NSFileManager.defaultManager
+              isWritableFileAtPath:@"/Applications"]) {
         local_reasons |= GCCC_ERROR_ACCESSDENIED;
+      }
     }
   }
 
-  if (reasons != nullptr)
+  if (reasons != nullptr) {
     *reasons = local_reasons;
+  }
   return local_reasons == 0;
 }
 
@@ -354,13 +390,15 @@
                         const char* brand_code,
                         const char* master_prefs_contents,
                         unsigned master_prefs_contents_size) {
-  if (!GoogleChromeCompatibilityCheck(nullptr))
+  if (!GoogleChromeCompatibilityCheck(nullptr)) {
     return 0;
+  }
 
   @autoreleasepool {
     passwd* user = GetRealUserId();
-    if (!user)
+    if (!user) {
       return 0;
+    }
 
     NSString* app_path = @(source_path);
     NSString* info_plist_path =
@@ -376,7 +414,7 @@
     }
 
     @try {
-      NSTask* task = [[[NSTask alloc] init] autorelease];
+      NSTask* task = [[NSTask alloc] init];
 
       // install.sh tries to make the installed app admin-writable, but
       // only when it's not run as root.
@@ -387,7 +425,7 @@
         // dropped groups).
         // Since geteuid() is 0, su won't prompt for a password.
         NSString* run_as = @(user->pw_name);
-        [task setLaunchPath:@"/usr/bin/su"];
+        task.launchPath = @"/usr/bin/su";
 
         NSString* single_quote_escape = @"'\"'\"'";
         NSString* install_script_quoted = [install_script
@@ -403,15 +441,15 @@
         NSString* install_script_execution = [NSString
             stringWithFormat:@"exec '%@' '%@' '%@'", install_script_quoted,
                              app_path_quoted, install_path_quoted];
-        [task setArguments:@[ run_as, @"-c", install_script_execution ]];
+        task.arguments = @[ run_as, @"-c", install_script_execution ];
       } else {
-        [task setLaunchPath:install_script];
-        [task setArguments:@[ app_path, kChromeInstallPath ]];
+        task.launchPath = install_script;
+        task.arguments = @[ app_path, kChromeInstallPath ];
       }
 
       [task launch];
       [task waitUntilExit];
-      if ([task terminationStatus] != 0) {
+      if (task.terminationStatus != 0) {
         return 0;
       }
     } @catch (id exception) {
@@ -421,20 +459,23 @@
     // Set brand code. If Chrome's Info.plist contains a brand code, use that.
     NSString* info_plist_brand = info_plist[kBrandKey];
     if (info_plist_brand &&
-        [info_plist_brand respondsToSelector:@selector(UTF8String)])
+        [info_plist_brand respondsToSelector:@selector(UTF8String)]) {
       brand_code = [info_plist_brand UTF8String];
+    }
 
     BOOL valid_brand_code =
         brand_code && strlen(brand_code) == 4 && isbrandchar(brand_code[0]) &&
         isbrandchar(brand_code[1]) && isbrandchar(brand_code[2]) &&
         isbrandchar(brand_code[3]);
 
-    if (valid_brand_code)
+    if (valid_brand_code) {
       WriteBrandCode(brand_code, user);
+    }
 
     // Write master prefs.
-    if (master_prefs_contents)
+    if (master_prefs_contents) {
       WriteMasterPrefs(master_prefs_contents, master_prefs_contents_size, user);
+    }
 
     // TODO Set default browser if requested.
   }
@@ -444,21 +485,23 @@
 int LaunchGoogleChrome() {
   @autoreleasepool {
     passwd* user = GetRealUserId();
-    if (!user)
+    if (!user) {
       return 0;
+    }
 
     NSString* app_path;
 
     NSString* path;
-    if (FindChromeTicket(kUserTicket, user, &path) && path)
+    if (FindChromeTicket(kUserTicket, user, &path) && path) {
       app_path = path;
-    else if (FindChromeTicket(kSystemTicket, nullptr, &path) && path)
+    } else if (FindChromeTicket(kSystemTicket, nullptr, &path) && path) {
       app_path = path;
-    else
+    } else {
       app_path = kChromeInstallPath;
+    }
 
     // NSWorkspace launches processes as the current console owner,
     // even when running with euid of 0.
-    return [[NSWorkspace sharedWorkspace] launchApplication:app_path];
+    return [NSWorkspace.sharedWorkspace launchApplication:app_path];
   }
 }
diff --git a/chrome/installer/gcapi_mac/gcapi_example_client.mm b/chrome/installer/gcapi_mac/gcapi_example_client.mm
index d751ca5..0a19ef83 100644
--- a/chrome/installer/gcapi_mac/gcapi_example_client.mm
+++ b/chrome/installer/gcapi_mac/gcapi_example_client.mm
@@ -9,6 +9,10 @@
 
 #include <string>
 
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
 void Usage() {
   fprintf(stderr,
           "usage: gcapi_example [options]\n"
@@ -64,7 +68,7 @@
   }
 
   if (reinstall) {
-    [[NSFileManager defaultManager]
+    [NSFileManager.defaultManager
         removeItemAtPath:@"/Applications/Google Chrome.app"
                    error:nil];
   }
@@ -72,8 +76,9 @@
   unsigned reasons;
   int can_install = GoogleChromeCompatibilityCheck(&reasons);
   NSLog(@"can_install: %d, reasons %x", can_install, reasons);
-  if (check_only)
+  if (check_only) {
     return 0;
+  }
 
   if (can_install && !source_path.empty()) {
     int install_result = InstallGoogleChrome(
@@ -82,6 +87,7 @@
     NSLog(@"install result: %d", install_result);
   }
 
-  if (launch)
+  if (launch) {
     LaunchGoogleChrome();
+  }
 }
diff --git a/chrome/renderer/accessibility/ax_tree_distiller.cc b/chrome/renderer/accessibility/ax_tree_distiller.cc
index b1b0933..a61dda6 100644
--- a/chrome/renderer/accessibility/ax_tree_distiller.cc
+++ b/chrome/renderer/accessibility/ax_tree_distiller.cc
@@ -136,9 +136,13 @@
 void AXTreeDistiller::ProcessScreen2xResult(
     const ui::AXTreeID& tree_id,
     const std::vector<ui::AXNodeID>& content_node_ids) {
-  on_ax_tree_distilled_callback_.Run(tree_id, content_node_ids);
+  // If content nodes were identified, run callback.
+  if (!content_node_ids.empty()) {
+    on_ax_tree_distilled_callback_.Run(tree_id, content_node_ids);
+    return;
+  }
 
-  // TODO(crbug.com/1266555): If no content nodes were identified, and
+  // TODO(crbug.com/1266555): If still no content nodes were identified, and
   // there is a selection, try sending Screen2x a partial tree just containing
   // the selected nodes.
 }
diff --git a/chrome/services/sharing/nearby/quick_start_decoder/quick_start_decoder.cc b/chrome/services/sharing/nearby/quick_start_decoder/quick_start_decoder.cc
index bb0e6b2..025f324 100644
--- a/chrome/services/sharing/nearby/quick_start_decoder/quick_start_decoder.cc
+++ b/chrome/services/sharing/nearby/quick_start_decoder/quick_start_decoder.cc
@@ -27,9 +27,6 @@
 using CBOR = cbor::Value;
 using GetAssertionStatus = mojom::GetAssertionResponse::GetAssertionStatus;
 
-using GetWifiCredentialsFailureReason = mojom::GetWifiCredentialsFailureReason;
-using GetWifiCredentialsResponse = mojom::GetWifiCredentialsResponse;
-
 constexpr char kCredentialIdKey[] = "id";
 constexpr char kEntitiyIdMapKey[] = "id";
 constexpr char kDeviceDetailsKey[] = "deviceDetails";
@@ -231,49 +228,54 @@
 void QuickStartDecoder::DecodeWifiCredentialsResponse(
     const std::vector<uint8_t>& data,
     DecodeWifiCredentialsResponseCallback callback) {
-  std::move(callback).Run(DoDecodeWifiCredentialsResponse(data));
+  DoDecodeWifiCredentialsResponse(data, std::move(callback));
 }
 
-mojom::GetWifiCredentialsResponsePtr
-QuickStartDecoder::DoDecodeWifiCredentialsResponse(
-    const std::vector<uint8_t>& data) {
+void QuickStartDecoder::DoDecodeWifiCredentialsResponse(
+    const std::vector<uint8_t>& data,
+    DecodeWifiCredentialsResponseCallback callback) {
   std::unique_ptr<ash::quick_start::QuickStartMessage> message =
       QuickStartMessage::ReadMessage(data,
                                      QuickStartMessageType::kQuickStartPayload);
 
   if (!message) {
     LOG(ERROR) << "Message cannot be parsed as a JSON Dictionary.";
-    return GetWifiCredentialsResponse::NewFailureReason(
-        GetWifiCredentialsFailureReason::kFailedToDecodeMessage);
+    std::move(callback).Run(nullptr,
+                            mojom::QuickStartDecoderError::kUnableToReadAsJSON);
+    return;
   }
 
   base::Value::Dict* wifi_network_information =
       message->GetPayload()->FindDict(kWifiNetworkInformationKey);
   if (!wifi_network_information) {
     LOG(ERROR) << "Wifi Network information not present in payload";
-    return GetWifiCredentialsResponse::NewFailureReason(
-        GetWifiCredentialsFailureReason::kMissingWifiInformation);
+    std::move(callback).Run(
+        nullptr, mojom::QuickStartDecoderError::kMessageDoesNotMatchSchema);
+    return;
   }
 
   std::string* ssid = wifi_network_information->FindString(kWifiNetworkSsidKey);
   if (!ssid) {
     LOG(ERROR) << "SSID cannot be found within WifiCredentialsResponse.";
-    return GetWifiCredentialsResponse::NewFailureReason(
-        GetWifiCredentialsFailureReason::kMissingWifiSSID);
+    std::move(callback).Run(
+        nullptr, mojom::QuickStartDecoderError::kMessageDoesNotMatchSchema);
+    return;
   }
 
   if (ssid->length() == 0) {
     LOG(ERROR) << "SSID has a length of 0.";
-    return GetWifiCredentialsResponse::NewFailureReason(
-        GetWifiCredentialsFailureReason::kEmptyWifiSSID);
+    std::move(callback).Run(
+        nullptr, mojom::QuickStartDecoderError::kMessageDoesNotMatchSchema);
+    return;
   }
 
   std::string* password =
       wifi_network_information->FindString(kWifiNetworkPasswordKey);
   if (!password) {
     LOG(ERROR) << "Password cannot be found within WifiCredentialsResponse";
-    return GetWifiCredentialsResponse::NewFailureReason(
-        GetWifiCredentialsFailureReason::kMissingWifiPassword);
+    std::move(callback).Run(
+        nullptr, mojom::QuickStartDecoderError::kMessageDoesNotMatchSchema);
+    return;
   }
 
   std::string* security_type_string =
@@ -281,8 +283,9 @@
   if (!security_type_string) {
     LOG(ERROR)
         << "Security Type cannot be found within WifiCredentialsResponse";
-    return GetWifiCredentialsResponse::NewFailureReason(
-        GetWifiCredentialsFailureReason::kMissingWifiSecurityType);
+    std::move(callback).Run(
+        nullptr, mojom::QuickStartDecoderError::kMessageDoesNotMatchSchema);
+    return;
   }
 
   absl::optional<mojom::WifiSecurityType> security_type =
@@ -291,8 +294,9 @@
   if (!security_type.has_value()) {
     {
       LOG(ERROR) << "Security type was not a valid value.";
-      return GetWifiCredentialsResponse::NewFailureReason(
-          GetWifiCredentialsFailureReason::kInvalidWifiSecurityType);
+      std::move(callback).Run(
+          nullptr, mojom::QuickStartDecoderError::kMessageDoesNotMatchSchema);
+      return;
     }
   }
 
@@ -301,12 +305,15 @@
   if (!is_hidden.has_value()) {
     LOG(ERROR)
         << "Wifi Hide Status cannot be found within WifiCredentialsResponse";
-    return GetWifiCredentialsResponse::NewFailureReason(
-        GetWifiCredentialsFailureReason::kMissingWifiHiddenStatus);
+    std::move(callback).Run(
+        nullptr, mojom::QuickStartDecoderError::kMessageDoesNotMatchSchema);
+    return;
   }
 
-  return GetWifiCredentialsResponse::NewCredentials(mojom::WifiCredentials::New(
-      *ssid, security_type.value(), is_hidden.value(), *password));
+  std::move(callback).Run(
+      mojom::WifiCredentials::New(*ssid, security_type.value(),
+                                  is_hidden.value(), *password),
+      absl::nullopt);
 }
 
 void QuickStartDecoder::DecodeGetAssertionResponse(
diff --git a/chrome/services/sharing/nearby/quick_start_decoder/quick_start_decoder.h b/chrome/services/sharing/nearby/quick_start_decoder/quick_start_decoder.h
index d1420d9..64a51155 100644
--- a/chrome/services/sharing/nearby/quick_start_decoder/quick_start_decoder.h
+++ b/chrome/services/sharing/nearby/quick_start_decoder/quick_start_decoder.h
@@ -53,8 +53,9 @@
       const std::vector<uint8_t>& data);
   mojom::GetAssertionResponsePtr DoDecodeGetAssertionResponse(
       const std::vector<uint8_t>& data);
-  mojom::GetWifiCredentialsResponsePtr DoDecodeWifiCredentialsResponse(
-      const std::vector<uint8_t>& data);
+  void DoDecodeWifiCredentialsResponse(
+      const std::vector<uint8_t>& data,
+      DecodeWifiCredentialsResponseCallback callback);
   absl::optional<std::vector<uint8_t>> ExtractFidoDataFromJsonResponse(
       const std::vector<uint8_t>& data);
   // If the kNotifySourceOfUpdateAckKey boolean is present in the response, this
diff --git a/chrome/services/sharing/nearby/quick_start_decoder/quick_start_decoder_unittest.cc b/chrome/services/sharing/nearby/quick_start_decoder/quick_start_decoder_unittest.cc
index c328863..437b243 100644
--- a/chrome/services/sharing/nearby/quick_start_decoder/quick_start_decoder_unittest.cc
+++ b/chrome/services/sharing/nearby/quick_start_decoder/quick_start_decoder_unittest.cc
@@ -8,6 +8,7 @@
 #include "base/json/json_writer.h"
 #include "base/test/bind.h"
 #include "base/test/task_environment.h"
+#include "base/test/test_future.h"
 #include "base/values.h"
 #include "chromeos/ash/components/quick_start/quick_start_message.h"
 #include "chromeos/ash/services/nearby/public/mojom/quick_start_decoder_types.mojom-forward.h"
@@ -107,10 +108,11 @@
     return decoder_->DoDecodeBootstrapConfigurations(data);
   }
 
-  mojom::GetWifiCredentialsResponsePtr DoDecodeWifiCredentialsResponse(
-      QuickStartMessage* message) {
+  void DoDecodeWifiCredentialsResponse(
+      QuickStartMessage* message,
+      QuickStartDecoder::DecodeWifiCredentialsResponseCallback callback) {
     return decoder_->DoDecodeWifiCredentialsResponse(
-        ConvertMessageToBytes(message));
+        ConvertMessageToBytes(message), std::move(callback));
   }
 
   absl::optional<bool> DoDecodeNotifySourceOfUpdateResponse(
@@ -415,16 +417,19 @@
   message.GetPayload()->Set(kWifiNetworkInformationKey,
                             std::move(wifi_information));
 
-  mojom::GetWifiCredentialsResponsePtr response =
-      DoDecodeWifiCredentialsResponse(&message);
+  base::test::TestFuture<
+      ::ash::quick_start::mojom::WifiCredentialsPtr,
+      absl::optional<::ash::quick_start::mojom::QuickStartDecoderError>>
+      future;
 
-  EXPECT_TRUE(response->is_credentials());
+  DoDecodeWifiCredentialsResponse(&message, future.GetCallback());
 
-  EXPECT_EQ(response->get_credentials()->ssid, "ssid");
-  EXPECT_EQ(response->get_credentials()->password, "password");
-  EXPECT_EQ(response->get_credentials()->security_type,
-            mojom::WifiSecurityType::kPSK);
-  EXPECT_TRUE(response->get_credentials()->is_hidden);
+  ASSERT_FALSE(future.Get<0>().is_null());
+  EXPECT_EQ(future.Get<0>()->ssid, "ssid");
+  EXPECT_EQ(future.Get<0>()->password, "password");
+  EXPECT_EQ(future.Get<0>()->security_type, mojom::WifiSecurityType::kPSK);
+  EXPECT_TRUE(future.Get<0>()->is_hidden);
+  EXPECT_EQ(future.Get<1>(), absl::nullopt);
 }
 
 TEST_F(QuickStartDecoderTest, ExtractWifiInformationFailsIfSSIDLengthIsZero) {
@@ -438,12 +443,16 @@
   message.GetPayload()->Set(kWifiNetworkInformationKey,
                             std::move(wifi_information));
 
-  mojom::GetWifiCredentialsResponsePtr response =
-      DoDecodeWifiCredentialsResponse(&message);
+  base::test::TestFuture<
+      ::ash::quick_start::mojom::WifiCredentialsPtr,
+      absl::optional<::ash::quick_start::mojom::QuickStartDecoderError>>
+      future;
 
-  EXPECT_TRUE(response->is_failure_reason());
-  EXPECT_EQ(response->get_failure_reason(),
-            mojom::GetWifiCredentialsFailureReason::kEmptyWifiSSID);
+  DoDecodeWifiCredentialsResponse(&message, future.GetCallback());
+
+  EXPECT_TRUE(future.Get<0>().is_null());
+  EXPECT_EQ(future.Get<1>(),
+            mojom::QuickStartDecoderError::kMessageDoesNotMatchSchema);
 }
 
 TEST_F(QuickStartDecoderTest, ExtractWifiInformationFailsWhenMissingSSID) {
@@ -456,12 +465,16 @@
   message.GetPayload()->Set(kWifiNetworkInformationKey,
                             std::move(wifi_information));
 
-  mojom::GetWifiCredentialsResponsePtr response =
-      DoDecodeWifiCredentialsResponse(&message);
+  base::test::TestFuture<
+      ::ash::quick_start::mojom::WifiCredentialsPtr,
+      absl::optional<::ash::quick_start::mojom::QuickStartDecoderError>>
+      future;
 
-  EXPECT_TRUE(response->is_failure_reason());
-  EXPECT_EQ(response->get_failure_reason(),
-            mojom::GetWifiCredentialsFailureReason::kMissingWifiSSID);
+  DoDecodeWifiCredentialsResponse(&message, future.GetCallback());
+
+  EXPECT_TRUE(future.Get<0>().is_null());
+  EXPECT_EQ(future.Get<1>(),
+            mojom::QuickStartDecoderError::kMessageDoesNotMatchSchema);
 }
 
 TEST_F(QuickStartDecoderTest, ExtractWifiInformationFailsWhenMissingPassword) {
@@ -474,12 +487,16 @@
   message.GetPayload()->Set(kWifiNetworkInformationKey,
                             std::move(wifi_information));
 
-  mojom::GetWifiCredentialsResponsePtr response =
-      DoDecodeWifiCredentialsResponse(&message);
+  base::test::TestFuture<
+      ::ash::quick_start::mojom::WifiCredentialsPtr,
+      absl::optional<::ash::quick_start::mojom::QuickStartDecoderError>>
+      future;
 
-  EXPECT_TRUE(response->is_failure_reason());
-  EXPECT_EQ(response->get_failure_reason(),
-            mojom::GetWifiCredentialsFailureReason::kMissingWifiPassword);
+  DoDecodeWifiCredentialsResponse(&message, future.GetCallback());
+
+  EXPECT_TRUE(future.Get<0>().is_null());
+  EXPECT_EQ(future.Get<1>(),
+            mojom::QuickStartDecoderError::kMessageDoesNotMatchSchema);
 }
 
 TEST_F(QuickStartDecoderTest,
@@ -493,12 +510,16 @@
   message.GetPayload()->Set(kWifiNetworkInformationKey,
                             std::move(wifi_information));
 
-  mojom::GetWifiCredentialsResponsePtr response =
-      DoDecodeWifiCredentialsResponse(&message);
+  base::test::TestFuture<
+      ::ash::quick_start::mojom::WifiCredentialsPtr,
+      absl::optional<::ash::quick_start::mojom::QuickStartDecoderError>>
+      future;
 
-  EXPECT_TRUE(response->is_failure_reason());
-  EXPECT_EQ(response->get_failure_reason(),
-            mojom::GetWifiCredentialsFailureReason::kMissingWifiSecurityType);
+  DoDecodeWifiCredentialsResponse(&message, future.GetCallback());
+
+  EXPECT_TRUE(future.Get<0>().is_null());
+  EXPECT_EQ(future.Get<1>(),
+            mojom::QuickStartDecoderError::kMessageDoesNotMatchSchema);
 }
 
 TEST_F(QuickStartDecoderTest,
@@ -513,12 +534,16 @@
   message.GetPayload()->Set(kWifiNetworkInformationKey,
                             std::move(wifi_information));
 
-  mojom::GetWifiCredentialsResponsePtr response =
-      DoDecodeWifiCredentialsResponse(&message);
+  base::test::TestFuture<
+      ::ash::quick_start::mojom::WifiCredentialsPtr,
+      absl::optional<::ash::quick_start::mojom::QuickStartDecoderError>>
+      future;
 
-  EXPECT_TRUE(response->is_failure_reason());
-  EXPECT_EQ(response->get_failure_reason(),
-            mojom::GetWifiCredentialsFailureReason::kInvalidWifiSecurityType);
+  DoDecodeWifiCredentialsResponse(&message, future.GetCallback());
+
+  EXPECT_TRUE(future.Get<0>().is_null());
+  EXPECT_EQ(future.Get<1>(),
+            mojom::QuickStartDecoderError::kMessageDoesNotMatchSchema);
 }
 
 TEST_F(QuickStartDecoderTest,
@@ -532,24 +557,32 @@
   message.GetPayload()->Set(kWifiNetworkInformationKey,
                             std::move(wifi_information));
 
-  mojom::GetWifiCredentialsResponsePtr response =
-      DoDecodeWifiCredentialsResponse(&message);
+  base::test::TestFuture<
+      ::ash::quick_start::mojom::WifiCredentialsPtr,
+      absl::optional<::ash::quick_start::mojom::QuickStartDecoderError>>
+      future;
 
-  EXPECT_TRUE(response->is_failure_reason());
-  EXPECT_EQ(response->get_failure_reason(),
-            mojom::GetWifiCredentialsFailureReason::kMissingWifiHiddenStatus);
+  DoDecodeWifiCredentialsResponse(&message, future.GetCallback());
+
+  EXPECT_TRUE(future.Get<0>().is_null());
+  EXPECT_EQ(future.Get<1>(),
+            mojom::QuickStartDecoderError::kMessageDoesNotMatchSchema);
 }
 
 TEST_F(QuickStartDecoderTest,
        ExtractWifiInformationFailsWhenMissingWifiInformation) {
   QuickStartMessage message(QuickStartMessageType::kQuickStartPayload);
 
-  mojom::GetWifiCredentialsResponsePtr response =
-      DoDecodeWifiCredentialsResponse(&message);
+  base::test::TestFuture<
+      ::ash::quick_start::mojom::WifiCredentialsPtr,
+      absl::optional<::ash::quick_start::mojom::QuickStartDecoderError>>
+      future;
 
-  EXPECT_TRUE(response->is_failure_reason());
-  EXPECT_EQ(response->get_failure_reason(),
-            mojom::GetWifiCredentialsFailureReason::kMissingWifiInformation);
+  DoDecodeWifiCredentialsResponse(&message, future.GetCallback());
+
+  EXPECT_TRUE(future.Get<0>().is_null());
+  EXPECT_EQ(future.Get<1>(),
+            mojom::QuickStartDecoderError::kMessageDoesNotMatchSchema);
 }
 
 TEST_F(QuickStartDecoderTest, DecodeNotifySourceOfUpdateResponseSuccess) {
diff --git a/chrome/test/BUILD.gn b/chrome/test/BUILD.gn
index 74e6675..d3eae65 100644
--- a/chrome/test/BUILD.gn
+++ b/chrome/test/BUILD.gn
@@ -3963,6 +3963,7 @@
         "../browser/ash/login/screens/edu_coexistence_login_browsertest.cc",
         "../browser/ash/login/screens/family_link_notice_browsertest.cc",
         "../browser/ash/login/screens/fingerprint_setup_browsertest.cc",
+        "../browser/ash/login/screens/gaia_info_screen_browsertest.cc",
         "../browser/ash/login/screens/gesture_navigation_screen_browsertest.cc",
         "../browser/ash/login/screens/guest_tos_screen_browsertest.cc",
         "../browser/ash/login/screens/hid_detection_screen_browsertest.cc",
diff --git a/chrome/test/data/webui/chromeos/personalization_app/personalization_app_browsertest.js b/chrome/test/data/webui/chromeos/personalization_app/personalization_app_browsertest.js
index 42b50a4..aa44df7 100644
--- a/chrome/test/data/webui/chromeos/personalization_app/personalization_app_browsertest.js
+++ b/chrome/test/data/webui/chromeos/personalization_app/personalization_app_browsertest.js
@@ -10,6 +10,7 @@
 
 GEN('#include "ash/constants/ash_features.h"');
 GEN('#include "ash/public/cpp/ambient/ambient_client.h"');
+GEN('#include "chromeos/constants/chromeos_features.h"');
 GEN('#include "content/public/test/browser_test.h"');
 
 const ROOT_PAGE = 'chrome://personalization/';
@@ -518,3 +519,156 @@
 
   mocha.run();
 });
+
+class PersonalizationAppDynamicColorEnabledBrowserTest extends
+    PersonalizationAppBrowserTest {
+  /** @override */
+  get featureList() {
+    return {
+      enabled: [
+        'ash::features::kPersonalizationJelly',
+        'chromeos::features::kJelly',
+      ],
+    };
+  }
+}
+
+this[PersonalizationAppDynamicColorEnabledBrowserTest.name] =
+    PersonalizationAppDynamicColorEnabledBrowserTest;
+
+TEST_F(
+    PersonalizationAppDynamicColorEnabledBrowserTest.name, 'All', async () => {
+      await import('chrome://webui-test/mojo_webui_test_support.js');
+
+      function getDynamicColorElement() {
+        const dynamicColor =
+            getRouter()
+                .shadowRoot.querySelector('personalization-main')
+                .shadowRoot.querySelector('personalization-theme')
+                .shadowRoot.querySelector('dynamic-color');
+        assertTrue(!!dynamicColor);
+        return dynamicColor;
+      }
+
+      function getDynamicColorToggle() {
+        const toggle = getDynamicColorElement().shadowRoot.getElementById(
+            'dynamicColorToggle');
+        assertTrue(!!toggle);
+        return toggle;
+      }
+
+      function getColorSchemeSelector() {
+        const colorScheme = getDynamicColorElement().shadowRoot.getElementById(
+            'colorSchemeSelector');
+        assertTrue(!!colorScheme);
+        return colorScheme;
+      }
+
+      function getStaticColorSelector() {
+        const staticColor = getDynamicColorElement().shadowRoot.getElementById(
+            'staticColorSelector');
+        assertTrue(!!staticColor);
+        return staticColor;
+      }
+
+      function setDynamicColorToggle(checkedState) {
+        const toggle = getDynamicColorToggle();
+        if (checkedState !== toggle.checked) {
+          toggle.click();
+        }
+      }
+
+      suite('dynamic color', () => {
+        test('shows dynamic color options', () => {
+          assertTrue(!!getDynamicColorToggle());
+          assertTrue(!!getColorSchemeSelector());
+          assertTrue(!!getStaticColorSelector());
+        });
+      });
+
+      test('shows color scheme options', () => {
+        setDynamicColorToggle(true);
+
+        assertTrue(getDynamicColorToggle().checked);
+        assertTrue(getStaticColorSelector().hidden);
+        assertFalse(getColorSchemeSelector().hidden);
+      });
+
+      test('selects color scheme options', async () => {
+        const toggleDescription =
+            getDynamicColorElement().shadowRoot.getElementById(
+                'dynamicColorToggleDescription');
+        setDynamicColorToggle(true);
+
+        // Click all of the color scheme buttons and save the text color of the
+        // toggle description to a set.
+        const crosSysSecondarySet = new Set();
+        const colorSchemeButtons =
+            Array.from(getColorSchemeSelector().querySelectorAll('cr-button'));
+        for (const button of colorSchemeButtons) {
+          if (button.ariaPressed === 'false') {
+            const originalColor = getComputedStyle(toggleDescription).color;
+            button.click();
+            await waitUntil(
+                () =>
+                    originalColor !== getComputedStyle(toggleDescription).color,
+                'failed to update colors');
+          }
+
+          const newColor = getComputedStyle(toggleDescription).color;
+          crosSysSecondarySet.add(newColor);
+        }
+
+        assertEquals(
+            colorSchemeButtons.length, crosSysSecondarySet.size,
+            'Each color should be unique');
+      });
+
+      test('shows static color options', () => {
+        const toggleButton = getDynamicColorToggle();
+
+        setDynamicColorToggle(false);
+
+        assertFalse(toggleButton.checked);
+        assertFalse(getStaticColorSelector().hidden);
+        assertTrue(getColorSchemeSelector().hidden);
+      });
+
+      test('selects static color options', async () => {
+        const theme = getRouter()
+                          .shadowRoot.querySelector('personalization-main')
+                          .shadowRoot.querySelector('personalization-theme');
+        const lightButton = theme.shadowRoot.getElementById('lightMode');
+        lightButton.click();
+        await waitUntil(
+            () => getBodyColorChannels().every(channel => channel > 200),
+            'failed to switch to light mode');
+        assertEquals('true', lightButton.getAttribute('aria-pressed'));
+        setDynamicColorToggle(false);
+
+        // Click all of the static color buttons and save the background color
+        // of the light mode button to a set.
+        const crosButtonBackgroundColorPrimarySet = new Set();
+        const staticColorButtons =
+            Array.from(getStaticColorSelector().querySelectorAll('cr-button'));
+        for (const button of staticColorButtons) {
+          if (button.ariaPressed === 'false') {
+            const originalColor = getComputedStyle(lightButton).backgroundColor;
+            button.click();
+            await waitUntil(
+                () => originalColor !==
+                    getComputedStyle(lightButton).backgroundColor,
+                'failed to update colors');
+          }
+
+          const newColor = getComputedStyle(lightButton).backgroundColor;
+          crosButtonBackgroundColorPrimarySet.add(newColor);
+        }
+
+        assertEquals(
+            staticColorButtons.length, crosButtonBackgroundColorPrimarySet.size,
+            'Each color should be unique');
+      });
+
+      mocha.run();
+    });
diff --git a/chrome/test/data/webui/new_tab_page/modules/history_clusters_v2/module_test.ts b/chrome/test/data/webui/new_tab_page/modules/history_clusters_v2/module_test.ts
index b587a59..45b9a721 100644
--- a/chrome/test/data/webui/new_tab_page/modules/history_clusters_v2/module_test.ts
+++ b/chrome/test/data/webui/new_tab_page/modules/history_clusters_v2/module_test.ts
@@ -86,10 +86,10 @@
 
       // Assert.
       assertTrue(!!moduleElement);
-      const headerElement = $$(moduleElement, 'ntp-module-header');
+      const headerElement = $$(moduleElement, 'ntp-module-header-v2');
       assertTrue(!!headerElement);
 
-      assertModuleHeaderTitle(headerElement, sampleClusterLabel);
+      assertModuleHeaderTitle(headerElement, `${sampleClusterLabel}`);
     });
 
     test('Header info button click opens info dialog', async () => {
@@ -100,7 +100,7 @@
 
       // Act.
       assertTrue(!!moduleElement);
-      const headerElement = $$(moduleElement, 'ntp-module-header');
+      const headerElement = $$(moduleElement, 'ntp-module-header-v2');
       assertTrue(!!headerElement);
 
       headerElement!.dispatchEvent(new Event('info-button-click'));
diff --git a/chrome/test/data/webui/settings/zoom_levels_test.ts b/chrome/test/data/webui/settings/zoom_levels_test.ts
index 5ab0e82..6e8e9a40 100644
--- a/chrome/test/data/webui/settings/zoom_levels_test.ts
+++ b/chrome/test/data/webui/settings/zoom_levels_test.ts
@@ -27,19 +27,17 @@
    */
   const zoomList: ZoomLevelEntry[] = [
     {
-      origin: 'http://www.google.com',
-      displayName: 'http://www.google.com',
-      originForFavicon: 'http://www.google.com',
-      setting: '',
-      source: '',
+      hostOrSpec: 'www.google.com',
+      originForFavicon: 'www.google.com',
+      displayName: 'www.google.com',
       zoom: '125%',
     },
     {
-      origin: 'http://www.chromium.org',
-      displayName: 'http://www.chromium.org',
-      originForFavicon: 'http://www.chromium.org',
-      setting: '',
-      source: '',
+      hostOrSpec:
+          'isolated-app://aerugqztij5biqquuk3mfwpsaibuegaqcitgfchwuosuofdjabzqaaic',
+      originForFavicon:
+          'isolated-app://aerugqztij5biqquuk3mfwpsaibuegaqcitgfchwuosuofdjabzqaaic',
+      displayName: 'IWA Name',
       zoom: '125%',
     },
   ];
@@ -95,6 +93,6 @@
     assertTrue(!!removeButton);
     removeButton.click();
     const args = await browserProxy.whenCalled('removeZoomLevel');
-    assertEquals('http://www.google.com', args[0]);
+    assertEquals('www.google.com', args[0]);
   });
 });
diff --git a/chrome/test/variations/fixtures/skia_gold.py b/chrome/test/variations/fixtures/skia_gold.py
index d549292..1e7f3d5 100644
--- a/chrome/test/variations/fixtures/skia_gold.py
+++ b/chrome/test/variations/fixtures/skia_gold.py
@@ -38,17 +38,6 @@
   def GetSessionClass():
     return omsgs.OutputManagerlessSkiaGoldSession
 
-class _VariationsSkiaGoldProperties(sgp.SkiaGoldProperties):
-  @staticmethod
-  def _GetGitOriginMainHeadSha1():
-    try:
-      return subprocess.check_output(
-          ['git', 'rev-parse', 'origin/main'],
-          shell=(sys.platform == 'win32'),
-          cwd=SRC_DIR).strip()
-    except subprocess.CalledProcessError:
-      return None
-
 
 @attr.attrs()
 class VariationsSkiaGoldUtil:
@@ -99,7 +88,7 @@
   skia_tmp_dir = tmp_path_factory.mktemp('skia_gold', True)
   skia_img_dir = tmp_path_factory.mktemp('skia_img', True)
 
-  skia_gold_properties = _VariationsSkiaGoldProperties(
+  skia_gold_properties = sgp.SkiaGoldProperties(
     args=_get_skia_gold_args())
   skia_gold_session_manager = _VariationsSkiaGoldSessionManager(
     skia_tmp_dir,
diff --git a/chromecast/media/api/BUILD.gn b/chromecast/media/api/BUILD.gn
index 877346b..2e07e87 100644
--- a/chromecast/media/api/BUILD.gn
+++ b/chromecast/media/api/BUILD.gn
@@ -23,6 +23,7 @@
   public_deps = [
     "//base",
     "//chromecast/public/media",
+    "//chromecast/external_mojo/external_service_support:external_service",
   ]
 
   assert_no_deps = [ "//media" ]
@@ -35,6 +36,8 @@
     "test/mock_cast_sounds_manager.h",
     "test/mock_cma_backend.cc",
     "test/mock_cma_backend.h",
+    "test/mock_cma_backend_factory.cc",
+    "test/mock_cma_backend_factory.h",
     "test/mock_sound_player.cc",
     "test/mock_sound_player.h",
   ]
diff --git a/chromecast/media/api/cma_backend_factory.h b/chromecast/media/api/cma_backend_factory.h
index 2192e6d..9471e81 100644
--- a/chromecast/media/api/cma_backend_factory.h
+++ b/chromecast/media/api/cma_backend_factory.h
@@ -9,10 +9,7 @@
 
 #include "base/memory/scoped_refptr.h"
 #include "base/task/sequenced_task_runner.h"
-
-namespace service_manager {
-class Connector;
-}  // namespace service_manager
+#include "chromecast/external_mojo/external_service_support/external_connector.h"
 
 namespace chromecast {
 namespace media {
@@ -26,7 +23,7 @@
  public:
   static std::unique_ptr<CmaBackendFactory> Create(
       MediaPipelineBackendManager* media_pipeline_backend_manager,
-      std::unique_ptr<service_manager::Connector> connector);
+      std::unique_ptr<external_service_support::ExternalConnector> connector);
 
   virtual ~CmaBackendFactory() = default;
 
diff --git a/chromecast/media/cma/test/mock_cma_backend_factory.cc b/chromecast/media/api/test/mock_cma_backend_factory.cc
similarity index 84%
rename from chromecast/media/cma/test/mock_cma_backend_factory.cc
rename to chromecast/media/api/test/mock_cma_backend_factory.cc
index e94f5f6..543404df 100644
--- a/chromecast/media/cma/test/mock_cma_backend_factory.cc
+++ b/chromecast/media/api/test/mock_cma_backend_factory.cc
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "chromecast/media/cma/test/mock_cma_backend_factory.h"
+#include "chromecast/media/api/test/mock_cma_backend_factory.h"
 
 namespace chromecast {
 namespace media {
diff --git a/chromecast/media/cma/test/mock_cma_backend_factory.h b/chromecast/media/api/test/mock_cma_backend_factory.h
similarity index 81%
rename from chromecast/media/cma/test/mock_cma_backend_factory.h
rename to chromecast/media/api/test/mock_cma_backend_factory.h
index a32fe38..99f7404 100644
--- a/chromecast/media/cma/test/mock_cma_backend_factory.h
+++ b/chromecast/media/api/test/mock_cma_backend_factory.h
@@ -2,8 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef CHROMECAST_MEDIA_CMA_TEST_MOCK_CMA_BACKEND_FACTORY_H_
-#define CHROMECAST_MEDIA_CMA_TEST_MOCK_CMA_BACKEND_FACTORY_H_
+#ifndef CHROMECAST_MEDIA_API_TEST_MOCK_CMA_BACKEND_FACTORY_H_
+#define CHROMECAST_MEDIA_API_TEST_MOCK_CMA_BACKEND_FACTORY_H_
 
 #include <memory>
 
@@ -29,4 +29,4 @@
 }  // namespace media
 }  // namespace chromecast
 
-#endif  // CHROMECAST_MEDIA_CMA_TEST_MOCK_CMA_BACKEND_FACTORY_H_
+#endif  // CHROMECAST_MEDIA_API_TEST_MOCK_CMA_BACKEND_FACTORY_H_
diff --git a/chromecast/media/audio/cast_audio_manager_alsa_unittest.cc b/chromecast/media/audio/cast_audio_manager_alsa_unittest.cc
index 0ccc95a3..8c5e417 100644
--- a/chromecast/media/audio/cast_audio_manager_alsa_unittest.cc
+++ b/chromecast/media/audio/cast_audio_manager_alsa_unittest.cc
@@ -12,8 +12,8 @@
 #include "base/test/test_message_loop.h"
 #include "chromecast/common/mojom/service_connector.mojom.h"
 #include "chromecast/external_mojo/external_service_support/fake_external_connector.h"
+#include "chromecast/media/api/test/mock_cma_backend_factory.h"
 #include "chromecast/media/audio/mock_cast_audio_manager_helper_delegate.h"
-#include "chromecast/media/cma/test/mock_cma_backend_factory.h"
 #include "media/audio/fake_audio_log_factory.h"
 #include "media/audio/test_audio_thread.h"
 #include "mojo/public/cpp/bindings/pending_receiver.h"
diff --git a/chromecast/media/audio/cast_audio_manager_unittest.cc b/chromecast/media/audio/cast_audio_manager_unittest.cc
index 2fcdb80..d3aa56e 100644
--- a/chromecast/media/audio/cast_audio_manager_unittest.cc
+++ b/chromecast/media/audio/cast_audio_manager_unittest.cc
@@ -20,8 +20,8 @@
 #include "chromecast/external_mojo/external_service_support/fake_external_connector.h"
 #include "chromecast/media/api/cma_backend.h"
 #include "chromecast/media/api/test/mock_cma_backend.h"
+#include "chromecast/media/api/test/mock_cma_backend_factory.h"
 #include "chromecast/media/audio/mock_cast_audio_manager_helper_delegate.h"
-#include "chromecast/media/cma/test/mock_cma_backend_factory.h"
 #include "chromecast/media/cma/test/mock_multiroom_manager.h"
 #include "media/audio/audio_device_info_accessor_for_tests.h"
 #include "media/audio/fake_audio_log_factory.h"
diff --git a/chromecast/media/audio/cast_audio_output_stream_unittest.cc b/chromecast/media/audio/cast_audio_output_stream_unittest.cc
index 1c33e79..9521e7ce 100644
--- a/chromecast/media/audio/cast_audio_output_stream_unittest.cc
+++ b/chromecast/media/audio/cast_audio_output_stream_unittest.cc
@@ -21,11 +21,11 @@
 #include "chromecast/external_mojo/external_service_support/fake_external_connector.h"
 #include "chromecast/media/api/cma_backend.h"
 #include "chromecast/media/api/decoder_buffer_base.h"
+#include "chromecast/media/api/test/mock_cma_backend_factory.h"
 #include "chromecast/media/audio/cast_audio_manager.h"
 #include "chromecast/media/audio/cast_audio_mixer.h"
 #include "chromecast/media/audio/mock_cast_audio_manager_helper_delegate.h"
 #include "chromecast/media/base/default_monotonic_clock.h"
-#include "chromecast/media/cma/test/mock_cma_backend_factory.h"
 #include "chromecast/media/cma/test/mock_multiroom_manager.h"
 #include "chromecast/public/task_runner.h"
 #include "chromecast/public/volume_control.h"
diff --git a/chromecast/media/cma/BUILD.gn b/chromecast/media/cma/BUILD.gn
index f9fd94f..f083265 100644
--- a/chromecast/media/cma/BUILD.gn
+++ b/chromecast/media/cma/BUILD.gn
@@ -21,8 +21,6 @@
     "test/frame_generator_for_test.h",
     "test/frame_segmenter_for_test.cc",
     "test/frame_segmenter_for_test.h",
-    "test/mock_cma_backend_factory.cc",
-    "test/mock_cma_backend_factory.h",
     "test/mock_frame_consumer.cc",
     "test/mock_frame_consumer.h",
     "test/mock_frame_provider.cc",
diff --git a/chromeos/ash/components/dbus/dlcservice/dlcservice_client.cc b/chromeos/ash/components/dbus/dlcservice/dlcservice_client.cc
index ad221e6f..9196609 100644
--- a/chromeos/ash/components/dbus/dlcservice/dlcservice_client.cc
+++ b/chromeos/ash/components/dbus/dlcservice/dlcservice_client.cc
@@ -23,6 +23,7 @@
 #include "base/no_destructor.h"
 #include "base/strings/string_number_conversions.h"
 #include "base/strings/string_util.h"
+#include "base/task/sequenced_task_runner.h"
 #include "base/task/single_thread_task_runner.h"
 #include "base/time/time.h"
 #include "chromeos/ash/components/dbus/dlcservice/fake_dlcservice_client.h"
@@ -284,6 +285,23 @@
   }
 
   void CheckAndRunPendingTask() {
+    // If there are no pending tasks, we can call TaskEnded() now to allow new
+    // requests to run immediately.
+    if (pending_tasks_.empty()) {
+      TaskEnded();
+      return;
+    }
+
+    // Delay pending tasks and let new tasks get queued to ensure we don't spin
+    // the CPU with repeated calls when the DLC installer is busy.
+    base::SequencedTaskRunner::GetCurrentDefault()->PostDelayedTask(
+        FROM_HERE,
+        base::BindOnce(&DlcserviceClientImpl::DelayedPendingTask,
+                       weak_ptr_factory_.GetWeakPtr()),
+        base::Seconds(3));
+  }
+
+  void DelayedPendingTask() {
     TaskEnded();
     if (!pending_tasks_.empty()) {
       std::move(pending_tasks_.front()).Run();
diff --git a/chromeos/ash/components/dbus/dlcservice/dlcservice_client_unittest.cc b/chromeos/ash/components/dbus/dlcservice/dlcservice_client_unittest.cc
index 66c771a7..785455e 100644
--- a/chromeos/ash/components/dbus/dlcservice/dlcservice_client_unittest.cc
+++ b/chromeos/ash/components/dbus/dlcservice/dlcservice_client_unittest.cc
@@ -106,7 +106,8 @@
     return install_request;
   }
 
-  base::test::SingleThreadTaskEnvironment task_environment_;
+  base::test::SingleThreadTaskEnvironment task_environment_{
+      base::test::TaskEnvironment::TimeSource::MOCK_TIME};
   raw_ptr<DlcserviceClient, ExperimentalAsh> client_;
   scoped_refptr<dbus::MockBus> mock_bus_;
   scoped_refptr<dbus::MockObjectProxy> mock_proxy_;
@@ -468,6 +469,7 @@
 
   for (size_t i = 1; i < 100; ++i) {
     client_->DlcStateChangedForTest(signal.get());
+    task_environment_.FastForwardBy(base::Seconds(3));
     base::RunLoop().RunUntilIdle();
     EXPECT_EQ(i <= kLoopCount ? i : kLoopCount, counter.load());
   }
diff --git a/chromeos/ash/components/memory/swap_configuration.cc b/chromeos/ash/components/memory/swap_configuration.cc
index 2eee59d..b9e4ae4 100644
--- a/chromeos/ash/components/memory/swap_configuration.cc
+++ b/chromeos/ash/components/memory/swap_configuration.cc
@@ -38,11 +38,11 @@
 // for ARC enabled users and one for ARC disabled users.
 BASE_FEATURE(kCrOSMemoryPressureSignalStudyNonArc,
              "ChromeOSMemoryPressureSignalStudyNonArc",
-             base::FEATURE_ENABLED_BY_DEFAULT);
+             base::FEATURE_DISABLED_BY_DEFAULT);
 
 const base::FeatureParam<int> kCrOSMemoryPressureSignalStudyNonArcCriticalBps{
     &kCrOSMemoryPressureSignalStudyNonArc, "critical_threshold_percentage",
-    1500};
+    520};
 
 const base::FeatureParam<int> kCrOSMemoryPressureSignalStudyNonArcModerateBps{
     &kCrOSMemoryPressureSignalStudyNonArc, "moderate_threshold_percentage",
diff --git a/chromeos/ash/components/memory/swap_configuration_unittest.cc b/chromeos/ash/components/memory/swap_configuration_unittest.cc
index ae0d71d..8cb724f9 100644
--- a/chromeos/ash/components/memory/swap_configuration_unittest.cc
+++ b/chromeos/ash/components/memory/swap_configuration_unittest.cc
@@ -34,18 +34,18 @@
   feature_list_.InitAndEnableFeature(kCrOSMemoryPressureSignalStudyNonArc);
   ConfigureSwap(/*arc_enabled=*/false);
 
-  EXPECT_EQ(resourced_client_->get_critical_margin_bps(), 1500u);
+  EXPECT_EQ(resourced_client_->get_critical_margin_bps(), 520u);
   EXPECT_EQ(resourced_client_->get_moderate_margin_bps(), 4000u);
 }
 
 TEST_F(SwapConfigurationPressureThreshold, NoArcCustom) {
   feature_list_.InitAndEnableFeatureWithParameters(
       kCrOSMemoryPressureSignalStudyNonArc,
-      {{kCrOSMemoryPressureSignalStudyNonArcCriticalBps.name, "1700"},
+      {{kCrOSMemoryPressureSignalStudyNonArcCriticalBps.name, "1500"},
        {kCrOSMemoryPressureSignalStudyNonArcModerateBps.name, "6000"}});
   ConfigureSwap(/*arc_enabled=*/false);
 
-  EXPECT_EQ(resourced_client_->get_critical_margin_bps(), 1700u);
+  EXPECT_EQ(resourced_client_->get_critical_margin_bps(), 1500u);
   EXPECT_EQ(resourced_client_->get_moderate_margin_bps(), 6000u);
 }
 
diff --git a/chromeos/ash/components/quick_start/fake_quick_start_decoder.cc b/chromeos/ash/components/quick_start/fake_quick_start_decoder.cc
index 3ab7bed3..80fc7d7 100644
--- a/chromeos/ash/components/quick_start/fake_quick_start_decoder.cc
+++ b/chromeos/ash/components/quick_start/fake_quick_start_decoder.cc
@@ -5,6 +5,7 @@
 #include "fake_quick_start_decoder.h"
 
 #include "chromeos/ash/services/nearby/public/mojom/quick_start_decoder_types.mojom-forward.h"
+#include "testing/gtest/include/gtest/gtest.h"
 
 namespace ash::quick_start {
 
@@ -27,7 +28,7 @@
 void FakeQuickStartDecoder::DecodeWifiCredentialsResponse(
     const std::vector<uint8_t>& data,
     DecodeWifiCredentialsResponseCallback callback) {
-  std::move(callback).Run(std::move(wifi_credentials_response_));
+  std::move(callback).Run(std::move(credentials_), error_);
 }
 
 void FakeQuickStartDecoder::DecodeGetAssertionResponse(
@@ -69,8 +70,10 @@
 }
 
 void FakeQuickStartDecoder::SetWifiCredentialsResponse(
-    mojom::GetWifiCredentialsResponsePtr response) {
-  wifi_credentials_response_ = std::move(response);
+    mojom::WifiCredentialsPtr credentials,
+    absl::optional<mojom::QuickStartDecoderError> error) {
+  credentials_ = std::move(credentials);
+  error_ = error;
 }
 
 void FakeQuickStartDecoder::SetNotifySourceOfUpdateResponse(
diff --git a/chromeos/ash/components/quick_start/fake_quick_start_decoder.h b/chromeos/ash/components/quick_start/fake_quick_start_decoder.h
index 662a62c..82b238bc 100644
--- a/chromeos/ash/components/quick_start/fake_quick_start_decoder.h
+++ b/chromeos/ash/components/quick_start/fake_quick_start_decoder.h
@@ -6,6 +6,8 @@
 #define CHROMEOS_ASH_COMPONENTS_QUICK_START_FAKE_QUICK_START_DECODER_H_
 
 #include "chromeos/ash/services/nearby/public/mojom/quick_start_decoder.mojom.h"
+#include "chromeos/ash/services/nearby/public/mojom/quick_start_decoder_types.mojom-forward.h"
+#include "chromeos/ash/services/nearby/public/mojom/quick_start_decoder_types.mojom-shared.h"
 #include "chromeos/ash/services/nearby/public/mojom/quick_start_decoder_types.mojom.h"
 #include "mojo/public/cpp/bindings/receiver_set.h"
 #include "testing/gtest/include/gtest/gtest.h"
@@ -47,7 +49,8 @@
       const std::vector<uint8_t>& data);
 
   void SetWifiCredentialsResponse(
-      mojom::GetWifiCredentialsResponsePtr response);
+      mojom::WifiCredentialsPtr credentials,
+      absl::optional<mojom::QuickStartDecoderError> error);
 
   void SetNotifySourceOfUpdateResponse(absl::optional<bool> ack_received);
 
@@ -61,8 +64,9 @@
   std::vector<uint8_t> response_signature_;
   std::vector<uint8_t> response_data_;
   mojo::ReceiverSet<ash::quick_start::mojom::QuickStartDecoder> receiver_set_;
-  mojom::GetWifiCredentialsResponsePtr wifi_credentials_response_;
   absl::optional<bool> notify_source_of_update_response_;
+  mojom::WifiCredentialsPtr credentials_;
+  absl::optional<mojom::QuickStartDecoderError> error_;
 };
 
 }  // namespace ash::quick_start
diff --git a/chromeos/ash/components/quick_start/quick_start_message.cc b/chromeos/ash/components/quick_start/quick_start_message.cc
index 993f4e1..87aa4c25 100644
--- a/chromeos/ash/components/quick_start/quick_start_message.cc
+++ b/chromeos/ash/components/quick_start/quick_start_message.cc
@@ -93,7 +93,8 @@
 
     std::string json_payload;
     bool base64_decoding_succeeded =
-        base::Base64Decode(*base64_encoded_payload, &json_payload);
+        base::Base64Decode(*base64_encoded_payload, &json_payload,
+                           base::Base64DecodePolicy::kForgiving);
     if (!base64_decoding_succeeded) {
       LOG(ERROR) << "Message does not contain a valid base64 encoded payload";
       return nullptr;
diff --git a/chromeos/ash/services/nearby/public/mojom/quick_start_decoder.mojom b/chromeos/ash/services/nearby/public/mojom/quick_start_decoder.mojom
index 3b23116..b3ae3783 100644
--- a/chromeos/ash/services/nearby/public/mojom/quick_start_decoder.mojom
+++ b/chromeos/ash/services/nearby/public/mojom/quick_start_decoder.mojom
@@ -24,7 +24,7 @@
   // Decode a D2D response to a request for Wifi Credentials
   // This response is JSON, with a Base64 encoded JSON payload
   DecodeWifiCredentialsResponse(array<uint8> data) => (
-    GetWifiCredentialsResponse response);
+    WifiCredentials? credentials, QuickStartDecoderError? error);
 
   // Decode a simple ack response to a message notifying the source device
   // that the target device will update and reboot. This response is JSON,
diff --git a/chromeos/ash/services/nearby/public/mojom/quick_start_decoder_types.mojom b/chromeos/ash/services/nearby/public/mojom/quick_start_decoder_types.mojom
index 4a900bbe..8768fbd 100644
--- a/chromeos/ash/services/nearby/public/mojom/quick_start_decoder_types.mojom
+++ b/chromeos/ash/services/nearby/public/mojom/quick_start_decoder_types.mojom
@@ -18,15 +18,10 @@
   kSAE = 5,
 };
 
-enum GetWifiCredentialsFailureReason {
-  kFailedToDecodeMessage = 1,
-  kMissingWifiInformation = 2,
-  kMissingWifiSSID = 3,
-  kMissingWifiPassword = 4,
-  kMissingWifiSecurityType = 5,
-  kMissingWifiHiddenStatus = 6,
-  kInvalidWifiSecurityType = 7,
-  kEmptyWifiSSID = 8,
+enum QuickStartDecoderError {
+  kEmptyMessage = 0,
+  kUnableToReadAsJSON = 1,
+  kMessageDoesNotMatchSchema = 2,
 };
 
 struct WifiCredentials {
@@ -36,16 +31,6 @@
   string password;
 };
 
-union GetWifiCredentialsResponse {
-
-  // Success - return credentials
-  WifiCredentials credentials;
-
-  // Failure - return error reason
-  GetWifiCredentialsFailureReason failure_reason;
-
-};
-
 struct GetAssertionResponse {
   enum GetAssertionStatus {
     kSuccess = 0,
diff --git a/chromeos/components/quick_answers/quick_answers_model.cc b/chromeos/components/quick_answers/quick_answers_model.cc
index 2ef886f..1c142bb 100644
--- a/chromeos/components/quick_answers/quick_answers_model.cc
+++ b/chromeos/components/quick_answers/quick_answers_model.cc
@@ -36,6 +36,12 @@
     default;
 QuickAnswersRequest::~QuickAnswersRequest() = default;
 
+Sense::Sense() = default;
+Sense::~Sense() = default;
+
+DefinitionResult::DefinitionResult() = default;
+DefinitionResult::~DefinitionResult() = default;
+
 TranslationResult::TranslationResult() = default;
 TranslationResult::~TranslationResult() = default;
 
diff --git a/chromeos/components/quick_answers/quick_answers_model.h b/chromeos/components/quick_answers/quick_answers_model.h
index b45b9bf..8ad1671 100644
--- a/chromeos/components/quick_answers/quick_answers_model.h
+++ b/chromeos/components/quick_answers/quick_answers_model.h
@@ -127,6 +127,9 @@
   PhoneticsInfo(const PhoneticsInfo&);
   ~PhoneticsInfo();
 
+  // Pronunciation of a word, i.e. in phonetic symbols.
+  std::string text;
+
   // Phonetics audio URL for playing pronunciation of dictionary results.
   // For other type of results the URL will be empty.
   GURL phonetics_audio = GURL();
@@ -224,6 +227,27 @@
   // links, etc).
 };
 
+// `Sense` must be copyable.
+struct Sense {
+ public:
+  Sense();
+  ~Sense();
+
+  std::string definition;
+};
+
+// `DefinitionResult` holds result for definition intent.
+// `DefinitionResult` must be copyable.
+struct DefinitionResult {
+ public:
+  DefinitionResult();
+  ~DefinitionResult();
+
+  std::string word;
+  PhoneticsInfo phonetics_info;
+  Sense sense;
+};
+
 // `TranslationResult` holds result for translation intent.
 // `TranslationResult` must be copyable as it can be copied to a view.
 struct TranslationResult {
@@ -231,6 +255,7 @@
   TranslationResult();
   ~TranslationResult();
 
+  // TODO(b/278929409): Migrate to `std::string` for strings in structs.
   std::u16string text_to_translate;
   std::u16string translated_text;
   std::string source_locale;
@@ -248,6 +273,7 @@
 
   // Result type specific structs must be copyable.
   std::unique_ptr<TranslationResult> translation_result;
+  std::unique_ptr<DefinitionResult> definition_result;
 };
 
 // `QuickAnswersSession` holds states related to a single Quick Answer session.
diff --git a/chromeos/components/quick_answers/search_result_loader.cc b/chromeos/components/quick_answers/search_result_loader.cc
index 84d5f28..5457bd0b 100644
--- a/chromeos/components/quick_answers/search_result_loader.cc
+++ b/chromeos/components/quick_answers/search_result_loader.cc
@@ -112,30 +112,8 @@
     std::unique_ptr<std::string> response_body,
     ResponseParserCallback complete_callback) {
   search_response_parser_ =
-      std::make_unique<SearchResponseParser>(base::BindOnce(
-          &SearchResultLoader::OnSearchResponseParsed,
-          weak_ptr_factory_.GetWeakPtr(), std::move(complete_callback)));
+      std::make_unique<SearchResponseParser>(std::move(complete_callback));
   search_response_parser_->ProcessResponse(std::move(response_body));
 }
 
-void SearchResultLoader::OnSearchResponseParsed(
-    ResponseParserCallback complete_callback,
-    std::unique_ptr<QuickAnswer> quick_answer) {
-  // If no `QuickAnswer` is returned, e.g. parse failure, return nullptr instead
-  // of an empty `QuickAnswersSession`. `SearchResultLoaderTest.EmptyResponse`
-  // expects this behavior. For longer term, migrate to an empty `quick_answer`
-  // field in `QuickAnswersSession` as `QuickAnswersSession` will hold more
-  // information, e.g. intent.
-  if (!quick_answer) {
-    std::move(complete_callback).Run(nullptr);
-    return;
-  }
-
-  // TODO(b/278929409) Fill structured_result field.
-  std::unique_ptr<QuickAnswersSession> quick_answers_session =
-      std::make_unique<QuickAnswersSession>();
-  quick_answers_session->quick_answer = std::move(quick_answer);
-  std::move(complete_callback).Run(std::move(quick_answers_session));
-}
-
 }  // namespace quick_answers
diff --git a/chromeos/components/quick_answers/search_result_loader.h b/chromeos/components/quick_answers/search_result_loader.h
index 5c93cee..4c20a9f 100644
--- a/chromeos/components/quick_answers/search_result_loader.h
+++ b/chromeos/components/quick_answers/search_result_loader.h
@@ -38,9 +38,6 @@
                        ResponseParserCallback complete_callback) override;
 
  private:
-  void OnSearchResponseParsed(ResponseParserCallback complete_callback,
-                              std::unique_ptr<QuickAnswer> quick_answer);
-
   std::unique_ptr<SearchResponseParser> search_response_parser_;
   base::WeakPtrFactory<SearchResultLoader> weak_ptr_factory_{this};
 };
diff --git a/chromeos/components/quick_answers/search_result_parsers/definition_result_parser.cc b/chromeos/components/quick_answers/search_result_parsers/definition_result_parser.cc
index 5e7ce78f..f880215 100644
--- a/chromeos/components/quick_answers/search_result_parsers/definition_result_parser.cc
+++ b/chromeos/components/quick_answers/search_result_parsers/definition_result_parser.cc
@@ -8,7 +8,9 @@
 
 #include "base/logging.h"
 #include "base/values.h"
+#include "chromeos/components/quick_answers/quick_answers_model.h"
 #include "chromeos/components/quick_answers/utils/quick_answers_utils.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
 #include "url/gurl.h"
 
 namespace quick_answers {
@@ -18,56 +20,170 @@
 
 constexpr char kHttpsPrefix[] = "https:";
 
-constexpr char kDictionaryEntriesPath[] = "dictionaryResult.entries";
-constexpr char kDefinitionPathUnderSense[] = "definition.text";
+// DictionaryResult
+constexpr char kDictionaryResultKey[] = "dictionaryResult";
+constexpr char kQueryTermKey[] = "queryTerm";
+constexpr char kEntriesKey[] = "entries";
+
+// Entry
 constexpr char kHeadwordKey[] = "headword";
 constexpr char kLocaleKey[] = "locale";
 constexpr char kPhoneticsKey[] = "phonetics";
+constexpr char kSenseFamiliesKey[] = "senseFamilies";
+
+// Phonetics
 constexpr char kPhoneticsTextKey[] = "text";
 constexpr char kPhoneticsAudioKey[] = "oxfordAudio";
 constexpr char kPhoneticsTtsAudioEnabledKey[] = "ttsAudioEnabled";
-constexpr char kSenseFamiliesKey[] = "senseFamilies";
+
+// SenseFamilies
 constexpr char kSensesKey[] = "senses";
-constexpr char kQueryTermPath[] = "dictionaryResult.queryTerm";
+
+// Sense
+constexpr char kDefinitionKey[] = "definition";
+constexpr char kDefinitionTextKey[] = "text";
+
+std::unique_ptr<Sense> ParseSense(const base::Value::Dict& sense_result) {
+  const base::Value::Dict* definition_entry =
+      sense_result.FindDict(kDefinitionKey);
+  if (!definition_entry) {
+    DLOG(ERROR) << "Unable to find definition entry.";
+    return nullptr;
+  }
+
+  const std::string* definition_text =
+      definition_entry->FindString(kDefinitionTextKey);
+  if (!definition_text) {
+    DLOG(ERROR) << "Unable to find a text in a definition entry.";
+    return nullptr;
+  }
+
+  std::unique_ptr<Sense> sense = std::make_unique<Sense>();
+  sense->definition = *definition_text;
+  return sense;
+}
+
+const std::string* GetQueryTerm(const base::Value::Dict& result) {
+  return result.FindString(kQueryTermKey);
+}
+
+const std::string* GetHeadword(const base::Value::Dict& entry_result) {
+  return entry_result.FindString(kHeadwordKey);
+}
 
 }  // namespace
 
+std::unique_ptr<StructuredResult>
+DefinitionResultParser::ParseInStructuredResult(
+    const base::Value::Dict& result) {
+  const Value::Dict* dictionary_result = result.FindDict(kDictionaryResultKey);
+  if (!dictionary_result) {
+    DLOG(ERROR) << "Unable to find the dictionary result entry.";
+    return nullptr;
+  }
+
+  const Value::Dict* first_entry =
+      GetFirstListElement(*dictionary_result, kEntriesKey);
+  if (!first_entry) {
+    DLOG(ERROR) << "Unable to find a first entry.";
+    return nullptr;
+  }
+
+  const Value::Dict* first_sense_family = ExtractFirstSenseFamily(*first_entry);
+  if (!first_sense_family) {
+    DLOG(ERROR) << "Unable to find a first sense familiy.";
+    return nullptr;
+  }
+
+  const Value::Dict* first_sense =
+      GetFirstListElement(*first_sense_family, kSensesKey);
+  if (!first_sense) {
+    DLOG(ERROR) << "Unable to find a first sense.";
+    return nullptr;
+  }
+
+  std::unique_ptr<Sense> sense = ParseSense(*first_sense);
+  if (!sense) {
+    DLOG(ERROR) << "Unable to parse a sense.";
+    return nullptr;
+  }
+  std::unique_ptr<DefinitionResult> definition_result =
+      std::make_unique<DefinitionResult>();
+  definition_result->sense = *(sense.get());
+
+  const std::string* word = GetQueryTerm(*dictionary_result);
+  if (!word) {
+    word = GetHeadword(*first_entry);
+  }
+  if (!word) {
+    DLOG(ERROR) << "Unable to find a word in either query term or headword.";
+    return nullptr;
+  }
+  definition_result->word = *word;
+
+  std::unique_ptr<PhoneticsInfo> phonetics_info =
+      ParsePhoneticsInfo(*first_entry);
+  if (phonetics_info) {
+    definition_result->phonetics_info = *(phonetics_info.get());
+  }
+
+  std::unique_ptr<StructuredResult> structured_result =
+      std::make_unique<StructuredResult>();
+  structured_result->definition_result = std::move(definition_result);
+  return structured_result;
+}
+
+bool DefinitionResultParser::PopulateQuickAnswer(
+    const StructuredResult& structured_result,
+    QuickAnswer* quick_answer) {
+  DefinitionResult* definition_result =
+      structured_result.definition_result.get();
+  if (!definition_result) {
+    DLOG(ERROR) << "Unable to find definition_result.";
+    return false;
+  }
+
+  quick_answer->result_type = ResultType::kDefinitionResult;
+  quick_answer->phonetics_info =
+      structured_result.definition_result->phonetics_info;
+
+  // Title line
+  if (definition_result->word.empty()) {
+    DLOG(ERROR) << "Unable to find a word in definition_result.";
+    return false;
+  }
+  const std::string& title =
+      !quick_answer->phonetics_info.text.empty()
+          ? BuildDefinitionTitleText(definition_result->word,
+                                     quick_answer->phonetics_info.text)
+          : definition_result->word;
+  quick_answer->title.push_back(std::make_unique<QuickAnswerText>(title));
+
+  // Second line, i.e. definition.
+  if (definition_result->sense.definition.empty()) {
+    DLOG(ERROR) << "Unable to find a definition in a sense.";
+    return false;
+  }
+
+  quick_answer->first_answer_row.push_back(
+      std::make_unique<QuickAnswerResultText>(
+          definition_result->sense.definition));
+  return true;
+}
+
+bool DefinitionResultParser::SupportsNewInterface() const {
+  return true;
+}
+
 bool DefinitionResultParser::Parse(const base::Value::Dict& result,
                                    QuickAnswer* quick_answer) {
-  const Value::Dict* first_entry =
-      GetFirstListElement(result, kDictionaryEntriesPath);
-  if (!first_entry) {
-    LOG(ERROR) << "Can't find a definition entry.";
+  std::unique_ptr<StructuredResult> structured_result =
+      ParseInStructuredResult(result);
+  if (!structured_result) {
     return false;
   }
 
-  // Get definition and phonetics.
-  const std::string* definition = ExtractDefinition(*first_entry);
-  if (!definition) {
-    LOG(ERROR) << "Fail in extracting definition.";
-    return false;
-  }
-  const std::string* phonetics = ExtractPhoneticsText(*first_entry);
-
-  // If query term path not found, fallback to use headword.
-  const std::string* query = result.FindStringByDottedPath(kQueryTermPath);
-  if (!query)
-    query = first_entry->FindStringByDottedPath(kHeadwordKey);
-  if (!query) {
-    LOG(ERROR) << "Fail in extracting query.";
-    return false;
-  }
-
-  const std::string& secondary_answer =
-      phonetics ? BuildDefinitionTitleText(query->c_str(), phonetics->c_str())
-                : query->c_str();
-  quick_answer->result_type = ResultType::kDefinitionResult;
-  quick_answer->title.push_back(
-      std::make_unique<QuickAnswerText>(secondary_answer));
-  quick_answer->first_answer_row.push_back(
-      std::make_unique<QuickAnswerResultText>(*definition));
-  ExtractPhoneticsInfo(&quick_answer->phonetics_info, *first_entry);
-  return true;
+  return PopulateQuickAnswer(*structured_result, quick_answer);
 }
 
 const Value::Dict* DefinitionResultParser::ExtractFirstSenseFamily(
@@ -75,7 +191,7 @@
   const Value::Dict* first_sense_family =
       GetFirstListElement(definition_entry, kSenseFamiliesKey);
   if (!first_sense_family) {
-    LOG(ERROR) << "Can't find a sense family.";
+    DLOG(ERROR) << "Can't find a sense family.";
     return nullptr;
   }
 
@@ -95,67 +211,52 @@
   if (sense_family)
     return GetFirstListElement(*sense_family, kPhoneticsKey);
 
-  LOG(ERROR) << "Can't find a phonetics.";
+  DLOG(ERROR) << "Can't find a phonetics.";
   return nullptr;
 }
 
-const std::string* DefinitionResultParser::ExtractDefinition(
-    const base::Value::Dict& definition_entry) {
-  const Value::Dict* first_sense_family =
-      ExtractFirstSenseFamily(definition_entry);
-  if (!first_sense_family)
-    return nullptr;
-
-  const Value::Dict* first_sense =
-      GetFirstListElement(*first_sense_family, kSensesKey);
-  if (!first_sense) {
-    LOG(ERROR) << "Can't find a sense.";
+std::unique_ptr<PhoneticsInfo> DefinitionResultParser::ParsePhoneticsInfo(
+    const base::Value::Dict& entry_result) {
+  const Value::Dict* first_phonetics = ExtractFirstPhonetics(entry_result);
+  if (!first_phonetics) {
+    DLOG(ERROR) << "Unable to find a first phonetics.";
     return nullptr;
   }
 
-  return first_sense->FindStringByDottedPath(kDefinitionPathUnderSense);
-}
+  std::unique_ptr<PhoneticsInfo> phonetics_info =
+      std::make_unique<PhoneticsInfo>();
 
-const std::string* DefinitionResultParser::ExtractPhoneticsText(
-    const base::Value::Dict& definition_entry) {
-  const Value::Dict* first_phonetics = ExtractFirstPhonetics(definition_entry);
-  if (!first_phonetics)
-    return nullptr;
+  const std::string* text = first_phonetics->FindString(kPhoneticsTextKey);
+  if (text) {
+    phonetics_info->text = *text;
+  }
 
-  return first_phonetics->FindStringByDottedPath(kPhoneticsTextKey);
-}
-
-void DefinitionResultParser::ExtractPhoneticsInfo(
-    PhoneticsInfo* phonetics_info,
-    const base::Value::Dict& definition_entry) {
   // Check for the query text used for tts audio.
-  if (definition_entry.FindStringByDottedPath(kHeadwordKey)) {
-    phonetics_info->query_text =
-        *definition_entry.FindStringByDottedPath(kHeadwordKey);
+  const std::string* headword = GetHeadword(entry_result);
+  if (headword) {
+    phonetics_info->query_text = *headword;
   }
 
   // Check for the locale used for tts audio.
-  if (definition_entry.FindStringByDottedPath(kLocaleKey)) {
-    phonetics_info->locale =
-        *definition_entry.FindStringByDottedPath(kLocaleKey);
+  const std::string* locale = entry_result.FindString(kLocaleKey);
+  if (locale) {
+    phonetics_info->locale = *locale;
   }
 
-  const Value::Dict* first_phonetics = ExtractFirstPhonetics(definition_entry);
-
-  if (!first_phonetics)
-    return;
-
   // Check if the phonetics has an audio URL.
-  if (first_phonetics->FindStringByDottedPath(kPhoneticsAudioKey)) {
-    phonetics_info->phonetics_audio =
-        GURL(kHttpsPrefix +
-             *first_phonetics->FindStringByDottedPath(kPhoneticsAudioKey));
+  const std::string* audio_url =
+      first_phonetics->FindString(kPhoneticsAudioKey);
+  if (audio_url) {
+    phonetics_info->phonetics_audio = GURL(kHttpsPrefix + *audio_url);
   }
 
   // Check if tts audio is enabled for the query.
-  if (first_phonetics->FindBoolByDottedPath(kPhoneticsTtsAudioEnabledKey)) {
-    phonetics_info->tts_audio_enabled = true;
+  absl::optional<bool> tts_audio_enabled =
+      first_phonetics->FindBool(kPhoneticsTtsAudioEnabledKey);
+  if (tts_audio_enabled) {
+    phonetics_info->tts_audio_enabled = tts_audio_enabled.value();
   }
+  return phonetics_info;
 }
 
 }  // namespace quick_answers
diff --git a/chromeos/components/quick_answers/search_result_parsers/definition_result_parser.h b/chromeos/components/quick_answers/search_result_parsers/definition_result_parser.h
index 2fb7711c..a4921e7 100644
--- a/chromeos/components/quick_answers/search_result_parsers/definition_result_parser.h
+++ b/chromeos/components/quick_answers/search_result_parsers/definition_result_parser.h
@@ -19,18 +19,19 @@
   // ResultParser:
   bool Parse(const base::Value::Dict& result,
              QuickAnswer* quick_answer) override;
+  std::unique_ptr<StructuredResult> ParseInStructuredResult(
+      const base::Value::Dict& result) override;
+  bool PopulateQuickAnswer(const StructuredResult& structured_result,
+                           QuickAnswer* quick_answer) override;
+  bool SupportsNewInterface() const override;
 
  private:
   const base::Value::Dict* ExtractFirstSenseFamily(
       const base::Value::Dict& definition_entry);
   const base::Value::Dict* ExtractFirstPhonetics(
       const base::Value::Dict& definition_entry);
-  const std::string* ExtractDefinition(
-      const base::Value::Dict& definition_entry);
-  const std::string* ExtractPhoneticsText(
-      const base::Value::Dict& definition_entry);
-  void ExtractPhoneticsInfo(PhoneticsInfo* phonetics_info,
-                            const base::Value::Dict& definition_entry);
+  std::unique_ptr<PhoneticsInfo> ParsePhoneticsInfo(
+      const base::Value::Dict& entry_result);
 };
 
 }  // namespace quick_answers
diff --git a/chromeos/components/quick_answers/search_result_parsers/definition_result_parser_unittest.cc b/chromeos/components/quick_answers/search_result_parsers/definition_result_parser_unittest.cc
index df534dd..890d808f 100644
--- a/chromeos/components/quick_answers/search_result_parsers/definition_result_parser_unittest.cc
+++ b/chromeos/components/quick_answers/search_result_parsers/definition_result_parser_unittest.cc
@@ -9,12 +9,16 @@
 
 #include "base/strings/utf_string_conversions.h"
 #include "base/values.h"
+#include "chromeos/components/quick_answers/quick_answers_model.h"
 #include "chromeos/components/quick_answers/test/test_helpers.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "ui/color/color_id.h"
 
 namespace quick_answers {
 namespace {
+constexpr char kPhoneticsAudioUrlWithoutProtocol[] = "//example.com/audio";
+constexpr char kPhoneticsAudioUrlWithProtocol[] = "https://example.com/audio";
+
 using base::Value;
 }  // namespace
 
@@ -40,11 +44,14 @@
     Value::List entries;
     Value::Dict entry;
 
+    entry.Set("locale", "en");
+
     // Build phonetics.
     if (!phonetic_str.empty()) {
       Value::List phonetics;
       Value::Dict phonetic;
       phonetic.Set("text", phonetic_str);
+      phonetic.Set("oxfordAudio", kPhoneticsAudioUrlWithoutProtocol);
       phonetics.Append(std::move(phonetic));
       entry.Set("phonetics", std::move(phonetics));
     }
@@ -99,6 +106,28 @@
   auto* title = static_cast<QuickAnswerText*>(quick_answer.title[0].get());
   EXPECT_EQ(expected_title, GetQuickAnswerTextForTesting(quick_answer.title));
   EXPECT_EQ(ui::kColorLabelForeground, title->color_id);
+
+  // Expectations for `StructuredResult`.
+  std::unique_ptr<StructuredResult> structured_result =
+      parser_->ParseInStructuredResult(result);
+  ASSERT_TRUE(structured_result);
+  ASSERT_TRUE(structured_result->definition_result);
+
+  DefinitionResult* definition_result =
+      structured_result->definition_result.get();
+  EXPECT_EQ(definition_result->word, "unfathomable");
+
+  // `PhoneticsInfo::query_text` is headword. It's a query text for TTS. We
+  // should not expect this test case response from the server as either
+  // `query_text` or `phonetics_audio` should be filled.
+  EXPECT_TRUE(definition_result->phonetics_info.query_text.empty());
+  EXPECT_EQ(definition_result->phonetics_info.text, "ˌənˈfaT͟Həməb(ə)");
+  EXPECT_EQ(definition_result->phonetics_info.locale, "en");
+  EXPECT_EQ(definition_result->phonetics_info.phonetics_audio,
+            GURL(kPhoneticsAudioUrlWithProtocol));
+  EXPECT_FALSE(definition_result->phonetics_info.tts_audio_enabled);
+
+  EXPECT_EQ(definition_result->sense.definition, expected_answer);
 }
 
 TEST_F(DefinitionResultParserTest, EmptyValue) {
diff --git a/chromeos/components/quick_answers/search_result_parsers/result_parser.cc b/chromeos/components/quick_answers/search_result_parsers/result_parser.cc
index 2e0c531..3e67b7e 100644
--- a/chromeos/components/quick_answers/search_result_parsers/result_parser.cc
+++ b/chromeos/components/quick_answers/search_result_parsers/result_parser.cc
@@ -6,6 +6,7 @@
 
 #include "base/notreached.h"
 #include "base/values.h"
+#include "chromeos/components/quick_answers/quick_answers_model.h"
 #include "chromeos/components/quick_answers/search_result_parsers/definition_result_parser.h"
 #include "chromeos/components/quick_answers/search_result_parsers/kp_entity_result_parser.h"
 #include "chromeos/components/quick_answers/search_result_parsers/unit_conversion_result_parser.h"
@@ -15,6 +16,21 @@
 using base::Value;
 }  // namespace
 
+std::unique_ptr<StructuredResult> ResultParser::ParseInStructuredResult(
+    const base::Value::Dict& result) {
+  return nullptr;
+}
+
+bool ResultParser::PopulateQuickAnswer(
+    const StructuredResult& structured_result,
+    QuickAnswer* quick_answer) {
+  return false;
+}
+
+bool ResultParser::SupportsNewInterface() const {
+  return false;
+}
+
 const Value::Dict* ResultParser::GetFirstListElement(const Value::Dict& dict,
                                                      const std::string& path) {
   const Value::List* entries = dict.FindListByDottedPath(path);
diff --git a/chromeos/components/quick_answers/search_result_parsers/result_parser.h b/chromeos/components/quick_answers/search_result_parsers/result_parser.h
index 8f3ccd8ea..17b0c7f 100644
--- a/chromeos/components/quick_answers/search_result_parsers/result_parser.h
+++ b/chromeos/components/quick_answers/search_result_parsers/result_parser.h
@@ -18,10 +18,26 @@
  public:
   virtual ~ResultParser() = default;
 
-  // Parse the result into |quick_answer|.
+  // Parse the result into `quick_answer`. All `ResultParser`s must support this
+  // for now for backward compatibility reason. `Parse` method would be deleted
+  // after we migrate interfaces of all parsers.
   virtual bool Parse(const base::Value::Dict& result,
                      QuickAnswer* quick_answer) = 0;
 
+  // Interfaces for supporting Rich Answers.
+  virtual std::unique_ptr<StructuredResult> ParseInStructuredResult(
+      const base::Value::Dict& result);
+
+  // `quick_answer` can be modified even if `PopulateQuickAnswer` returns false,
+  // i.e. do not assume that `quick_answer` is un-modified if this method
+  // returns false.
+  virtual bool PopulateQuickAnswer(const StructuredResult& structured_result,
+                                   QuickAnswer* quick_answer);
+
+  // Returns true if this parser supports the new interfaces. Note that all
+  // parsers must support old interfaces even if it supports new interfaces.
+  virtual bool SupportsNewInterface() const;
+
  protected:
   // Helper function to get the first element in a value list, which is expected
   // to be a dictionary.
diff --git a/chromeos/components/quick_answers/search_result_parsers/search_response_parser.cc b/chromeos/components/quick_answers/search_result_parsers/search_response_parser.cc
index 41c7b97c..25dbcff 100644
--- a/chromeos/components/quick_answers/search_result_parsers/search_response_parser.cc
+++ b/chromeos/components/quick_answers/search_result_parsers/search_response_parser.cc
@@ -4,10 +4,12 @@
 
 #include "chromeos/components/quick_answers/search_result_parsers/search_response_parser.h"
 
+#include <memory>
 #include <utility>
 
 #include "base/functional/bind.h"
 #include "base/values.h"
+#include "chromeos/components/quick_answers/quick_answers_model.h"
 #include "chromeos/components/quick_answers/search_result_parsers/result_parser.h"
 
 namespace quick_answers {
@@ -64,9 +66,10 @@
   }
 
   for (const auto& entry : *entries) {
-    auto quick_answer = std::make_unique<QuickAnswer>();
-    if (ProcessResult(&entry, quick_answer.get())) {
-      std::move(complete_callback_).Run(std::move(quick_answer));
+    std::unique_ptr<QuickAnswersSession> quick_answers_session =
+        ProcessResult(&entry);
+    if (quick_answers_session) {
+      std::move(complete_callback_).Run(std::move(quick_answers_session));
       return;
     }
   }
@@ -74,22 +77,55 @@
   std::move(complete_callback_).Run(nullptr);
 }
 
-bool SearchResponseParser::ProcessResult(const Value* result,
-                                         QuickAnswer* quick_answer) {
+std::unique_ptr<QuickAnswersSession> SearchResponseParser::ProcessResult(
+    const Value* result) {
   const base::Value::Dict& dict = result->GetDict();
   auto one_namespace_type = dict.FindInt("oneNamespaceType");
   if (!one_namespace_type.has_value()) {
     // Can't find valid one namespace type from the response.
     LOG(ERROR) << "Can't find valid one namespace type from the response.";
-    return false;
+    return nullptr;
   }
 
   std::unique_ptr<ResultParser> result_parser =
       ResultParserFactory::Create(one_namespace_type.value());
-  if (!result_parser)
-    return false;
+  if (!result_parser) {
+    return nullptr;
+  }
 
-  return result_parser->Parse(dict, quick_answer);
+  if (result_parser->SupportsNewInterface()) {
+    // Try to parse from StructuredResult, which supports Rich Answers.
+    std::unique_ptr<StructuredResult> structured_result =
+        result_parser->ParseInStructuredResult(dict);
+    if (!structured_result) {
+      return nullptr;
+    }
+
+    std::unique_ptr<QuickAnswer> quick_answer = std::make_unique<QuickAnswer>();
+    if (!result_parser->PopulateQuickAnswer(*structured_result,
+                                            quick_answer.get())) {
+      return nullptr;
+    }
+
+    std::unique_ptr<QuickAnswersSession> quick_answers_session =
+        std::make_unique<QuickAnswersSession>();
+    quick_answers_session->structured_result = std::move(structured_result);
+    quick_answers_session->quick_answer = std::move(quick_answer);
+    return quick_answers_session;
+  }
+
+  // If a parser does not support `StructuredResult`, falls back to `Parse`
+  // method. This is for a parser which has not migrated to the new interfaces
+  // yet.
+  std::unique_ptr<QuickAnswer> quick_answer = std::make_unique<QuickAnswer>();
+  if (!result_parser->Parse(dict, quick_answer.get())) {
+    return nullptr;
+  }
+
+  std::unique_ptr<QuickAnswersSession> quick_answers_session =
+      std::make_unique<QuickAnswersSession>();
+  quick_answers_session->quick_answer = std::move(quick_answer);
+  return quick_answers_session;
 }
 
 }  // namespace quick_answers
diff --git a/chromeos/components/quick_answers/search_result_parsers/search_response_parser.h b/chromeos/components/quick_answers/search_result_parsers/search_response_parser.h
index 89d18cb..9afb3001 100644
--- a/chromeos/components/quick_answers/search_result_parsers/search_response_parser.h
+++ b/chromeos/components/quick_answers/search_result_parsers/search_response_parser.h
@@ -10,6 +10,7 @@
 
 #include "base/functional/callback.h"
 #include "base/memory/weak_ptr.h"
+#include "chromeos/components/quick_answers/quick_answers_model.h"
 #include "services/data_decoder/public/cpp/data_decoder.h"
 
 namespace base {
@@ -18,15 +19,13 @@
 
 namespace quick_answers {
 
-struct QuickAnswer;
-
 // Parser for extracting quick answer result out of the search response.
 class SearchResponseParser {
  public:
-  // Callback used when parsing of |quick_answer| is complete. Note that
-  // |quick_answer| may be |nullptr|.
-  using SearchResponseParserCallback =
-      base::OnceCallback<void(std::unique_ptr<QuickAnswer> quick_answer)>;
+  // Callback used when parsing of `quick_answers_session` is complete. Note
+  // that `quick_answers_session` may be `nullptr`.
+  using SearchResponseParserCallback = base::OnceCallback<void(
+      std::unique_ptr<QuickAnswersSession> quick_answers_session)>;
 
   explicit SearchResponseParser(SearchResponseParserCallback complete_callback);
   ~SearchResponseParser();
@@ -41,7 +40,7 @@
   void OnJsonParsed(data_decoder::DataDecoder::ValueOrError result);
   //  void OnJSONParseFailed(const std::string& error_message);
 
-  bool ProcessResult(const base::Value* result, QuickAnswer* quick_answer);
+  std::unique_ptr<QuickAnswersSession> ProcessResult(const base::Value* result);
 
   SearchResponseParserCallback complete_callback_;
 
diff --git a/chromeos/components/quick_answers/search_result_parsers/search_response_parser_unittest.cc b/chromeos/components/quick_answers/search_result_parsers/search_response_parser_unittest.cc
index 8c94bc03..5049523 100644
--- a/chromeos/components/quick_answers/search_result_parsers/search_response_parser_unittest.cc
+++ b/chromeos/components/quick_answers/search_result_parsers/search_response_parser_unittest.cc
@@ -30,8 +30,13 @@
     run_loop_ = std::make_unique<base::RunLoop>();
   }
 
-  void SearchResponseParserCallback(std::unique_ptr<QuickAnswer> quick_answer) {
-    quick_answer_ = std::move(quick_answer);
+  void SearchResponseParserCallback(
+      std::unique_ptr<QuickAnswersSession> quick_answers_session) {
+    if (quick_answers_session) {
+      quick_answer_ = std::move(quick_answers_session->quick_answer);
+    } else {
+      quick_answer_ = nullptr;
+    }
     run_loop_->Quit();
   }
 
diff --git a/chromeos/profiles/arm-exp.afdo.newest.txt b/chromeos/profiles/arm-exp.afdo.newest.txt
index 71455892..0381a2b9 100644
--- a/chromeos/profiles/arm-exp.afdo.newest.txt
+++ b/chromeos/profiles/arm-exp.afdo.newest.txt
@@ -1 +1 @@
-chromeos-chrome-arm-none-115-5715.0-1682940843-benchmark-115.0.5757.0-r1-redacted.afdo.xz
+chromeos-chrome-arm-none-115-5715.0-1683546756-benchmark-115.0.5758.0-r1-redacted.afdo.xz
diff --git a/chromeos/profiles/arm.afdo.newest.txt b/chromeos/profiles/arm.afdo.newest.txt
index 71455892..0381a2b9 100644
--- a/chromeos/profiles/arm.afdo.newest.txt
+++ b/chromeos/profiles/arm.afdo.newest.txt
@@ -1 +1 @@
-chromeos-chrome-arm-none-115-5715.0-1682940843-benchmark-115.0.5757.0-r1-redacted.afdo.xz
+chromeos-chrome-arm-none-115-5715.0-1683546756-benchmark-115.0.5758.0-r1-redacted.afdo.xz
diff --git a/chromeos/profiles/bigcore.afdo.newest.txt b/chromeos/profiles/bigcore.afdo.newest.txt
index 5354d61..0d67df9 100644
--- a/chromeos/profiles/bigcore.afdo.newest.txt
+++ b/chromeos/profiles/bigcore.afdo.newest.txt
@@ -1 +1 @@
-chromeos-chrome-amd64-bigcore-115-5715.0-1682934000-benchmark-115.0.5755.0-r1-redacted.afdo.xz
+chromeos-chrome-amd64-bigcore-115-5735.12-1683542912-benchmark-115.0.5758.0-r1-redacted.afdo.xz
diff --git a/chromeos/ui/frame/multitask_menu/multitask_menu_view.cc b/chromeos/ui/frame/multitask_menu/multitask_menu_view.cc
index 8ea2b59..0a5c7d26 100644
--- a/chromeos/ui/frame/multitask_menu/multitask_menu_view.cc
+++ b/chromeos/ui/frame/multitask_menu/multitask_menu_view.cc
@@ -43,7 +43,7 @@
 
 // If the menu was opened as a result of hovering over the frame size button,
 // moving the mouse outside the menu or size button will result in closing it
-// after 3 seconds have elapsed.
+// after 250 ms have elapsed.
 constexpr base::TimeDelta kMouseExitMenuTimeout = base::Milliseconds(250);
 
 // Creates multitask button with label.
@@ -67,7 +67,7 @@
 
 // -----------------------------------------------------------------------------
 // MultitaskMenuView::MenuPreTargetHandler:
-
+// Auto-closes the multitask menu on click outside or after timeout.
 class MultitaskMenuView::MenuPreTargetHandler : public ui::EventHandler {
  public:
   MenuPreTargetHandler(views::Widget* menu_widget,
@@ -86,6 +86,10 @@
   }
 
   void OnMouseEvent(ui::MouseEvent* event) override {
+    if (!menu_widget_ || menu_widget_->IsClosed()) {
+      return;
+    }
+
     if (event->type() == ui::ET_MOUSE_PRESSED) {
       ProcessPressedEvent(*event);
     }
@@ -108,6 +112,10 @@
   }
 
   void OnTouchEvent(ui::TouchEvent* event) override {
+    if (!menu_widget_ || menu_widget_->IsClosed()) {
+      return;
+    }
+
     if (event->type() == ui::ET_TOUCH_PRESSED) {
       ProcessPressedEvent(*event);
     }
diff --git a/components/browser_ui/bottomsheet/android/internal/java/src/org/chromium/components/browser_ui/bottomsheet/BottomSheet.java b/components/browser_ui/bottomsheet/android/internal/java/src/org/chromium/components/browser_ui/bottomsheet/BottomSheet.java
index 93e2c59..45649a8 100644
--- a/components/browser_ui/bottomsheet/android/internal/java/src/org/chromium/components/browser_ui/bottomsheet/BottomSheet.java
+++ b/components/browser_ui/bottomsheet/android/internal/java/src/org/chromium/components/browser_ui/bottomsheet/BottomSheet.java
@@ -196,8 +196,7 @@
     public boolean shouldGestureMoveSheet(MotionEvent initialEvent, MotionEvent currentEvent) {
         // If the sheet is scrolling off-screen or in the process of hiding, gestures should not
         // affect it.
-        if (getCurrentOffsetPx() < getSheetHeightForState(SheetState.PEEK)
-                || getOffsetFromBrowserControls() > 0 || isHiding()) {
+        if (getOffsetFromBrowserControls() > 0 || isHiding()) {
             return false;
         }
 
diff --git a/components/browser_ui/styles/android/java/res/values/themes.xml b/components/browser_ui/styles/android/java/res/values/themes.xml
index ffe0f61..738166d0 100644
--- a/components/browser_ui/styles/android/java/res/values/themes.xml
+++ b/components/browser_ui/styles/android/java/res/values/themes.xml
@@ -47,4 +47,23 @@
              color state list. -->
         <item name="android:textColorHint">@color/default_text_color_hint_list</item>
     </style>
+
+    <!-- Add a persistent back button toolbar to automotive -->
+    <style name="ThemeOverlay.BrowserUI.Automotive.PersistentBackButtonToolbar" parent="">
+        <item name="windowActionBar">true</item>
+        <item name="windowNoTitle">false</item>
+        <item name="actionBarStyle">@style/ActionBarWithBackButton</item>
+        <item name="actionBarTheme">@style/DarkModeRippleColor</item>
+    </style>
+
+    <!-- Black action bar with white back button -->
+    <style name="ActionBarWithBackButton">
+        <item name="displayOptions">homeAsUp</item>
+        <item name="background">@android:color/black</item>
+        <item name="homeAsUpIndicator">@drawable/ic_arrow_back_white_24dp</item>
+    </style>
+
+    <style name="DarkModeRippleColor">
+        <item name="colorControlHighlight">@color/ripple_material_dark</item>
+    </style>
 </resources>
diff --git a/components/browsing_data/core/counters/history_counter.cc b/components/browsing_data/core/counters/history_counter.cc
index 0dc0efa2..2aac459 100644
--- a/components/browsing_data/core/counters/history_counter.cc
+++ b/components/browsing_data/core/counters/history_counter.cc
@@ -137,7 +137,7 @@
 
 void HistoryCounter::OnGetWebHistoryCount(
     history::WebHistoryService::Request* request,
-    const base::Value* result) {
+    base::optional_ref<base::Value::Dict> result) {
   // Ensure that all callbacks are on the same thread, so that we do not need
   // a mutex for |MergeResults|.
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
@@ -151,10 +151,9 @@
   // If the query failed, err on the safe side and inform the user that they
   // may have history items stored in Sync. Otherwise, we expect at least one
   // entry in the "event" list.
-  if (!result) {
+  if (!result.has_value()) {
     has_synced_visits_ = true;
-  } else if (const base::Value::List* events =
-                 result->GetDict().FindList("event")) {
+  } else if (const base::Value::List* events = result->FindList("event")) {
     has_synced_visits_ = !events->empty();
   } else {
     has_synced_visits_ = false;
diff --git a/components/browsing_data/core/counters/history_counter.h b/components/browsing_data/core/counters/history_counter.h
index 0b593228..9a39413 100644
--- a/components/browsing_data/core/counters/history_counter.h
+++ b/components/browsing_data/core/counters/history_counter.h
@@ -11,6 +11,7 @@
 #include "base/sequence_checker.h"
 #include "base/task/cancelable_task_tracker.h"
 #include "base/timer/timer.h"
+#include "base/types/optional_ref.h"
 #include "components/browsing_data/core/counters/browsing_data_counter.h"
 #include "components/browsing_data/core/counters/sync_tracker.h"
 #include "components/history/core/browser/history_service.h"
@@ -55,7 +56,7 @@
 
   void OnGetLocalHistoryCount(history::HistoryCountResult result);
   void OnGetWebHistoryCount(history::WebHistoryService::Request* request,
-                            const base::Value* result);
+                            base::optional_ref<base::Value::Dict> result);
   void OnWebHistoryTimeout();
   void MergeResults();
 
diff --git a/components/custom_handlers/protocol_handler_registry_browsertest.cc b/components/custom_handlers/protocol_handler_registry_browsertest.cc
index 5e3fd8d..63318ba 100644
--- a/components/custom_handlers/protocol_handler_registry_browsertest.cc
+++ b/components/custom_handlers/protocol_handler_registry_browsertest.cc
@@ -138,10 +138,10 @@
 
   // Attempt to add an entry.
   ProtocolHandlerChangeWaiter waiter(registry);
-  ASSERT_TRUE(content::ExecuteScriptWithoutUserGesture(
-      web_contents(),
-      "navigator.registerProtocolHandler('web+"
-      "search', 'test.html?%s', 'test');"));
+  ASSERT_TRUE(content::ExecJs(web_contents(),
+                              "navigator.registerProtocolHandler('web+"
+                              "search', 'test.html?%s', 'test');",
+                              content::EXECUTE_SCRIPT_NO_USER_GESTURE));
   waiter.Wait();
 
   // Verify the registration is ignored if no user gesture involved.
diff --git a/components/device_signals/core/browser/mac/BUILD.gn b/components/device_signals/core/browser/mac/BUILD.gn
index dd997ca..928af8a 100644
--- a/components/device_signals/core/browser/mac/BUILD.gn
+++ b/components/device_signals/core/browser/mac/BUILD.gn
@@ -13,6 +13,8 @@
     "//components/device_signals/core/common/",
   ]
 
+  configs += [ "//build/config/compiler:enable_arc" ]
+
   frameworks = [ "Foundation.framework" ]
 }
 
@@ -27,4 +29,5 @@
     "//testing/gmock",
     "//testing/gtest",
   ]
+  configs += [ "//build/config/compiler:enable_arc" ]
 }
diff --git a/components/device_signals/core/browser/mac/plist_settings_client.mm b/components/device_signals/core/browser/mac/plist_settings_client.mm
index 14d2e35..9a1a481 100644
--- a/components/device_signals/core/browser/mac/plist_settings_client.mm
+++ b/components/device_signals/core/browser/mac/plist_settings_client.mm
@@ -12,7 +12,6 @@
 #include "base/files/file_util.h"
 #include "base/json/json_writer.h"
 #import "base/mac/foundation_util.h"
-#include "base/mac/scoped_nsobject.h"
 #include "base/strings/stringprintf.h"
 #include "base/strings/sys_string_conversions.h"
 #include "base/task/thread_pool.h"
@@ -21,6 +20,10 @@
 #include "components/device_signals/core/browser/signals_types.h"
 #include "components/device_signals/core/common/platform_utils.h"
 
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
 namespace device_signals {
 
 namespace {
@@ -36,37 +39,40 @@
 id ParseArrays(id data_obj, NSString* path) {
   NSArray* indexes = [path componentsSeparatedByString:@"["];
 
-  for (NSString* index_str in indexes) {
-    if (![data_obj isKindOfClass:[NSArray class]]) {
+  for (NSString* index_with_bracket in indexes) {
+    NSArray* data_array = base::mac::ObjCCast<NSArray>(data_obj);
+    if (!data_array) {
       return nil;
     }
 
-    if (![index_str length])
+    if (!index_with_bracket.length) {
       continue;
+    }
 
     // Checking for the square brackets being closed. If they are not, then the
     // key path is malformed.
-    NSRange range = [index_str rangeOfString:@"]"];
+    NSRange range = [index_with_bracket rangeOfString:@"]"];
     if (range.location == NSNotFound ||
-        range.location < [index_str length] - 1) {
+        range.location < index_with_bracket.length - 1) {
       return nil;
     }
-    index_str = [index_str substringToIndex:range.location];
+    NSString* index_str = [index_with_bracket substringToIndex:range.location];
 
     // Validate that the index is a numeric. If the index is an alpha, issues
     // will occur during string to integer conversion.
-    NSCharacterSet* numeric_set = [NSCharacterSet decimalDigitCharacterSet];
+    NSCharacterSet* numeric_set = NSCharacterSet.decimalDigitCharacterSet;
     if (![numeric_set
             isSupersetOfSet:[NSCharacterSet characterSetWithCharactersInString:
                                                 index_str]]) {
       return nil;
     }
 
-    NSUInteger index = base::checked_cast<NSUInteger>([index_str integerValue]);
-    if (index > [data_obj count])
+    NSUInteger index = base::checked_cast<NSUInteger>(index_str.integerValue);
+    if (index > data_array.count) {
       return nil;
+    }
 
-    data_obj = [data_obj objectAtIndex:index];
+    data_obj = data_array[index];
   }
 
   return data_obj;
@@ -97,7 +103,7 @@
   // This will occur if the key path is incorrect and does not actually point to
   // a setting item. At the end of a parse, the only remaining object should be
   // the single setting item.
-  if ([current_obj isKindOfClass:[NSArray class]] && [current_obj count] != 1) {
+  if ([current_obj isKindOfClass:[NSArray class]] && current_obj.count != 1) {
     return nil;
   }
   return current_obj;
@@ -133,9 +139,9 @@
 
     NSError* error = nil;
     NSURL* url = base::mac::FilePathToNSURL(resolved_path);
-    base::scoped_nsobject<NSDictionary> plist_dict(
-        [[NSDictionary alloc] initWithContentsOfURL:url error:&error]);
-    if (error && [error code] == NSFileReadNoPermissionError) {
+    NSDictionary* plist_dict =
+        [[NSDictionary alloc] initWithContentsOfURL:url error:&error];
+    if (error && error.code == NSFileReadNoPermissionError) {
       item.presence = PresenceValue::kAccessDenied;
       items.push_back(item);
       continue;
@@ -160,7 +166,7 @@
     }
 
     if (NSString* setting_str = base::mac::ObjCCast<NSString>(value_ptr)) {
-      if ([setting_str length] <= kMaxStringSizeInBytes) {
+      if (setting_str.length <= kMaxStringSizeInBytes) {
         std::string setting_json_string;
         base::JSONWriter::Write(
             base::Value(base::SysNSStringToUTF8(setting_str)),
@@ -169,12 +175,12 @@
       }
     } else if (NSNumber* value_num = base::mac::ObjCCast<NSNumber>(value_ptr)) {
       // Differentiating between integer and float types.
-      const char* value_type = [value_num objCType];
+      const char* value_type = value_num.objCType;
       if (strcmp(value_type, "d") == 0 || strcmp(value_type, "f") == 0) {
-        double setting_num = [value_num doubleValue];
+        double setting_num = value_num.doubleValue;
         item.setting_json_value = base::StringPrintf("%f", setting_num);
       } else {
-        int setting_num = [value_num integerValue];
+        int setting_num = value_num.integerValue;
         item.setting_json_value = base::StringPrintf("%d", setting_num);
       }
     }
diff --git a/components/device_signals/core/browser/mac/plist_settings_client_unittest.mm b/components/device_signals/core/browser/mac/plist_settings_client_unittest.mm
index 5172a51..010a5f81 100644
--- a/components/device_signals/core/browser/mac/plist_settings_client_unittest.mm
+++ b/components/device_signals/core/browser/mac/plist_settings_client_unittest.mm
@@ -16,6 +16,10 @@
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
 namespace device_signals {
 
 class PlistSettingsClientTest : public testing::Test {
diff --git a/components/device_signals/core/system_signals/mac/BUILD.gn b/components/device_signals/core/system_signals/mac/BUILD.gn
index cda6f6f..3481e68d 100644
--- a/components/device_signals/core/system_signals/mac/BUILD.gn
+++ b/components/device_signals/core/system_signals/mac/BUILD.gn
@@ -19,6 +19,8 @@
     "//net",
   ]
 
+  configs += [ "//build/config/compiler:enable_arc" ]
+
   frameworks = [
     "CoreFoundation.framework",
     "Foundation.framework",
@@ -39,4 +41,5 @@
     "//testing/gmock",
     "//testing/gtest",
   ]
+  configs += [ "//build/config/compiler:enable_arc" ]
 }
diff --git a/components/device_signals/core/system_signals/mac/mac_platform_delegate.mm b/components/device_signals/core/system_signals/mac/mac_platform_delegate.mm
index 935e2ce..08ef0d0 100644
--- a/components/device_signals/core/system_signals/mac/mac_platform_delegate.mm
+++ b/components/device_signals/core/system_signals/mac/mac_platform_delegate.mm
@@ -14,6 +14,10 @@
 #include "net/cert/asn1_util.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
 
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
 namespace device_signals {
 
 namespace {
diff --git a/components/device_signals/core/system_signals/mac/mac_platform_delegate_unittest.mm b/components/device_signals/core/system_signals/mac/mac_platform_delegate_unittest.mm
index 54db4dfd..526b438 100644
--- a/components/device_signals/core/system_signals/mac/mac_platform_delegate_unittest.mm
+++ b/components/device_signals/core/system_signals/mac/mac_platform_delegate_unittest.mm
@@ -13,6 +13,10 @@
 #include "components/device_signals/test/test_constants.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
 namespace device_signals {
 
 class MacPlatformDelegateTest : public testing::Test {
diff --git a/components/external_intents/android/java/src/org/chromium/components/external_intents/ExternalNavigationHandler.java b/components/external_intents/android/java/src/org/chromium/components/external_intents/ExternalNavigationHandler.java
index 4b63f60..408f1a7 100644
--- a/components/external_intents/android/java/src/org/chromium/components/external_intents/ExternalNavigationHandler.java
+++ b/components/external_intents/android/java/src/org/chromium/components/external_intents/ExternalNavigationHandler.java
@@ -1497,17 +1497,9 @@
                 || ignoreBackForwardNav(params);
     }
 
-    private void recordIntentSelectorMetrics(GURL targetUrl, Intent targetIntent) {
-        if (UrlUtilities.hasIntentScheme(targetUrl)) {
-            RecordHistogram.recordBooleanHistogram(
-                    "Android.Intent.IntentUriWithSelector", targetIntent.getSelector() != null);
-        }
-    }
-
     private OverrideUrlLoadingResult shouldOverrideUrlLoadingInternal(
             ExternalNavigationParams params, Intent targetIntent, GURL browserFallbackUrl,
             MutableBoolean canLaunchExternalFallbackResult) {
-        recordIntentSelectorMetrics(params.getUrl(), targetIntent);
         sanitizeQueryIntentActivitiesIntent(targetIntent);
 
         // Any subsequent navigations should cancel the existing AlertDialog.
diff --git a/components/handoff/BUILD.gn b/components/handoff/BUILD.gn
index 98b286d..fc581f0 100644
--- a/components/handoff/BUILD.gn
+++ b/components/handoff/BUILD.gn
@@ -15,6 +15,7 @@
       "//net",
       "//url",
     ]
+    configs += [ "//build/config/compiler:enable_arc" ]
   }
   if (is_ios) {
     sources += [
diff --git a/components/handoff/handoff_manager.mm b/components/handoff/handoff_manager.mm
index 8232adca..b9ec57d6 100644
--- a/components/handoff/handoff_manager.mm
+++ b/components/handoff/handoff_manager.mm
@@ -5,7 +5,6 @@
 #include "components/handoff/handoff_manager.h"
 
 #include "base/check.h"
-#include "base/mac/scoped_nsobject.h"
 #include "base/notreached.h"
 #include "base/strings/sys_string_conversions.h"
 #include "build/build_config.h"
@@ -20,10 +19,14 @@
 #include "base/mac/mac_util.h"
 #endif
 
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
 @interface HandoffManager ()
 
 // The active user activity.
-@property(nonatomic, retain) NSUserActivity* userActivity;
+@property(nonatomic, strong) NSUserActivity* userActivity;
 
 // Whether the URL of the current tab should be exposed for Handoff.
 - (BOOL)shouldUseActiveURL;
@@ -35,7 +38,6 @@
 
 @implementation HandoffManager {
   GURL _activeURL;
-  NSUserActivity* _userActivity;
   handoff::Origin _origin;
 }
 
@@ -63,11 +65,6 @@
   return self;
 }
 
-- (void)dealloc {
-  [_userActivity release];
-  [super dealloc];
-}
-
 - (void)updateActiveURL:(const GURL&)url {
   _activeURL = url;
   [self updateUserActivity];
@@ -93,20 +90,20 @@
   }
 
   // No change to the user activity.
-  const GURL userActivityURL(net::GURLWithNSURL(self.userActivity.webpageURL));
-  if (userActivityURL == _activeURL)
+  const GURL userActivityURL = net::GURLWithNSURL(self.userActivity.webpageURL);
+  if (userActivityURL == _activeURL) {
     return;
+  }
 
   // Invalidate the old user activity and make a new one.
   [self.userActivity invalidate];
 
-  base::scoped_nsobject<NSUserActivity> userActivity([[NSUserActivity alloc]
-      initWithActivityType:NSUserActivityTypeBrowsingWeb]);
-  self.userActivity = userActivity;
+  self.userActivity = [[NSUserActivity alloc]
+      initWithActivityType:NSUserActivityTypeBrowsingWeb];
   self.userActivity.webpageURL = net::NSURLWithGURL(_activeURL);
   NSString* origin = handoff::StringFromOrigin(_origin);
   DCHECK(origin);
-  self.userActivity.userInfo = @{ handoff::kOriginKey : origin };
+  self.userActivity.userInfo = @{handoff::kOriginKey : origin};
   [self.userActivity becomeCurrent];
 }
 
diff --git a/components/handoff/handoff_utility.mm b/components/handoff/handoff_utility.mm
index f7b84fc..952e200e 100644
--- a/components/handoff/handoff_utility.mm
+++ b/components/handoff/handoff_utility.mm
@@ -4,6 +4,10 @@
 
 #include "components/handoff/handoff_utility.h"
 
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
 namespace handoff {
 
 NSString* const kChromeHandoffActivityType = @"com.google.chrome.handoff";
diff --git a/components/history/core/browser/browsing_history_service.cc b/components/history/core/browser/browsing_history_service.cc
index 6eea0c4..d71755ae 100644
--- a/components/history/core/browser/browsing_history_service.cc
+++ b/components/history/core/browser/browsing_history_service.cc
@@ -20,6 +20,7 @@
 #include "base/strings/utf_string_conversions.h"
 #include "base/time/default_clock.h"
 #include "base/time/time.h"
+#include "base/types/optional_ref.h"
 #include "base/values.h"
 #include "components/history/core/browser/browsing_history_driver.h"
 #include "components/history/core/browser/history_types.h"
@@ -641,7 +642,7 @@
     scoped_refptr<QueryHistoryState> state,
     base::Time start_time,
     WebHistoryService::Request* request,
-    const base::Value* results_value) {
+    base::optional_ref<base::Value::Dict> results_dict) {
   // If the response came in too late, do nothing.
   // TODO(dubroy): Maybe show a banner, and prompt the user to reload?
   if (!web_history_timer_->IsRunning())
@@ -649,10 +650,9 @@
   web_history_timer_->Stop();
   web_history_request_.reset();
 
-  if (results_value) {
+  if (results_dict.has_value()) {
     has_synced_results_ = true;
-    if (const base::Value::List* events =
-            results_value->GetDict().FindList("event")) {
+    if (const base::Value::List* events = results_dict->FindList("event")) {
       state->remote_results.reserve(state->remote_results.size() +
                                     events->size());
       std::string host_name_utf8 = base::UTF16ToUTF8(state->search_text);
@@ -708,8 +708,9 @@
           const std::string* timestamp_string;
           int64_t timestamp_usec = 0;
 
-          if (!id.is_dict() ||
-              !(timestamp_string = id.FindStringKey("timestamp_usec")) ||
+          auto* id_dict = id.GetIfDict();
+          if (!id_dict ||
+              !(timestamp_string = id_dict->FindString("timestamp_usec")) ||
               !base::StringToInt64(*timestamp_string, &timestamp_usec)) {
             NOTREACHED() << "Unable to extract timestamp.";
             continue;
@@ -732,7 +733,7 @@
       }
     }
     const std::string* continuation_token =
-        results_value->FindStringKey("continuation_token");
+        results_dict->FindString("continuation_token");
     state->remote_status = !continuation_token || continuation_token->empty()
                                ? REACHED_BEGINNING
                                : MORE_RESULTS;
diff --git a/components/history/core/browser/browsing_history_service.h b/components/history/core/browser/browsing_history_service.h
index 6768ee81..7792529f 100644
--- a/components/history/core/browser/browsing_history_service.h
+++ b/components/history/core/browser/browsing_history_service.h
@@ -224,10 +224,11 @@
   void WebHistoryTimeout(scoped_refptr<QueryHistoryState> state);
 
   // Callback from the WebHistoryService when a query has completed.
-  void WebHistoryQueryComplete(scoped_refptr<QueryHistoryState> state,
-                               base::Time start_time,
-                               WebHistoryService::Request* request,
-                               const base::Value* results_value);
+  void WebHistoryQueryComplete(
+      scoped_refptr<QueryHistoryState> state,
+      base::Time start_time,
+      WebHistoryService::Request* request,
+      base::optional_ref<base::Value::Dict> results_dict);
 
   // Callback telling us whether other forms of browsing history were found
   // on the history server.
diff --git a/components/history/core/browser/web_history_service.cc b/components/history/core/browser/web_history_service.cc
index e3d89d9..7281629 100644
--- a/components/history/core/browser/web_history_service.cc
+++ b/components/history/core/browser/web_history_service.cc
@@ -41,8 +41,7 @@
 
 namespace {
 
-const char kHistoryOAuthScope[] =
-    "https://www.googleapis.com/auth/chromesync";
+const char kHistoryOAuthScope[] = "https://www.googleapis.com/auth/chromesync";
 
 const char kHistoryQueryHistoryUrl[] =
     "https://history.google.com/history/api/lookup?client=chrome";
@@ -267,8 +266,9 @@
 // Converts a time into a string for use as a parameter in a request to the
 // history server.
 std::string ServerTimeString(base::Time time) {
-  if (time < base::Time::UnixEpoch())
+  if (time < base::Time::UnixEpoch()) {
     return base::NumberToString(0);
+  }
   return base::NumberToString(
       (time - base::Time::UnixEpoch()).InMicroseconds());
 }
@@ -295,8 +295,8 @@
   url = net::AppendQueryParameter(url, "max", ServerTimeString(end_time));
 
   if (!options.begin_time.is_null()) {
-    url = net::AppendQueryParameter(
-        url, "min", ServerTimeString(options.begin_time));
+    url = net::AppendQueryParameter(url, "min",
+                                    ServerTimeString(options.begin_time));
   }
 
   if (options.max_count) {
@@ -304,11 +304,13 @@
                                     base::NumberToString(options.max_count));
   }
 
-  if (!text_query.empty())
+  if (!text_query.empty()) {
     url = net::AppendQueryParameter(url, "q", base::UTF16ToUTF8(text_query));
+  }
 
-  if (!version_info.empty())
+  if (!version_info.empty()) {
     url = net::AppendQueryParameter(url, "kvi", version_info);
+  }
 
   return url;
 }
@@ -320,8 +322,9 @@
                                  const GURL& url) {
   base::Value::Dict deletion;
   deletion.Set("type", "CHROME_HISTORY");
-  if (url.is_valid())
+  if (url.is_valid()) {
     deletion.Set("url", url.spec());
+  }
   deletion.Set("min_timestamp_usec", min_time);
   deletion.Set("max_timestamp_usec", max_time);
   return deletion;
@@ -358,15 +361,17 @@
 }
 
 // static
-absl::optional<base::Value> WebHistoryService::ReadResponse(
+absl::optional<base::Value::Dict> WebHistoryService::ReadResponse(
     WebHistoryService::Request* request) {
-  if (request->GetResponseCode() == net::HTTP_OK) {
-    absl::optional<base::Value> value =
-        base::JSONReader::Read(request->GetResponseBody());
-    if (value && value->is_dict())
-      return value;
-    DLOG(WARNING) << "Non-JSON response received from history server.";
+  if (request->GetResponseCode() != net::HTTP_OK) {
+    return absl::nullopt;
   }
+  absl::optional<base::Value> value =
+      base::JSONReader::Read(request->GetResponseBody());
+  if (value && value->is_dict()) {
+    return std::move(*value).TakeDict();
+  }
+  DLOG(WARNING) << "Non-JSON response received from history server.";
   return absl::nullopt;
 }
 
@@ -399,15 +404,18 @@
     std::string min_timestamp = ServerTimeString(expire.begin_time);
     // TODO(dubroy): Use sane time (crbug.com/146090) here when it's available.
     base::Time end_time = expire.end_time;
-    if (end_time.is_null() || end_time > now)
+    if (end_time.is_null() || end_time > now) {
       end_time = now;
+    }
     std::string max_timestamp = ServerTimeString(end_time);
 
-    for (const auto& url : expire.urls)
+    for (const auto& url : expire.urls) {
       deletions.Append(CreateDeletion(min_timestamp, max_timestamp, url));
+    }
     // If no URLs were specified, delete everything in the time range.
-    if (expire.urls.empty())
+    if (expire.urls.empty()) {
       deletions.Append(CreateDeletion(min_timestamp, max_timestamp, GURL()));
+    }
   }
   delete_request.Set("del", std::move(deletions));
   std::string post_data;
@@ -417,8 +425,9 @@
 
   // Append the version info token, if it is available, to help ensure
   // consistency with any previous deletions.
-  if (!server_version_info_.empty())
+  if (!server_version_info_.empty()) {
     url = net::AppendQueryParameter(url, "kvi", server_version_info_);
+  }
 
   // Wrap the original callback into a generic completion callback.
   CompletionCallback completion_callback =
@@ -550,11 +559,9 @@
     WebHistoryService::QueryWebHistoryCallback callback,
     WebHistoryService::Request* request,
     bool success) {
-  absl::optional<base::Value> response_value;
-  if (success)
-    response_value = ReadResponse(request);
-  std::move(callback).Run(request,
-                          response_value ? &(*response_value) : nullptr);
+  absl::optional<base::Value::Dict> response =
+      success ? ReadResponse(request) : absl::nullopt;
+  std::move(callback).Run(request, response);
 }
 
 void WebHistoryService::ExpireHistoryCompletionCallback(
@@ -565,19 +572,25 @@
       std::move(pending_expire_requests_[request]);
   pending_expire_requests_.erase(request);
 
-  absl::optional<base::Value> response_value;
-  if (success) {
-    response_value = ReadResponse(request);
-    if (response_value) {
-      if (const auto* version = response_value->FindStringKey("version_info"))
-        server_version_info_ = *version;
-      // Inform the observers about the history deletion.
-      for (WebHistoryServiceObserver& observer : observer_list_)
-        observer.OnWebHistoryDeleted();
-    }
+  if (!success) {
+    std::move(callback).Run(/*success=*/false);
+    return;
   }
 
-  std::move(callback).Run(response_value && success);
+  absl::optional<base::Value::Dict> response = ReadResponse(request);
+  if (!response) {
+    std::move(callback).Run(/*success=*/false);
+    return;
+  }
+
+  if (const auto* version = response->FindString("version_info")) {
+    server_version_info_ = *version;
+  }
+  // Inform the observers about the history deletion.
+  for (WebHistoryServiceObserver& observer : observer_list_) {
+    observer.OnWebHistoryDeleted();
+  }
+  std::move(callback).Run(/*success=*/true);
 }
 
 void WebHistoryService::AudioHistoryCompletionCallback(
@@ -588,22 +601,25 @@
       std::move(pending_audio_history_requests_[request]);
   pending_audio_history_requests_.erase(request);
 
-  absl::optional<base::Value> response_value;
-  bool enabled_value = false;
-  if (success) {
-    response_value = ReadResponse(request);
-    if (response_value) {
-      if (absl::optional<bool> enabled =
-              response_value->GetDict().FindBool("history_recording_enabled")) {
-        enabled_value = *enabled;
-      }
+  if (!success) {
+    std::move(callback).Run(/*success=*/false, /*new_enabled_value=*/false);
+    return;
+  }
+
+  if (absl::optional<base::Value::Dict> response = ReadResponse(request)) {
+    bool enabled_value = false;
+    if (absl::optional<bool> enabled =
+            response->FindBool("history_recording_enabled")) {
+      enabled_value = *enabled;
     }
+    std::move(callback).Run(/*success=*/true, enabled_value);
+    return;
   }
 
   // If there is no response_value, then for our purposes, the request has
   // failed, despite receiving a true `success` value. This can happen if
   // the user is offline.
-  std::move(callback).Run(success && response_value, enabled_value);
+  std::move(callback).Run(/*success=*/false, /*new_enabled_value=*/false);
 }
 
 void WebHistoryService::QueryWebAndAppActivityCompletionCallback(
@@ -614,20 +630,21 @@
       std::move(pending_web_and_app_activity_requests_[request]);
   pending_web_and_app_activity_requests_.erase(request);
 
-  absl::optional<base::Value> response_value;
-  bool web_and_app_activity_enabled = false;
+  if (!success) {
+    std::move(callback).Run(/*web_and_app_activity_enabled=*/false);
+    return;
+  }
 
-  if (success) {
-    response_value = ReadResponse(request);
-    if (response_value) {
-      if (absl::optional<bool> enabled =
-              response_value->GetDict().FindBool("history_recording_enabled")) {
-        web_and_app_activity_enabled = *enabled;
-      }
+  if (absl::optional<base::Value::Dict> response = ReadResponse(request)) {
+    if (absl::optional<bool> enabled =
+            response->FindBool("history_recording_enabled")) {
+      std::move(callback).Run(
+          /*web_and_app_activity_enabled=*/*enabled);
+      return;
     }
   }
 
-  std::move(callback).Run(web_and_app_activity_enabled);
+  std::move(callback).Run(/*web_and_app_activity_enabled=*/false);
 }
 
 void WebHistoryService::QueryOtherFormsOfBrowsingHistoryCompletionCallback(
@@ -641,8 +658,9 @@
   bool has_other_forms_of_browsing_history = false;
   if (success && request->GetResponseCode() == net::HTTP_OK) {
     sync_pb::HistoryStatusResponse history_status;
-    if (history_status.ParseFromString(request->GetResponseBody()))
+    if (history_status.ParseFromString(request->GetResponseBody())) {
       has_other_forms_of_browsing_history = history_status.has_derived_data();
+    }
   }
 
   std::move(callback).Run(has_other_forms_of_browsing_history);
diff --git a/components/history/core/browser/web_history_service.h b/components/history/core/browser/web_history_service.h
index 7221623..56ecb950 100644
--- a/components/history/core/browser/web_history_service.h
+++ b/components/history/core/browser/web_history_service.h
@@ -17,6 +17,7 @@
 #include "base/memory/ref_counted.h"
 #include "base/memory/weak_ptr.h"
 #include "base/observer_list.h"
+#include "base/types/optional_ref.h"
 #include "components/history/core/browser/history_types.h"
 #include "components/keyed_service/core/keyed_service.h"
 #include "net/traffic_annotation/network_traffic_annotation.h"
@@ -84,7 +85,7 @@
   // TODO(dubroy): Extract the dictionary Value into a structured results
   // object.
   using QueryWebHistoryCallback =
-      base::OnceCallback<void(Request*, const base::Value*)>;
+      base::OnceCallback<void(Request*, base::optional_ref<base::Value::Dict>)>;
 
   using ExpireWebHistoryCallback = base::OnceCallback<void(bool success)>;
 
@@ -176,10 +177,10 @@
                                  const net::PartialNetworkTrafficAnnotationTag&
                                      partial_traffic_annotation);
 
-  // Extracts a JSON-encoded HTTP response into a dictionary Value.
+  // Extracts a JSON-encoded HTTP response into a base::Value::Dict.
   // If `request`'s HTTP response code indicates failure, or if the response
   // body is not JSON, nullopt is returned.
-  static absl::optional<base::Value> ReadResponse(Request* request);
+  static absl::optional<base::Value::Dict> ReadResponse(Request* request);
 
   // Called by `request` when a web history query has completed. Unpacks the
   // response and calls `callback`, which is the original callback that was
diff --git a/components/history/core/browser/web_history_service_unittest.cc b/components/history/core/browser/web_history_service_unittest.cc
index 11b0490..5734664f 100644
--- a/components/history/core/browser/web_history_service_unittest.cc
+++ b/components/history/core/browser/web_history_service_unittest.cc
@@ -55,7 +55,7 @@
 
   // This is sorta an override but override and static don't mix.
   // This function just calls WebHistoryService::ReadResponse.
-  static absl::optional<base::Value> ReadResponse(Request* request);
+  static absl::optional<base::Value::Dict> ReadResponse(Request* request);
 
   const std::string& GetExpectedPostData(WebHistoryService::Request* request);
 
@@ -173,7 +173,7 @@
   return request;
 }
 
-absl::optional<base::Value> TestingWebHistoryService::ReadResponse(
+absl::optional<base::Value::Dict> TestingWebHistoryService::ReadResponse(
     Request* request) {
   return WebHistoryService::ReadResponse(request);
 }
@@ -327,12 +327,12 @@
                       "{\n"         /* response body */
                       "  \"history_recording_enabled\": true\n"
                       "}"));
-  absl::optional<base::Value> response_value;
   // ReadResponse deletes the request
-  response_value = TestingWebHistoryService::ReadResponse(request.get());
+  auto response_value = TestingWebHistoryService::ReadResponse(request.get());
+  ASSERT_TRUE(response_value);
   bool enabled_value = false;
   if (absl::optional<bool> enabled =
-          response_value->GetDict().FindBool("history_recording_enabled")) {
+          response_value->FindBool("history_recording_enabled")) {
     enabled_value = *enabled;
   }
   EXPECT_TRUE(enabled_value);
@@ -344,12 +344,12 @@
       "{\n"
       "  \"history_recording_enabled\": false\n"
       "}"));
-  absl::optional<base::Value> response_value2;
   // ReadResponse deletes the request
-  response_value2 = TestingWebHistoryService::ReadResponse(request2.get());
+  auto response_value2 = TestingWebHistoryService::ReadResponse(request2.get());
+  ASSERT_TRUE(response_value2);
   enabled_value = true;
   if (absl::optional<bool> enabled =
-          response_value2->GetDict().FindBool("history_recording_enabled")) {
+          response_value2->FindBool("history_recording_enabled")) {
     enabled_value = *enabled;
   }
   EXPECT_FALSE(enabled_value);
@@ -361,9 +361,8 @@
                       "{\n"
                       "  \"history_recording_enabled\": true\n"
                       "}"));
-  absl::optional<base::Value> response_value3;
   // ReadResponse deletes the request
-  response_value3 = TestingWebHistoryService::ReadResponse(request3.get());
+  auto response_value3 = TestingWebHistoryService::ReadResponse(request3.get());
   EXPECT_FALSE(response_value3);
 
   // Test that improperly formatted response returns false.
@@ -375,9 +374,8 @@
       "{\n"
       "  \"history_recording_enabled\": not true\n"
       "}"));
-  absl::optional<base::Value> response_value4;
   // ReadResponse deletes the request
-  response_value4 = TestingWebHistoryService::ReadResponse(request4.get());
+  auto response_value4 = TestingWebHistoryService::ReadResponse(request4.get());
   EXPECT_FALSE(response_value4);
 
   // Test that improperly formatted response returns false.
@@ -386,11 +384,10 @@
       "{\n"
       "  \"history_recording\": true\n"
       "}"));
-  absl::optional<base::Value> response_value5;
   // ReadResponse deletes the request
-  response_value5 = TestingWebHistoryService::ReadResponse(request5.get());
-  EXPECT_FALSE(
-      response_value5->GetDict().FindBool("history_recording_enabled"));
+  auto response_value5 = TestingWebHistoryService::ReadResponse(request5.get());
+  ASSERT_TRUE(response_value5);
+  EXPECT_FALSE(response_value5->FindBool("history_recording_enabled"));
 }
 
 }  // namespace history
diff --git a/components/optimization_guide/core/prediction_manager.cc b/components/optimization_guide/core/prediction_manager.cc
index e182f65c..8547d9f39 100644
--- a/components/optimization_guide/core/prediction_manager.cc
+++ b/components/optimization_guide/core/prediction_manager.cc
@@ -360,7 +360,7 @@
   proto::ModelInfo base_model_info;
   // There should only be one supported model engine version at a time.
   base_model_info.add_supported_model_engine_versions(
-      proto::MODEL_ENGINE_VERSION_TFLITE_2_13);
+      proto::MODEL_ENGINE_VERSION_TFLITE_2_14);
   // This histogram is used for integration tests. Do not remove.
   // Update this to be 10000 if/when we exceed 100 model engine versions.
   LOCAL_HISTOGRAM_COUNTS_100(
diff --git a/components/optimization_guide/proto/models.proto b/components/optimization_guide/proto/models.proto
index d1c971432..2632b0cb 100644
--- a/components/optimization_guide/proto/models.proto
+++ b/components/optimization_guide/proto/models.proto
@@ -226,6 +226,8 @@
   MODEL_ENGINE_VERSION_TFLITE_2_12 = 11;
   // A model using only operations that are supported by TensorflowLite 2.13.0.
   MODEL_ENGINE_VERSION_TFLITE_2_13 = 12;
+  // A model using only operations that are supported by TensorflowLite 2.14.0.
+  MODEL_ENGINE_VERSION_TFLITE_2_14 = 13;
 }
 
 // The set of parameters that the server can use to vary the served model for
diff --git a/components/policy/resources/templates/policies.yaml b/components/policy/resources/templates/policies.yaml
index ce549ca..9f77e5e2 100644
--- a/components/policy/resources/templates/policies.yaml
+++ b/components/policy/resources/templates/policies.yaml
@@ -1100,6 +1100,9 @@
   1099: ShowDisplaySizeScreenEnabled
   1100: AnonymousSearchEnabled
   1101: LegacyTechReportAllowlist
+  1102: ReportAppInventory
+  1103: ReportAppUsage
+  1104: ReportAppUsageCollectionRateMs
 atomic_groups:
   1: Homepage
   2: RemoteAccess
diff --git a/components/policy/resources/templates/policy_definitions/UserAndDeviceReporting/ReportAppInventory.yaml b/components/policy/resources/templates/policy_definitions/UserAndDeviceReporting/ReportAppInventory.yaml
new file mode 100644
index 0000000..9fd9caa
--- /dev/null
+++ b/components/policy/resources/templates/policy_definitions/UserAndDeviceReporting/ReportAppInventory.yaml
@@ -0,0 +1,57 @@
+caption: App inventory reporting
+default: []
+desc: |-
+  Reports app inventory data for affiliated users.
+
+        Setting the policy controls app install, launch and uninstall event reporting for specified app types.
+        If unset, no app events will be reported.
+example_value:
+- chrome_apps_and_extensions
+- progressive_web_apps
+features:
+  dynamic_refresh: true
+  per_profile: true
+items:
+- caption: Chrome apps and extensions
+  value: chrome_apps_and_extensions
+  name: chrome_apps_and_extensions
+- caption: Progressive Web Apps
+  value: progressive_web_apps
+  name: progressive_web_apps
+- caption: Android applications
+  value: android_apps
+  name: android_apps
+- caption: Linux applications
+  value: linux_apps
+  name: linux_apps
+- caption: System applications
+  value: system_apps
+  name: system_apps
+- caption: Games
+  value: games
+  name: games
+- caption: Browser
+  value: browser
+  name: browser
+owners:
+- vshenvi@google.com
+- cros-reporting-team@google.com
+schema:
+  type: array
+  items:
+    type: string
+    enum:
+    - chrome_apps_and_extensions
+    - progressive_web_apps
+    - android_apps
+    - linux_apps
+    - system_apps
+    - games
+    - browser
+supported_chrome_os_management:
+- google_cloud
+future_on:
+- chrome_os
+tags:
+- admin-sharing
+type: string-enum-list
diff --git a/components/policy/resources/templates/policy_definitions/UserAndDeviceReporting/ReportAppUsage.yaml b/components/policy/resources/templates/policy_definitions/UserAndDeviceReporting/ReportAppUsage.yaml
new file mode 100644
index 0000000..54ad6070
--- /dev/null
+++ b/components/policy/resources/templates/policy_definitions/UserAndDeviceReporting/ReportAppUsage.yaml
@@ -0,0 +1,57 @@
+caption: App usage reporting
+default: []
+desc: |-
+  Reports app usage telemetry data for affiliated users.
+
+        Setting the policy controls app usage telemetry reporting for specified app types.
+        If unset, no app usage telemetry will be reported.
+example_value:
+- chrome_apps_and_extensions
+- progressive_web_apps
+features:
+  dynamic_refresh: true
+  per_profile: true
+items:
+- caption: Chrome apps and extensions
+  value: chrome_apps_and_extensions
+  name: chrome_apps_and_extensions
+- caption: Progressive Web Apps
+  value: progressive_web_apps
+  name: progressive_web_apps
+- caption: Android applications
+  value: android_apps
+  name: android_apps
+- caption: Linux applications
+  value: linux_apps
+  name: linux_apps
+- caption: System applications
+  value: system_apps
+  name: system_apps
+- caption: Games
+  value: games
+  name: games
+- caption: Browser
+  value: browser
+  name: browser
+owners:
+- vshenvi@google.com
+- cros-reporting-team@google.com
+schema:
+  type: array
+  items:
+    type: string
+    enum:
+    - chrome_apps_and_extensions
+    - progressive_web_apps
+    - android_apps
+    - linux_apps
+    - system_apps
+    - games
+    - browser
+supported_chrome_os_management:
+- google_cloud
+future_on:
+- chrome_os
+tags:
+- admin-sharing
+type: string-enum-list
diff --git a/components/policy/resources/templates/policy_definitions/UserAndDeviceReporting/ReportAppUsageCollectionRateMs.yaml b/components/policy/resources/templates/policy_definitions/UserAndDeviceReporting/ReportAppUsageCollectionRateMs.yaml
new file mode 100644
index 0000000..a10d17d
--- /dev/null
+++ b/components/policy/resources/templates/policy_definitions/UserAndDeviceReporting/ReportAppUsageCollectionRateMs.yaml
@@ -0,0 +1,25 @@
+caption: App usage telemetry collection rate in milliseconds.
+default: 900000
+desc: |-
+  Rate at which app usage telemetry is collected on enrolled devices for affiliated users. The minimum allowed is 5 minutes.
+
+        If not set, the default rate of 15 minutes applies.
+example_value: 900000
+features:
+  cloud_only: true
+  dynamic_refresh: true
+  per_profile: true
+  unlisted: true
+owners:
+- vshenvi@google.com
+- cros-reporting-team@google.com
+schema:
+  minimum: 300000
+  type: integer
+supported_chrome_os_management:
+- google_cloud
+future_on:
+- chrome_os
+tags:
+- admin-sharing
+type: int
diff --git a/components/policy/test/data/policy_test_cases.json b/components/policy/test/data/policy_test_cases.json
index 20d38f4..6db74d6f 100644
--- a/components/policy/test/data/policy_test_cases.json
+++ b/components/policy/test/data/policy_test_cases.json
@@ -22954,5 +22954,80 @@
   },
   "AnonymousSearchEnabled": {
     "reason_for_missing_test": "TODO(b/275929724): Feature not yet implemented"
+  },
+  "ReportAppInventory": {
+    "os": [ "chromeos_ash" ],
+    "policy_pref_mapping_tests": [
+      {
+        "note": "Check default values (no policies set).",
+        "policies": {},
+        "prefs": {
+          "reporting.report_app_inventory": {
+            "default_value": []
+          }
+        }
+      },
+      {
+        "note": "Simple list value.",
+        "policies": {
+          "ReportAppInventory": [ "chrome_apps_and_extensions" ]
+        },
+        "prefs": {
+          "reporting.report_app_inventory": {
+            "value": [ "chrome_apps_and_extensions" ]
+          }
+        }
+      }
+    ]
+  },
+  "ReportAppUsage": {
+    "os": [ "chromeos_ash" ],
+    "policy_pref_mapping_tests": [
+      {
+        "note": "Check default values (no policies set).",
+        "policies": {},
+        "prefs": {
+          "reporting.report_app_usage": {
+            "default_value": []
+          }
+        }
+      },
+      {
+        "note": "Simple list value.",
+        "policies": {
+          "ReportAppUsage": [ "chrome_apps_and_extensions" ]
+        },
+        "prefs": {
+          "reporting.report_app_usage": {
+            "value": [ "chrome_apps_and_extensions" ]
+          }
+        }
+      }
+    ]
+  },
+  "ReportAppUsageCollectionRateMs": {
+    "os": [ "chromeos_ash" ],
+    "policy_pref_mapping_tests": [
+      {
+        "note": "Check default values (no policies set).",
+        "policies": {},
+        "prefs": {
+          "reporting.report_app_usage_collection_rate_ms": {
+            "default_value": 900000
+          }
+        }
+      },
+      {
+        "note": "Simple value.",
+        "policies": {
+          "ReportAppUsageCollectionRateMs": 300000
+        },
+        "prefs": {
+          "reporting.report_app_usage_collection_rate_ms": {
+            "value": 300000
+          }
+        }
+      }
+    ]
   }
 }
diff --git a/components/services/storage/public/cpp/quota_error_or.h b/components/services/storage/public/cpp/quota_error_or.h
index 1105d27..8dff995 100644
--- a/components/services/storage/public/cpp/quota_error_or.h
+++ b/components/services/storage/public/cpp/quota_error_or.h
@@ -9,15 +9,19 @@
 
 namespace storage {
 
+// These values are logged to UMA. Entries should not be renumbered and numeric
+// values should never be reused. Please keep in sync with "QuotaError" in
+// tools/metrics/histograms/enums.xml.
 enum class QuotaError {
   kNone = 0,
-  kUnknownError,
-  kDatabaseError,
-  kNotFound,
-  kEntryExistsError,
-  kFileOperationError,
-  kInvalidExpiration,
-  kQuotaExceeded,
+  kUnknownError = 1,
+  kDatabaseError = 2,
+  kNotFound = 3,
+  kEntryExistsError = 4,
+  kFileOperationError = 5,
+  kInvalidExpiration = 6,
+  kQuotaExceeded = 7,
+  kMaxValue = kQuotaExceeded
 };
 
 struct DetailedQuotaError {
diff --git a/components/viz/service/display/display.cc b/components/viz/service/display/display.cc
index 6572a09..53caa88 100644
--- a/components/viz/service/display/display.cc
+++ b/components/viz/service/display/display.cc
@@ -614,12 +614,23 @@
       continue;
     }
 
+    DBG_DRAW_RECT_OPT("frame.render_pass.output_rect", DBG_OPT_BLUE,
+                      render_pass->output_rect);
     DBG_DRAW_RECT_OPT("frame.render_pass.damage", DBG_OPT_RED,
                       render_pass->damage_rect);
 
-    DBG_LOG_OPT("frame.render_pass.numquads", DBG_OPT_BLUE,
-                "Num render pass quads=%d",
-                static_cast<int>(render_pass->quad_list.size()));
+    DBG_LOG_OPT("frame.render_pass.meta", DBG_OPT_BLUE,
+                "Render pass id=%" PRIu64
+                ", output_rect=(%s), damage_rect=(%s), "
+                "quad_list.size=%zu",
+                render_pass->id.value(),
+                render_pass->output_rect.ToString().c_str(),
+                render_pass->damage_rect.ToString().c_str(),
+                render_pass->quad_list.size());
+    DBG_LOG_OPT(
+        "frame.render_pass.transform_to_root_target", DBG_OPT_BLUE,
+        "Render pass transform=%s",
+        render_pass->transform_to_root_target.ToDecomposedString().c_str());
 
     for (auto* quad : render_pass->quad_list) {
       auto* sqs = quad->shared_quad_state;
diff --git a/components/wifi/BUILD.gn b/components/wifi/BUILD.gn
index c05f146..5b114d8 100644
--- a/components/wifi/BUILD.gn
+++ b/components/wifi/BUILD.gn
@@ -35,6 +35,7 @@
       "Foundation.framework",
       "SystemConfiguration.framework",
     ]
+    configs += [ "//build/config/compiler:enable_arc" ]
   }
 
   if (is_fuchsia) {
diff --git a/components/wifi/wifi_service_mac.mm b/components/wifi/wifi_service_mac.mm
index 8dfca07..523fab0 100644
--- a/components/wifi/wifi_service_mac.mm
+++ b/components/wifi/wifi_service_mac.mm
@@ -16,7 +16,6 @@
 #include "base/functional/bind.h"
 #include "base/mac/foundation_util.h"
 #include "base/mac/scoped_cftyperef.h"
-#include "base/mac/scoped_nsobject.h"
 #include "base/strings/sys_string_conversions.h"
 #import "base/task/sequenced_task_runner.h"
 #import "base/task/single_thread_task_runner.h"
@@ -26,9 +25,13 @@
 #include "components/wifi/network_properties.h"
 #include "crypto/apple_keychain.h"
 
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
 namespace wifi {
 
-// Implementation of WiFiService for Mac OS X.
+// Implementation of WiFiService for macOS.
 class WiFiServiceMac : public WiFiService {
  public:
   WiFiServiceMac();
@@ -140,9 +143,10 @@
   void NotifyNetworkChanged(const std::string& network_guid);
 
   // Default interface.
-  base::scoped_nsobject<CWInterface> interface_;
-  // WLAN Notifications observer. |this| doesn't own this reference.
-  id wlan_observer_;
+  CWInterface* __strong interface_;
+
+  // WLAN Notifications observer.
+  id __strong wlan_observer_;
 
   // Observer to get notified when network(s) have changed (e.g. connect).
   NetworkGuidListCallback networks_changed_observer_;
@@ -160,8 +164,7 @@
   base::Value::Dict network_properties_;
 };
 
-WiFiServiceMac::WiFiServiceMac() : wlan_observer_(nil) {
-}
+WiFiServiceMac::WiFiServiceMac() = default;
 
 WiFiServiceMac::~WiFiServiceMac() {
   UnInitialize();
@@ -170,7 +173,7 @@
 void WiFiServiceMac::Initialize(
   scoped_refptr<base::SequencedTaskRunner> task_runner) {
   task_runner_.swap(task_runner);
-  interface_.reset([[[CWWiFiClient sharedWiFiClient] interface] retain]);
+  interface_ = [[CWWiFiClient sharedWiFiClient] interface];
   if (!interface_) {
     DVLOG(1) << "Failed to initialize default interface.";
     return;
@@ -178,9 +181,10 @@
 }
 
 void WiFiServiceMac::UnInitialize() {
-  if (wlan_observer_)
-    [[NSNotificationCenter defaultCenter] removeObserver:wlan_observer_];
-  interface_.reset();
+  if (wlan_observer_) {
+    [NSNotificationCenter.defaultCenter removeObserver:wlan_observer_];
+  }
+  interface_ = nil;
 }
 
 void WiFiServiceMac::GetProperties(const std::string& network_guid,
@@ -285,7 +289,7 @@
   if (CheckError(ns_error, kErrorScanForNetworksWithName, error))
     return;
 
-  CWNetwork* network = [networks anyObject];
+  CWNetwork* network = networks.anyObject;
   if (network == nil) {
     // System can't find the network, remove it from the |networks_| and notify
     // observers.
@@ -354,11 +358,11 @@
   static const char kAirPortServiceName[] = "AirPort";
 
   UInt32 password_length = 0;
-  void *password_data = NULL;
+  void* password_data = nullptr;
   crypto::AppleKeychain keychain;
   OSStatus status = keychain.FindGenericPassword(
       strlen(kAirPortServiceName), kAirPortServiceName, network_guid.length(),
-      network_guid.c_str(), &password_length, &password_data, NULL);
+      network_guid.c_str(), &password_length, &password_data, /*item=*/nullptr);
   if (status != errSecSuccess) {
     *error = kErrorNotFound;
     return;
@@ -381,7 +385,7 @@
 
   // Remove previous OS notifications observer.
   if (wlan_observer_) {
-    [[NSNotificationCenter defaultCenter] removeObserver:wlan_observer_];
+    [NSNotificationCenter.defaultCenter removeObserver:wlan_observer_];
     wlan_observer_ = nil;
   }
 
@@ -411,8 +415,10 @@
     // This is not a supported way to do this. The correct way to do this is the
     // -[CWWiFiClient startMonitoringEventWithType:error:] API:
     // https://developer.apple.com/documentation/corewlan/cwwificlient/1512439-startmonitoringeventwithtype?language=objc
-    // TODO(avi): Use this API. https://crbug.com/1054063
-    wlan_observer_ = [[NSNotificationCenter defaultCenter]
+    //
+    // TODO(https://crbug.com/1054063): Switch to using the
+    // -[CWWiFiClient startMonitoringEventWithType:error:] API.
+    wlan_observer_ = [NSNotificationCenter.defaultCenter
         addObserverForName:@"com.apple.coreWLAN.notification.ssid.legacy"
                     object:nil
                      queue:nil
@@ -475,20 +481,21 @@
 
     if (network_properties_map.find(network_guid) ==
             network_properties_map.end()) {
-      networks_.push_back(NetworkProperties());
+      networks_.emplace_back();
       network_properties_map[network_guid] = &networks_.back();
       update_all_properties = true;
     }
     // If current network is connected, use its properties for this network.
-    if (base::SysNSStringToUTF8([cw_network bssid]) == connected_bssid)
+    if (base::SysNSStringToUTF8(cw_network.bssid) == connected_bssid) {
       update_all_properties = true;
+    }
 
     NetworkProperties* properties = network_properties_map.at(network_guid);
     if (update_all_properties) {
       NetworkPropertiesFromCWNetwork(cw_network, properties);
     } else {
-      properties->frequency_set.insert(FrequencyFromCWChannelBand(
-          [[cw_network wlanChannel] channelBand]));
+      properties->frequency_set.insert(
+          FrequencyFromCWChannelBand(cw_network.wlanChannel.channelBand));
     }
   }
   // Sort networks, so connected/connecting is up front.
@@ -511,21 +518,21 @@
 void WiFiServiceMac::NetworkPropertiesFromCWNetwork(
     const CWNetwork* network,
     NetworkProperties* properties) const {
-  std::string network_guid = GUIDFromSSID([network ssid]);
+  std::string network_guid = GUIDFromSSID(network.ssid);
 
   properties->connection_state = GetNetworkConnectionState(network_guid);
-  properties->ssid = base::SysNSStringToUTF8([network ssid]);
+  properties->ssid = base::SysNSStringToUTF8(network.ssid);
   properties->name = properties->ssid;
   properties->guid = network_guid;
   properties->type = onc::network_type::kWiFi;
 
-  properties->bssid = base::SysNSStringToUTF8([network bssid]);
+  properties->bssid = base::SysNSStringToUTF8(network.bssid);
   properties->frequency = FrequencyFromCWChannelBand(
-      static_cast<CWChannelBand>([[network wlanChannel] channelBand]));
+      static_cast<CWChannelBand>(network.wlanChannel.channelBand));
   properties->frequency_set.insert(properties->frequency);
 
   properties->security = SecurityFromCWNetwork(network);
-  properties->signal_strength = [network rssiValue];
+  properties->signal_strength = network.rssiValue;
 }
 
 std::string WiFiServiceMac::SecurityFromCWNetwork(
@@ -602,10 +609,8 @@
     return;
 
   NetworkGuidList current_networks;
-  for (NetworkList::const_iterator it = networks.begin();
-       it != networks.end();
-       ++it) {
-    current_networks.push_back(it->guid);
+  for (const auto& network : networks) {
+    current_networks.push_back(network.guid);
   }
 
   event_task_runner_->PostTask(
diff --git a/content/browser/host_zoom_map_impl.cc b/content/browser/host_zoom_map_impl.cc
index 996c257b..5b57718 100644
--- a/content/browser/host_zoom_map_impl.cc
+++ b/content/browser/host_zoom_map_impl.cc
@@ -72,30 +72,30 @@
   }
 }
 
+// static
 HostZoomMap* HostZoomMap::GetDefaultForBrowserContext(BrowserContext* context) {
-  DCHECK_CURRENTLY_ON(BrowserThread::UI);
-  StoragePartition* partition = context->GetDefaultStoragePartition();
-  DCHECK(partition);
-  return partition->GetHostZoomMap();
+  return GetForStoragePartition(context->GetDefaultStoragePartition());
 }
 
+// static
 HostZoomMap* HostZoomMap::Get(SiteInstance* instance) {
-  DCHECK_CURRENTLY_ON(BrowserThread::UI);
-  StoragePartition* partition =
-      instance->GetBrowserContext()->GetStoragePartition(instance);
-  DCHECK(partition);
-  return partition->GetHostZoomMap();
+  return GetForStoragePartition(
+      instance->GetBrowserContext()->GetStoragePartition(instance));
 }
 
+// static
 HostZoomMap* HostZoomMap::GetForWebContents(WebContents* contents) {
-  DCHECK_CURRENTLY_ON(BrowserThread::UI);
   // TODO(wjmaclean): Update this behaviour to work with OOPIF.
   // See crbug.com/528407.
-  StoragePartition* partition =
-      contents->GetBrowserContext()->GetStoragePartition(
-          contents->GetSiteInstance());
-  DCHECK(partition);
-  return partition->GetHostZoomMap();
+  return Get(contents->GetSiteInstance());
+}
+
+// static
+HostZoomMap* HostZoomMap::GetForStoragePartition(
+    StoragePartition* storage_partition) {
+  DCHECK_CURRENTLY_ON(BrowserThread::UI);
+  CHECK(storage_partition);
+  return storage_partition->GetHostZoomMap();
 }
 
 // Helper function for setting/getting zoom levels for WebContents without
diff --git a/content/browser/media/media_license_manager.cc b/content/browser/media/media_license_manager.cc
index a73b1c0..c7bd14ce 100644
--- a/content/browser/media/media_license_manager.cc
+++ b/content/browser/media/media_license_manager.cc
@@ -15,6 +15,7 @@
 #include "base/functional/callback_forward.h"
 #include "base/memory/scoped_refptr.h"
 #include "base/memory/weak_ptr.h"
+#include "base/metrics/histogram_functions.h"
 #include "base/sequence_checker.h"
 #include "base/task/sequenced_task_runner.h"
 #include "base/task/task_traits.h"
@@ -26,6 +27,7 @@
 #include "content/browser/media/media_license_storage_host.h"
 #include "media/cdm/cdm_type.h"
 #include "sql/database.h"
+#include "sql/sqlite_result_code.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
 #include "third_party/blink/public/common/storage_key/storage_key.h"
 #include "third_party/blink/public/mojom/quota/quota_types.mojom-shared.h"
@@ -145,6 +147,12 @@
     // We could consider falling back to using an in-memory database in this
     // case, but failing here seems easier to reason about from a website
     // author's point of view.
+    sql::UmaHistogramSqliteResult(
+        "Media.EME.MediaLicenseDatabaseOpenSQLiteError",
+        result.error().sqlite_error);
+    base::UmaHistogramEnumeration(
+        "Media.EME.MediaLicenseDatabaseOpenQuotaError",
+        result.error().quota_error);
     MediaLicenseStorageHost::ReportDatabaseOpenError(
         MediaLicenseStorageHostOpenError::kBucketLocatorError, in_memory());
     DCHECK(bucket_locator.id.is_null());
diff --git a/content/browser/media/webaudio/audio_context_manager_browsertest.cc b/content/browser/media/webaudio/audio_context_manager_browsertest.cc
index 094b6b8..a833d61 100644
--- a/content/browser/media/webaudio/audio_context_manager_browsertest.cc
+++ b/content/browser/media/webaudio/audio_context_manager_browsertest.cc
@@ -61,23 +61,19 @@
     Waiter waiter(shell()->web_contents());
     testing::NiceMock<content::MockWebContentsObserver> mock_observer(
         shell()->web_contents());
+    EXPECT_CALL(mock_observer, AudioContextPlaybackStarted(testing::_))
+        .Times(1);
+    EXPECT_CALL(mock_observer, AudioContextPlaybackStopped(testing::_))
+        .Times(1);
 
     // Set gain to 1 to start audible audio and verify we got the
     // playback started message.
     ASSERT_TRUE(ExecJs(shell()->web_contents(), "gain.gain.value = 1;"));
-    EXPECT_CALL(mock_observer, AudioContextPlaybackStarted(testing::_))
-        .Times(1);
-    EXPECT_CALL(mock_observer, AudioContextPlaybackStopped(testing::_))
-        .Times(0);
     waiter.Wait();
 
     // Set gain to 0 to stop audible audio and verify we got the
     // playback stopped message.
     ASSERT_TRUE(ExecJs(shell()->web_contents(), "gain.gain.value = 0;"));
-    EXPECT_CALL(mock_observer, AudioContextPlaybackStarted(testing::_))
-        .Times(0);
-    EXPECT_CALL(mock_observer, AudioContextPlaybackStopped(testing::_))
-        .Times(1);
     waiter.Wait();
   }
 };
diff --git a/content/browser/renderer_host/render_frame_host_manager_browsertest.cc b/content/browser/renderer_host/render_frame_host_manager_browsertest.cc
index a2ff802..3295c21 100644
--- a/content/browser/renderer_host/render_frame_host_manager_browsertest.cc
+++ b/content/browser/renderer_host/render_frame_host_manager_browsertest.cc
@@ -1630,9 +1630,9 @@
   // The attacker page requests a navigation to a new document while the
   // browser-initiated navigation hasn't committed yet.
   TestNavigationManager attack_navigation(shell()->web_contents(), kAttackURL);
-  EXPECT_TRUE(ExecuteScriptWithoutUserGesture(
-      shell()->web_contents(),
-      "location.href = \"" + kAttackURL.spec() + "\";"));
+  EXPECT_TRUE(ExecJs(shell()->web_contents(),
+                     "location.href = \"" + kAttackURL.spec() + "\";",
+                     EXECUTE_SCRIPT_NO_USER_GESTURE));
   EXPECT_TRUE(attack_navigation.WaitForRequestStart());
 
   // This deletes the speculative RenderFrameHost that was supposed to commit
diff --git a/content/browser/renderer_host/render_widget_host_impl.cc b/content/browser/renderer_host/render_widget_host_impl.cc
index e45129c0..3dda7dc 100644
--- a/content/browser/renderer_host/render_widget_host_impl.cc
+++ b/content/browser/renderer_host/render_widget_host_impl.cc
@@ -3656,19 +3656,6 @@
     base::TimeTicks activation_time) {
   const auto& metadata =
       render_frame_metadata_provider_.LastRenderFrameMetadata();
-  // We only want to report the timings for the very First Surface that is
-  // activated.
-  if (!first_surface_activated_) {
-    first_surface_activated_ = true;
-    UMA_HISTOGRAM_TIMES(
-        "Compositing.Renderer.FirstSurfaceActivationUpdateDuration."
-        "PreviousSurfaces",
-        metadata.previous_surfaces_visual_update_duration);
-    UMA_HISTOGRAM_TIMES(
-        "Compositing.Renderer.FirstSurfaceActivationUpdateDuration."
-        "CurrentSurface",
-        metadata.current_surface_visual_update_duration);
-  }
 
   bool is_mobile_optimized = metadata.is_mobile_optimized;
   input_router_->NotifySiteIsMobileOptimized(is_mobile_optimized);
diff --git a/content/browser/renderer_host/render_widget_host_impl.h b/content/browser/renderer_host/render_widget_host_impl.h
index 308f116..fd91b2f2 100644
--- a/content/browser/renderer_host/render_widget_host_impl.h
+++ b/content/browser/renderer_host/render_widget_host_impl.h
@@ -1497,11 +1497,6 @@
   absl::optional<BrowserUIThreadScheduler::UserInputActiveHandle>
       user_input_active_handle_;
 
-  // Use for metrics reporting. Used to check if
-  // OnRenderFrameMetadataChangedAfterActivation is being called for the first
-  // time.
-  bool first_surface_activated_ = false;
-
   base::WeakPtrFactory<RenderWidgetHostImpl> weak_factory_{this};
 };
 
diff --git a/content/browser/service_worker/service_worker_registration_object_host.cc b/content/browser/service_worker/service_worker_registration_object_host.cc
index 1ba792f..c3d58e31 100644
--- a/content/browser/service_worker/service_worker_registration_object_host.cc
+++ b/content/browser/service_worker/service_worker_registration_object_host.cc
@@ -478,6 +478,7 @@
   std::vector<GURL> urls = {container_host_->url(), registration_->scope()};
   if (!service_worker_security_utils::AllOriginsMatchAndCanAccessServiceWorkers(
           urls)) {
+    base::debug::Alias(&urls);
     receivers_.ReportBadMessage(
         ServiceWorkerConsts::kBadMessageImproperOrigins);
     return false;
diff --git a/content/public/browser/host_zoom_map.h b/content/public/browser/host_zoom_map.h
index e65be8f..7ae4517e 100644
--- a/content/public/browser/host_zoom_map.h
+++ b/content/public/browser/host_zoom_map.h
@@ -25,6 +25,7 @@
 class NavigationEntry;
 class BrowserContext;
 class SiteInstance;
+class StoragePartition;
 class WebContents;
 struct GlobalRenderFrameHostId;
 
@@ -78,6 +79,10 @@
   // HostZoomMap.
   CONTENT_EXPORT static HostZoomMap* GetForWebContents(WebContents* contents);
 
+  // Returns the HostZoomMap associated with this StoragePartition.
+  CONTENT_EXPORT static HostZoomMap* GetForStoragePartition(
+      StoragePartition* storage_partition);
+
   // Returns the current zoom level for the specified WebContents. May be
   // temporary or host-specific.
   CONTENT_EXPORT static double GetZoomLevel(WebContents* web_contents);
diff --git a/content/public/test/browser_test_utils.cc b/content/public/test/browser_test_utils.cc
index 388021e6..1b7ab2c2 100644
--- a/content/public/test/browser_test_utils.cc
+++ b/content/public/test/browser_test_utils.cc
@@ -737,7 +737,7 @@
       "var iframes = document.getElementById('%s');iframes.src='%s';"
       "\",0)",
       iframe_id.c_str(), url.spec().c_str());
-  return ExecuteScriptWithoutUserGesture(web_contents, script);
+  return ExecJs(web_contents, script, EXECUTE_SCRIPT_NO_USER_GESTURE);
 }
 
 void NavigateToURLBlockUntilNavigationsComplete(
@@ -1444,12 +1444,6 @@
                                              script, user_gesture);
 }
 
-bool ExecuteScriptWithoutUserGesture(const ToRenderFrameHost& adapter,
-                                     const std::string& script) {
-  return ExecuteScriptWithUserGestureControl(adapter.render_frame_host(),
-                                             script, false);
-}
-
 void ExecuteScriptAsync(const ToRenderFrameHost& adapter,
                         const std::string& script) {
   // Prerendering pages will never have user gesture.
diff --git a/content/public/test/browser_test_utils.h b/content/public/test/browser_test_utils.h
index 83b1410..6ccb29b 100644
--- a/content/public/test/browser_test_utils.h
+++ b/content/public/test/browser_test_utils.h
@@ -570,12 +570,6 @@
 [[nodiscard]] bool ExecuteScript(const ToRenderFrameHost& adapter,
                                  const std::string& script);
 
-// Same as content::ExecuteScript but doesn't send a user gesture to the
-// renderer.
-[[nodiscard]] bool ExecuteScriptWithoutUserGesture(
-    const ToRenderFrameHost& adapter,
-    const std::string& script);
-
 // Similar to ExecuteScript above, but
 // - Doesn't modify the |script|.
 // - Kicks off execution of the |script| in the specified frame and returns
diff --git a/content/renderer/render_thread_impl.cc b/content/renderer/render_thread_impl.cc
index 5c0ff18c..484bed7 100644
--- a/content/renderer/render_thread_impl.cc
+++ b/content/renderer/render_thread_impl.cc
@@ -1162,6 +1162,10 @@
               ->GetGraphicsResetStatusKHR() == GL_NO_ERROR)
     return shared_main_thread_contexts_;
 
+  if (is_context_result_fatal_) {
+    return nullptr;
+  }
+
   scoped_refptr<gpu::GpuChannelHost> gpu_channel_host(
       EstablishGpuChannelSync());
   if (!gpu_channel_host) {
@@ -1180,6 +1184,7 @@
   // Enable automatic flushes to improve canvas throughput.
   // See https://crbug.com/880901
   bool automatic_flushes = true;
+
   // We use kGpuStreamIdDefault here, the same as in
   // PepperVideoDecodeContextProvider, so we don't need to handle
   // synchronization between the pepper context and the shared main thread
@@ -1192,8 +1197,11 @@
       viz::command_buffer_metrics::ContextType::RENDERER_MAIN_THREAD,
       kGpuStreamIdDefault, kGpuStreamPriorityDefault);
   auto result = shared_main_thread_contexts_->BindToCurrentSequence();
-  if (result != gpu::ContextResult::kSuccess)
+  if (result != gpu::ContextResult::kSuccess) {
     shared_main_thread_contexts_ = nullptr;
+    is_context_result_fatal_ = result == gpu::ContextResult::kFatalFailure;
+  }
+
   return shared_main_thread_contexts_;
 }
 
diff --git a/content/renderer/render_thread_impl.h b/content/renderer/render_thread_impl.h
index a5f8db97..1715904 100644
--- a/content/renderer/render_thread_impl.h
+++ b/content/renderer/render_thread_impl.h
@@ -588,6 +588,8 @@
 
   int32_t client_id_;
 
+  bool is_context_result_fatal_ = false;
+
   // A mojo connection to the CompositingModeReporter service.
   mojo::Remote<viz::mojom::CompositingModeReporter> compositing_mode_reporter_;
   // The class is a CompositingModeWatcher, which is bound to mojo through
diff --git a/content/test/data/accessibility/aria/toggle-button-expand-collapse-expected-uia-win.txt b/content/test/data/accessibility/aria/toggle-button-expand-collapse-expected-uia-win.txt
index 7ee38d2..e0ca4c51 100644
--- a/content/test/data/accessibility/aria/toggle-button-expand-collapse-expected-uia-win.txt
+++ b/content/test/data/accessibility/aria/toggle-button-expand-collapse-expected-uia-win.txt
@@ -1,5 +1,5 @@
 Document
-++Button Name='Toggle button, pressed, collapsed' ExpandCollapse.ExpandCollapseState='Collapsed' Toggle.ToggleState='On'
-++Button Name='Toggle button, not pressed, collapsed' ExpandCollapse.ExpandCollapseState='Collapsed' Toggle.ToggleState='Off'
-++Button Name='Toggle button, pressed, expanded' ExpandCollapse.ExpandCollapseState='Expanded' Toggle.ToggleState='On'
-++Button Name='Toggle button, not pressed, collapsed"' ExpandCollapse.ExpandCollapseState='Expanded' Toggle.ToggleState='Off'
+++Button Name='Toggle button, pressed, collapsed' ExpandCollapse.ExpandCollapseState='Collapsed'
+++Button Name='Toggle button, not pressed, collapsed' ExpandCollapse.ExpandCollapseState='Collapsed'
+++Button Name='Toggle button, pressed, expanded' ExpandCollapse.ExpandCollapseState='Expanded'
+++Button Name='Toggle button, not pressed, collapsed"' ExpandCollapse.ExpandCollapseState='Expanded'
diff --git a/content/test/gpu/gpu_tests/skia_gold/gpu_skia_gold_properties.py b/content/test/gpu/gpu_tests/skia_gold/gpu_skia_gold_properties.py
deleted file mode 100644
index 2947eab..0000000
--- a/content/test/gpu/gpu_tests/skia_gold/gpu_skia_gold_properties.py
+++ /dev/null
@@ -1,28 +0,0 @@
-# Copyright 2020 The Chromium Authors
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-"""GPU implementation of //testing/skia_gold_common/skia_gold_properties.py."""
-
-import subprocess
-import sys
-from typing import Optional
-
-import gpu_path_util
-
-from skia_gold_common import skia_gold_properties
-
-
-class GpuSkiaGoldProperties(skia_gold_properties.SkiaGoldProperties):
-  @staticmethod
-  def _GetGitOriginMainHeadSha1() -> Optional[str]:
-    try:
-      return subprocess.check_output(
-          ['git', 'rev-parse', 'origin/main'],
-          shell=_IsWin(),
-          cwd=gpu_path_util.CHROMIUM_SRC_DIR).decode('utf-8').strip()
-    except subprocess.CalledProcessError:
-      return None
-
-
-def _IsWin() -> bool:
-  return sys.platform == 'win32'
diff --git a/content/test/gpu/gpu_tests/skia_gold_integration_test_base.py b/content/test/gpu/gpu_tests/skia_gold_integration_test_base.py
index 8b21c755..2567ec47 100644
--- a/content/test/gpu/gpu_tests/skia_gold_integration_test_base.py
+++ b/content/test/gpu/gpu_tests/skia_gold_integration_test_base.py
@@ -18,9 +18,9 @@
 from gpu_tests import gpu_helper
 from gpu_tests import gpu_integration_test
 from gpu_tests import pixel_test_pages
-from gpu_tests.skia_gold import gpu_skia_gold_properties as sgp
 from gpu_tests.skia_gold import gpu_skia_gold_session_manager as sgsm
 
+from skia_gold_common import skia_gold_properties as sgp
 from skia_gold_common import skia_gold_session as sgs
 
 import gpu_path_util
@@ -76,9 +76,9 @@
     cls._skia_gold_temp_dir = tempfile.mkdtemp()
 
   @classmethod
-  def GetSkiaGoldProperties(cls) -> sgp.GpuSkiaGoldProperties:
+  def GetSkiaGoldProperties(cls) -> sgp.SkiaGoldProperties:
     if not cls._skia_gold_properties:
-      cls._skia_gold_properties = sgp.GpuSkiaGoldProperties(
+      cls._skia_gold_properties = sgp.SkiaGoldProperties(
           cls.GetOriginalFinderOptions())
     return cls._skia_gold_properties
 
diff --git a/content/test/gpu/gpu_tests/test_expectations/webgl2_conformance_expectations.txt b/content/test/gpu/gpu_tests/test_expectations/webgl2_conformance_expectations.txt
index bb93e81..1cbb0a8 100644
--- a/content/test/gpu/gpu_tests/test_expectations/webgl2_conformance_expectations.txt
+++ b/content/test/gpu/gpu_tests/test_expectations/webgl2_conformance_expectations.txt
@@ -330,7 +330,7 @@
 ## Metal Common ##
 # Common to all GPU types (both AMD and Intel, at least)
 
-crbug.com/angleproject/7079 [ angle-metal mac-x86_64 monterey passthrough ] deqp/functional/gles3/fboinvalidate/sub.html [ Failure ]
+crbug.com/angleproject/7079 [ angle-metal mac mac-x86_64 passthrough ] deqp/functional/gles3/fboinvalidate/sub.html [ Failure ]
 crbug.com/angleproject/7079 [ angle-metal mac mac-x86_64 passthrough ] deqp/functional/gles3/fboinvalidate/whole.html [ Failure ]
 crbug.com/angleproject/7079 [ angle-metal mac-x86_64 monterey passthrough ] deqp/functional/gles3/fbomultisample.2_samples.html [ Failure ]
 crbug.com/angleproject/7079 [ angle-metal mac-x86_64 monterey passthrough ] deqp/functional/gles3/fbomultisample.4_samples.html [ Failure ]
@@ -760,9 +760,10 @@
 crbug.com/1219024 [ chromeos chromeos-board-kevin no-passthrough ] conformance2/rendering/framebuffer-render-to-layer.html [ Failure ]
 crbug.com/1212917 [ chromeos chromeos-board-kevin no-passthrough ] conformance/context/context-creation-and-destruction.html [ Failure ]
 
-
+crbug.com/1441195 [ chromeos chromeos-board-kevin no-passthrough ] conformance/canvas/framebuffer-bindings-unaffected-on-resize.html [ RetryOnFailure ]
 crbug.com/1441195 [ chromeos chromeos-board-kevin no-passthrough ] conformance2/extensions/oes-draw-buffers-indexed.html [ Failure ]
 crbug.com/1441195 [ chromeos chromeos-board-kevin no-passthrough ] conformance2/rendering/framebuffer-mismatched-attachment-targets.html [ Failure ]
+crbug.com/1441195 [ chromeos chromeos-board-kevin no-passthrough ] conformance2/textures/canvas/tex-2d-srgb8_alpha8-rgba-unsigned_byte.html [ RetryOnFailure ]
 
 crbug.com/1232106 [ chromeos chromeos-board-amd64-generic no-passthrough ] conformance/extensions/webgl-compressed-texture-astc.html [ Failure ]
 crbug.com/1232118 [ angle-opengles chromeos chromeos-board-amd64-generic passthrough ] conformance/uniforms/uniform-samplers-test.html [ Failure ]
diff --git a/content/test/gpu/gpu_tests/test_expectations/webgl_conformance_expectations.txt b/content/test/gpu/gpu_tests/test_expectations/webgl_conformance_expectations.txt
index ff9041e7..0a9df70 100644
--- a/content/test/gpu/gpu_tests/test_expectations/webgl_conformance_expectations.txt
+++ b/content/test/gpu/gpu_tests/test_expectations/webgl_conformance_expectations.txt
@@ -614,8 +614,6 @@
 # Failures on validating command decoder only; won't fix.
 crbug.com/angleproject/5038 [ chromeos chromeos-board-amd64-generic mesa_ge_21.0 no-passthrough target-cpu-64 ] conformance/extensions/ext-color-buffer-half-float.html [ Failure ]
 
-
-
 crbug.com/1080400 [ chromeos chromeos-board-kevin ] WebglExtension_EXT_color_buffer_half_float [ Failure ]
 crbug.com/1080356 [ chromeos chromeos-board-kevin ] WebglExtension_EXT_frag_depth [ Failure ]
 crbug.com/1080375 [ chromeos chromeos-board-kevin ] WebglExtension_OES_texture_float [ Failure ]
@@ -629,6 +627,8 @@
 crbug.com/1108368 [ chromeos chromeos-board-kevin ] conformance/misc/shader-precision-format.html [ Failure ]
 crbug.com/1357064 [ chromeos chromeos-board-kevin no-passthrough ] conformance/rendering/blending.html [ Failure ]
 
+crbug.com/1441195 [ chromeos chromeos-board-kevin no-passthrough ] conformance/context/context-creation-and-destruction.html [ RetryOnFailure ]
+
 ## VM Issues ##
 
 # Failing on chromeos-amd64-generic-rel.
diff --git a/docs/linux/build_instructions.md b/docs/linux/build_instructions.md
index 20cf803..ff04be2 100644
--- a/docs/linux/build_instructions.md
+++ b/docs/linux/build_instructions.md
@@ -261,14 +261,8 @@
 
 The Chrome binary contains embedded symbols by default. You can reduce its size
 by using the Linux `strip` command to remove this debug information. You can
-also reduce binary size by disabling debug mode, disabling dchecks, and turning
-on all optimizations by enabling official build mode, with these GN args:
-
-```
-is_debug = false
-dcheck_always_on = false
-is_official_build = true
-```
+also reduce binary size and turn on all optimizations by enabling official build
+mode, with the GN arg `is_official_build = true`.
 
 ## Build Chromium
 
diff --git a/extensions/common/api/_api_features.json b/extensions/common/api/_api_features.json
index c40e2c0..87c16fa 100644
--- a/extensions/common/api/_api_features.json
+++ b/extensions/common/api/_api_features.json
@@ -108,7 +108,7 @@
   "bluetoothLowEnergy": {
     "dependencies": ["manifest:bluetooth"],
     "contexts": ["blessed_extension"],
-    "platforms": ["chromeos", "linux"]
+    "platforms": ["chromeos", "lacros", "linux"]
   },
   "bluetoothPrivate": [{
     "dependencies": ["permission:bluetoothPrivate"],
@@ -215,7 +215,7 @@
   },
   "feedbackPrivate.readLogSource": [
     {
-      "platforms": ["chromeos"],
+      "platforms": ["chromeos", "lacros"],
       "session_types": ["kiosk"]
     },
     {
diff --git a/extensions/common/api/_permission_features.json b/extensions/common/api/_permission_features.json
index 3506601..a7ffe1a 100644
--- a/extensions/common/api/_permission_features.json
+++ b/extensions/common/api/_permission_features.json
@@ -138,7 +138,7 @@
     {
       "channel": "stable",
       "extension_types": ["platform_app"],
-      "platforms": ["chromeos", "win", "mac", "linux"],
+      "platforms": ["chromeos", "lacros", "win", "mac", "linux"],
       "allowlist": [
         "1C93BD3CF875F4A73C0B2A163BB8FBDA8B8B3D80",  // http://crbug.com/387169
         "A3BC37E2148AC4E99BE4B16AF9D42DD1E592BBBE",  // http://crbug.com/387169
diff --git a/fuchsia_web/runners/cast/cast_resolver.cc b/fuchsia_web/runners/cast/cast_resolver.cc
index ceceac4..2cf50072 100644
--- a/fuchsia_web/runners/cast/cast_resolver.cc
+++ b/fuchsia_web/runners/cast/cast_resolver.cc
@@ -11,12 +11,15 @@
 
 #include <stdint.h>
 
+#include <string>
 #include <utility>
 #include <vector>
 
+#include "base/files/file_util.h"
 #include "base/fuchsia/fuchsia_logging.h"
 #include "base/notreached.h"
 #include "base/strings/string_piece.h"
+#include "base/sys_byteorder.h"
 
 namespace {
 
@@ -43,6 +46,24 @@
   }}));
 }
 
+uint64_t FetchAbiRevision() {
+  constexpr char kPkgAbiRevisionPath[] = "/pkg/meta/fuchsia.abi/abi-revision";
+
+  // Read the Little Endian representation of the unsigned 64-bit integer ABI
+  // revision from the file in the metadata directory.
+  uint64_t abi_revision_le = 0u;
+  int read_bytes = base::ReadFile(base::FilePath(kPkgAbiRevisionPath),
+                                  reinterpret_cast<char*>(&abi_revision_le),
+                                  sizeof(abi_revision_le));
+  CHECK_EQ(read_bytes, static_cast<int>(sizeof(abi_revision_le)));
+
+  // Swap the byte-order of `abi_revision_le` from little-endian to host-endian
+  // by using `ByteSwapToLE64()`. If the host is little-endian then this is a
+  // no-op, otherwise the byte order will be swapped, resulting in the correct
+  // big-endian/host-endian.
+  return base::ByteSwapToLE64(abi_revision_le);
+}
+
 }  // namespace
 
 CastResolver::CastResolver() = default;
@@ -99,11 +120,13 @@
   }
 
   // Encode the component manifest into the resolver result.
+  static const uint64_t abi_revision = FetchAbiRevision();
   fuchsia_component_resolution::ResolverResolveResponse result{{
       .component = fuchsia_component_resolution::Component{{
           .url = std::move(request.component_url()),
           .decl =
               fuchsia_mem::Data::WithBytes(std::move(persisted_decl.value())),
+          .abi_revision = abi_revision,
       }},
   }};
 
diff --git a/gpu/config/software_rendering_list.json b/gpu/config/software_rendering_list.json
index 18bceef0..82d2485 100644
--- a/gpu/config/software_rendering_list.json
+++ b/gpu/config/software_rendering_list.json
@@ -1628,7 +1628,7 @@
         "type": "chromeos"
       },
       "vendor_id": "0x8086",
-      "device_id": ["0x0a16", "0x0a1e", "0x0d26"],
+      "device_id": ["0x0a16", "0x0a1e", "0x0d26", "0x0a06"],
       "features": [
         "accelerated_2d_canvas"
       ]
diff --git a/ios/chrome/browser/browsing_data/cache_counter.cc b/ios/chrome/browser/browsing_data/cache_counter.cc
index aa1c23f..4c5432c 100644
--- a/ios/chrome/browser/browsing_data/cache_counter.cc
+++ b/ios/chrome/browser/browsing_data/cache_counter.cc
@@ -122,6 +122,9 @@
 }
 
 void CacheCounter::Count() {
+  // Cancel existing requests.
+  weak_ptr_factory_.InvalidateWeakPtrs();
+
   // disk_cache::Backend currently does not implement counting for subsets of
   // cache, only for the entire cache. Thus, ignore the time period setting and
   // always request counting for the unbounded time interval. It is up to the
diff --git a/ios/chrome/browser/ui/ntp/BUILD.gn b/ios/chrome/browser/ui/ntp/BUILD.gn
index 43702c2..788968d 100644
--- a/ios/chrome/browser/ui/ntp/BUILD.gn
+++ b/ios/chrome/browser/ui/ntp/BUILD.gn
@@ -203,7 +203,7 @@
     ":logo",
     ":ntp",
     "resources:fake_omnibox_background_color",
-    "resources:ntp_background_light_mode_only_color",
+    "resources:ntp_background_color",
     "//base",
     "//components/signin/public/base",
     "//components/signin/public/identity_manager/objc",
diff --git a/ios/chrome/browser/ui/ntp/new_tab_page_header_view.mm b/ios/chrome/browser/ui/ntp/new_tab_page_header_view.mm
index f5d274f..77c2f594 100644
--- a/ios/chrome/browser/ui/ntp/new_tab_page_header_view.mm
+++ b/ios/chrome/browser/ui/ntp/new_tab_page_header_view.mm
@@ -389,15 +389,6 @@
     percent = std::clamp<CGFloat>(
         animatingOffset / ntp_header::kAnimationDistance, 0, 1);
   }
-  if (IsMagicStackEnabled()) {
-    // Update background color of fake toolbar if stuck to top of NTP so that it
-    // has a non-clear background that matches the NTP background. Otherwise,
-    // return to clear background.
-    self.fakeToolbar.backgroundColor =
-        percent == 1.0f
-            ? [UIColor colorNamed:@"ntp_background_light_mode_only_color"]
-            : [UIColor clearColor];
-  }
   return percent;
 }
 
@@ -418,6 +409,14 @@
 
   CGFloat percent = [self searchFieldProgressForOffset:offset
                                         safeAreaInsets:safeAreaInsets];
+  if (IsMagicStackEnabled()) {
+    // Update background color of fake toolbar if stuck to top of NTP so that it
+    // has a non-clear background that matches the NTP background. Otherwise,
+    // return to clear background.
+    self.fakeToolbar.backgroundColor =
+        percent == 1.0f ? [UIColor colorNamed:@"ntp_background_color"]
+                        : [UIColor clearColor];
+  }
 
   // Offset the hint label constraints with half of the change in width
   // from the original scale, since constraints are calculated before
diff --git a/ios/chrome/browser/ui/ntp/new_tab_page_view_controller.mm b/ios/chrome/browser/ui/ntp/new_tab_page_view_controller.mm
index 579a20c..a3e4838 100644
--- a/ios/chrome/browser/ui/ntp/new_tab_page_view_controller.mm
+++ b/ios/chrome/browser/ui/ntp/new_tab_page_view_controller.mm
@@ -199,8 +199,7 @@
     AddSameConstraints(_backgroundGradientView, self.view);
     [self updateModularHomeBackgroundColorForUserInterfaceStyle:
               self.traitCollection.userInterfaceStyle];
-    self.view.backgroundColor =
-        [UIColor colorNamed:@"ntp_background_light_mode_only_color"];
+    self.view.backgroundColor = [UIColor colorNamed:@"ntp_background_color"];
   } else {
     self.view.backgroundColor = ntp_home::NTPBackgroundColor();
   }
@@ -1393,6 +1392,8 @@
   return adjustedOffset;
 }
 
+// Background gradient view will be used when in dark mode, the assigned
+// background color to this view's otherwise.
 - (void)updateModularHomeBackgroundColorForUserInterfaceStyle:
     (UIUserInterfaceStyle)style {
   _backgroundGradientView.hidden = style == UIUserInterfaceStyleLight;
diff --git a/ios/chrome/browser/ui/ntp/resources/BUILD.gn b/ios/chrome/browser/ui/ntp/resources/BUILD.gn
index dbad1d9..3c44874 100644
--- a/ios/chrome/browser/ui/ntp/resources/BUILD.gn
+++ b/ios/chrome/browser/ui/ntp/resources/BUILD.gn
@@ -16,8 +16,8 @@
   sources = [ "fake_omnibox_top_gradient_color.colorset/Contents.json" ]
 }
 
-colorset("ntp_background_light_mode_only_color") {
-  sources = [ "ntp_background_light_mode_only_color.colorset/Contents.json" ]
+colorset("ntp_background_color") {
+  sources = [ "ntp_background_color.colorset/Contents.json" ]
 }
 
 imageset("ntp_opentabs_last_row_h") {
diff --git a/ios/chrome/browser/ui/ntp/resources/ntp_background_color.colorset/Contents.json b/ios/chrome/browser/ui/ntp/resources/ntp_background_color.colorset/Contents.json
new file mode 100644
index 0000000..b6b45052
--- /dev/null
+++ b/ios/chrome/browser/ui/ntp/resources/ntp_background_color.colorset/Contents.json
@@ -0,0 +1,38 @@
+{
+  "info" : {
+    "version" : 1,
+    "author" : "xcode"
+  },
+  "colors" : [
+    {
+      "idiom" : "universal",
+      "color" : {
+        "color-space" : "display-p3",
+        "components" : {
+          "alpha" : "1.000",
+          "red" : "0xED",
+          "green" : "0xF4",
+          "blue" : "0xFE"
+        }
+      }
+    },
+    {
+      "idiom": "universal",
+      "appearances": [
+        {
+          "appearance": "luminosity",
+          "value": "dark"
+        }
+      ],
+      "color": {
+        "color-space": "display-p3",
+        "components": {
+          "alpha": "1.000",
+          "red": "0x35",
+          "green": "0x37",
+          "blue": "0x39"
+        }
+      }
+    }
+  ]
+}
diff --git a/ios/chrome/browser/ui/ntp/resources/ntp_background_light_mode_only_color.colorset/Contents.json b/ios/chrome/browser/ui/ntp/resources/ntp_background_light_mode_only_color.colorset/Contents.json
deleted file mode 100644
index 36b3ce4..0000000
--- a/ios/chrome/browser/ui/ntp/resources/ntp_background_light_mode_only_color.colorset/Contents.json
+++ /dev/null
@@ -1,20 +0,0 @@
-{
-  "info" : {
-    "version" : 1,
-    "author" : "xcode"
-  },
-  "colors" : [
-    {
-      "idiom" : "universal",
-      "color" : {
-        "color-space" : "display-p3",
-        "components" : {
-          "alpha" : "1.000",
-          "red" : "0xED",
-          "green" : "0xF4",
-          "blue" : "0xFE"
-        }
-      }
-    }
-  ]
-}
diff --git a/ios/chrome/browser/ui/settings/password/password_manager_view_controller_unittest.mm b/ios/chrome/browser/ui/settings/password/password_manager_view_controller_unittest.mm
index af4705e4..888f0ed 100644
--- a/ios/chrome/browser/ui/settings/password/password_manager_view_controller_unittest.mm
+++ b/ios/chrome/browser/ui/settings/password/password_manager_view_controller_unittest.mm
@@ -88,7 +88,8 @@
 class BasePasswordManagerViewControllerTest
     : public ChromeTableViewControllerTest {
  protected:
-  BasePasswordManagerViewControllerTest() = default;
+  BasePasswordManagerViewControllerTest()
+      : enable_grouping_(password_manager::features::kPasswordsGrouping) {}
 
   void SetUp() override {
     ChromeTableViewControllerTest::SetUp();
@@ -324,6 +325,7 @@
 
   void RunUntilIdle() { task_environment_.RunUntilIdle(); }
 
+  base::test::ScopedFeatureList enable_grouping_;
   web::WebTaskEnvironment task_environment_;
   std::unique_ptr<TestChromeBrowserState> browser_state_;
   std::unique_ptr<TestBrowser> browser_;
@@ -400,16 +402,15 @@
   AddSavedForm2();
 
   CheckURLCellTitleAndDetailText(
-      @"example2.com", @"test@egmail.com",
-      GetSectionIndex(SectionIdentifierSavedPasswords), 0);
+      @"example2.com", @"", GetSectionIndex(SectionIdentifierSavedPasswords),
+      0);
 
   AddSavedForm1();
   CheckURLCellTitleAndDetailText(
-      @"example.com", @"test@egmail.com",
-      GetSectionIndex(SectionIdentifierSavedPasswords), 0);
+      @"example.com", @"", GetSectionIndex(SectionIdentifierSavedPasswords), 0);
   CheckURLCellTitleAndDetailText(
-      @"example2.com", @"test@egmail.com",
-      GetSectionIndex(SectionIdentifierSavedPasswords), 1);
+      @"example2.com", @"", GetSectionIndex(SectionIdentifierSavedPasswords),
+      1);
   [GetPasswordManagerViewController() settingsWillBeDismissed];
 }
 
@@ -451,68 +452,10 @@
   [GetPasswordManagerViewController() settingsWillBeDismissed];
 }
 
-// Tests deleting items from saved passwords and blocked passwords sections.
-TEST_F(PasswordManagerViewControllerTest, DeleteItems) {
-  AddSavedForm1();
-  AddBlockedForm1();
-  AddBlockedForm2();
-  ASSERT_EQ(4, NumberOfSections());
-
-  // Delete item in save passwords section.
-  deleteItemAndWait(GetSectionIndex(SectionIdentifierSavedPasswords), 0);
-  EXPECT_EQ(3, NumberOfSections());
-
-  // The blocked passwords section should still have both its items.
-  EXPECT_EQ(2,
-            NumberOfItemsInSection(GetSectionIndex(SectionIdentifierBlocked)));
-
-  // Delete item in blocked passwords section.
-  deleteItemAndWait(GetSectionIndex(SectionIdentifierBlocked), 0);
-  EXPECT_EQ(1,
-            NumberOfItemsInSection(GetSectionIndex(SectionIdentifierBlocked)));
-
-  // There should be no password sections remaining and no search bar.
-  deleteItemAndWait(GetSectionIndex(SectionIdentifierBlocked), 0);
-  EXPECT_EQ(0, NumberOfSections());  // Empty state
-  [GetPasswordManagerViewController() settingsWillBeDismissed];
-}
-
-// Tests deleting items from saved passwords and blocked passwords sections
-// when there are duplicates in the store.
-TEST_F(PasswordManagerViewControllerTest, DeleteItemsWithDuplicates) {
-  AddSavedForm1();
-  AddSavedForm1();
-  AddBlockedForm1();
-  AddBlockedForm1();
-  AddBlockedForm2();
-  ASSERT_EQ(4, NumberOfSections());
-
-  // Delete item in save passwords section.
-  deleteItemAndWait(GetSectionIndex(SectionIdentifierSavedPasswords), 0);
-  EXPECT_EQ(3, NumberOfSections());
-
-  // The blocked passwords section should still have both its items.
-  EXPECT_EQ(2,
-            NumberOfItemsInSection(GetSectionIndex(SectionIdentifierBlocked)));
-
-  // Delete item in blocked passwords section.
-  deleteItemAndWait(GetSectionIndex(SectionIdentifierBlocked), 0);
-  EXPECT_EQ(1,
-            NumberOfItemsInSection(GetSectionIndex(SectionIdentifierBlocked)));
-
-  // There should be no password sections remaining and no search bar.
-  deleteItemAndWait(GetSectionIndex(SectionIdentifierBlocked), 0);
-  EXPECT_EQ(0, NumberOfSections());  // Empty state
-  [GetPasswordManagerViewController() settingsWillBeDismissed];
-}
-
 // Tests deleting items from saved passwords (as affiliated groups) and blocked
 // passwords sections.
 TEST_F(PasswordManagerViewControllerTest,
        DeleteAffiliatedGroupsAndBlockedPasswords) {
-  base::test::ScopedFeatureList scoped_feature_list(
-      password_manager::features::kPasswordsGrouping);
-
   AddSavedForm1();
   AddSavedForm1(u"test2@egmail.com");
   AddSavedForm2();
@@ -733,21 +676,7 @@
   EXPECT_EQ(0,
             NumberOfItemsInSection(GetSectionIndex(SectionIdentifierBlocked)));
   CheckURLCellTitleAndDetailText(
-      @"example.com", @"test@egmail.com",
-      GetSectionIndex(SectionIdentifierSavedPasswords), 0);
-
-  [passwords_controller searchBar:bar textDidChange:@"test@egmail.com"];
-  // Only two items in saved passwords should remain.
-  EXPECT_EQ(2, NumberOfItemsInSection(
-                   GetSectionIndex(SectionIdentifierSavedPasswords)));
-  EXPECT_EQ(0,
-            NumberOfItemsInSection(GetSectionIndex(SectionIdentifierBlocked)));
-  CheckURLCellTitleAndDetailText(
-      @"example.com", @"test@egmail.com",
-      GetSectionIndex(SectionIdentifierSavedPasswords), 0);
-  CheckURLCellTitleAndDetailText(
-      @"example2.com", @"test@egmail.com",
-      GetSectionIndex(SectionIdentifierSavedPasswords), 1);
+      @"example.com", @"", GetSectionIndex(SectionIdentifierSavedPasswords), 0);
 
   [passwords_controller searchBar:bar textDidChange:@"secret"];
   // Only two blocked items should remain.
diff --git a/net/http/transport_security_state_static.json b/net/http/transport_security_state_static.json
index 32c2916..3d45352 100644
--- a/net/http/transport_security_state_static.json
+++ b/net/http/transport_security_state_static.json
@@ -118236,6 +118236,229 @@
     { "name": "whitefordtownshipmi.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
     { "name": "woodstocknh.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
     { "name": "wvpebd.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "adriantwpmi.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "alamancecountync.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "alberttwpmi.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "alexandrianh.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "almacenterwi.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "androscoggincountyema.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "arlingtonmn.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "ashlandcountywi.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "assyriatwpmi.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "athensvillageny.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "attawauganfirect.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "baileysharborwi.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "baltimorecitybnmd.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "bannockcounty.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "bataviail.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "belvideretownshipmi.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "benningtonne.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "bnwrdil.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "boazwi.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "boltonct.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "boswellboropd-pa.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "brackettvilletx.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "brattleboro.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "bridgeportne.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "brileytownshipmi.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "bristowpoliceok.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "bucklinmo.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "bvrpd-pa.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "californiapa.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "callawaymn.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "camptonnh.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "canyontx.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "carrollcountynh.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "cassia.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "cecilsheriffmd.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "cecilstatesattorneymd.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "chaskafire.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "chaskapolice.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "cityofaltonil.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "cityofbristowok.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "cityofeatonrapids.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "cityofgrandblancmi.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "cityofgrossepointemi.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "cityofirvineca.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "cityofkewauneewi.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "cityoftulsa.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "clearfieldpa.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "coldcaserecords.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "coldspringsrancheria.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "coloradochildrep.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "cookcountysheriffil.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "cooperativecogohio.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "countyofmonterey.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "cvwrfut.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "dawsoncountyne.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "dcsoky.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "decherdtn.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "diamondbluffwi.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "drdca.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "durandwi.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "eastfordct.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "edennc.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "egvilparks.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "emmauspa.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "escanabatownshipmi.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "escondido.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "evelethpd.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "evergreenlangladewi.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "ewgri.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "fairviewheightsil.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "fennville.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "fenwickisland-de.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "festusmo.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "findsupport.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "fishersin.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "flightrights.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "floydcova.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "gmta.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "goodmanwi.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "goosecreeksc.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "halifaxboropdpa.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "haringtwpmi.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "harnettcountync.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "harrisoncountymo.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "hawaiipolice.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "haydenal.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "hennikernh.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "henrico.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "hohny.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "horiconwi.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "imperialne.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "invest.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "investinamerica.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "investinginamerica.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "jeffersoncountyor.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "jeromecountyid.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "ketchikan.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "kimberlyal.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "laconiapdnh.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "lakecountytn.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "langleyok.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "laonawi.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "lawrencecountyky.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "leefl.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "lincolncountyid.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "logismn.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "londonderrynhfire.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "loudontn911.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "madisoncountyky.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "magneticspringsoh.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "marioncountysheriffar.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "marylandcomptroller.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "mayfieldheightsohio.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "mckeancountypa.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "mclennan.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "miamicountyohioauditor.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "mifflincountypa.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "millercountymo.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "missoulacountymt.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "monroeoregon.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "mooremi.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "mowercountymn.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "mymdtaxes.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "myokaloosa.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "nassaucountyfl.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "nebraskajudicial.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "nelsontwpoh.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "newarkwi.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "northplattene.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "norvelltwp-mi.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "notify.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "oceancountynj.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "oldtowntownship-il.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "olmstedtownshipohio.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "omngc.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "oneaftac.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "orem.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "orleansiowa.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "oronomn.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "ottawatribe.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "owensvilleoh.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "palmyramo.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "pbgfl.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "phoenix-correspondence-commission.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "piute.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "piutesd.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "plandomemanorny.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "police4tn.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "powercounty.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "putnamcountywv.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "ransonwv.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "rathdrum.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "rathdrumpolice.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "redgranitefdwi.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "richlandwa.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "roaringforkfire.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "rocktonil.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "rockymountva.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "rooseveltcountymt.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "rushcountykansas.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "sanbenitocounty-ca-cre.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "santaclaratx.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "saratogasprings-ut.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "satellitebeach.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "scappoose.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "shermantwphuronmi.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "shoshonecityid.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "sibfl.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "sidneymi.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "siouxcountyne.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "sofdwi.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "southwebsterohio.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "southwickma.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "tatuhagu.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "thomascountyne.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "thurstoncountyne.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "tlingitandhaida.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "townofcanandaigua.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "townofclaytonny.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "townofdewhurstwi.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "townoffairfieldwi.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "townofgoreok.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "townofjohnstonsc.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "townofkewaskumwi.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "townoflakeviewor.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "townofleonmocowi.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "townofmosineewi.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "townofsandcreekwi.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "townofsasserga.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "townofsheboyganwi.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "townofuticawi.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "townofwittenbergwi.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "townofwyomingwi.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "trfmn.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "ulstersheriffny.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "urbanail.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "vail.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "vallejo.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "villageofavocawi.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "villageofcascowi.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "villageofhempsteadpdny.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "villageoflonerock-wi.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "villageofluxemburgwi.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "visforvaccinated.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "vvwraca.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "wakullavotes.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "walkercountytx.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "walnutgrovemo.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "wardsborovt.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "warrenct.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "wasatchcounty.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "waukee.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "waynewashcowi.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "wearenh.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "westernwaterca.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "wilkes-barretownship.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "windhamct.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "winonamn.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "woodburynj.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "woodlawnky.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "wvbold.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "wyomingcountyny.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
+    { "name": "yellcountyar.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true },
     // END OF ETLD-OWNER REQUESTED ENTRIES
 
     // To avoid trailing comma changes from showing up in diffs, we place a
diff --git a/remoting/base/BUILD.gn b/remoting/base/BUILD.gn
index b5bf0f1..c68999d 100644
--- a/remoting/base/BUILD.gn
+++ b/remoting/base/BUILD.gn
@@ -3,6 +3,7 @@
 # found in the LICENSE file.
 
 import("//build/config/chromeos/ui_mode.gni")
+import("//build/util/process_version.gni")
 import("//remoting/build/config/remoting_logging.gni")
 import("//testing/libfuzzer/fuzzer_test.gni")
 import("//third_party/protobuf/proto_library.gni")
@@ -11,6 +12,12 @@
   sources = [ "protobuf_http_client_messages.proto" ]
 }
 
+process_version("remoting_base_version") {
+  sources = [ "//chrome/VERSION" ]
+  template_file = "//remoting/base/version.h.in"
+  output = "$target_gen_dir/version.h"
+}
+
 source_set("base") {
   sources = [
     "auto_thread.cc",
@@ -162,7 +169,15 @@
   deps = [ "//base" ]
 
   if (is_linux || is_chromeos) {
-    sources += [ "breakpad_linux.cc" ]
+    sources += [
+      "breakpad_linux.cc",
+      "breakpad_utils_linux.cc",
+      "breakpad_utils_linux.h",
+    ]
+    deps += [
+      ":remoting_base_version",
+      "//third_party/breakpad:client",
+    ]
   }
 
   if (is_mac) {
diff --git a/remoting/base/breakpad_linux.cc b/remoting/base/breakpad_linux.cc
index 1ae50b6..6f97f45 100644
--- a/remoting/base/breakpad_linux.cc
+++ b/remoting/base/breakpad_linux.cc
@@ -4,11 +4,123 @@
 
 #include "remoting/base/breakpad.h"
 
+#include <unistd.h>
+
+#include <atomic>
+#include <memory>
+#include <string>
+
+#include "base/command_line.h"
+#include "base/files/file_util.h"
+#include "base/json/json_writer.h"
+#include "base/logging.h"
+#include "base/no_destructor.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/time/time.h"
+#include "base/values.h"
+#include "remoting/base/breakpad_utils_linux.h"
+#include "remoting/base/version.h"
+#include "third_party/breakpad/breakpad/src/client/linux/handler/exception_handler.h"
+
 namespace remoting {
 
+namespace {
+
+class BreakpadLinux {
+ public:
+  BreakpadLinux();
+
+  BreakpadLinux(const BreakpadLinux&) = delete;
+  BreakpadLinux& operator=(const BreakpadLinux&) = delete;
+
+  ~BreakpadLinux() = delete;
+
+  static BreakpadLinux& GetInstance();
+
+  std::atomic<bool>& handling_exception() { return handling_exception_; }
+  base::Time process_start_time() const { return process_start_time_; }
+  int process_id() const { return pid_; }
+  const std::string& program_name() const { return program_name_; }
+
+ private:
+  // Breakpad's exception handler.
+  std::unique_ptr<google_breakpad::ExceptionHandler> breakpad_;
+
+  // Indicates whether an exception is already being handled.
+  std::atomic<bool> handling_exception_{false};
+
+  base::Time process_start_time_ = base::Time::NowFromSystemTime();
+  pid_t pid_ = getpid();
+  std::string program_name_;
+};
+
+bool MinidumpCallback(const google_breakpad::MinidumpDescriptor& descriptor,
+                      void* context,
+                      bool succeeded) {
+  BreakpadLinux& self = BreakpadLinux::GetInstance();
+  if (self.handling_exception().exchange(true)) {
+    LOG(WARNING) << "Already processing another crash";
+    return false;
+  }
+
+  auto process_uptime =
+      base::Time::NowFromSystemTime() - self.process_start_time();
+  auto metadata =
+      base::Value::Dict()
+          .Set(kBreakpadProcessIdKey, self.process_id())
+          .Set(kBreakpadProcessNameKey, self.program_name())
+          .Set(kBreakpadProcessStartTimeKey,
+               base::NumberToString(self.process_start_time().ToTimeT()))
+          .Set(kBreakpadProcessUptimeKey,
+               base::NumberToString(process_uptime.InSeconds()))
+          .Set(kBreakpadHostVersionKey, REMOTING_VERSION_STRING);
+
+  auto metadata_file_contents = base::WriteJson(metadata);
+  if (!metadata_file_contents.has_value()) {
+    LOG(ERROR) << "Failed to convert metadata to JSON.";
+    return false;
+  }
+
+  ScopedAllowBlockingForCrashReporting scoped_allow_blocking;
+  base::FilePath metadata_file_path =
+      base::FilePath(descriptor.path()).ReplaceExtension("json");
+  if (!base::WriteFile(metadata_file_path, *metadata_file_contents)) {
+    LOG(ERROR) << "Failed to write crash dump metadata to file.";
+    return false;
+  }
+
+  return succeeded;
+}
+
+BreakpadLinux::BreakpadLinux() {
+  const base::CommandLine* cmd_line = base::CommandLine::ForCurrentProcess();
+  // This includes both executable name and the path, e.g.
+  // /opt/google/chrome-remote-desktop/chrome-remote-desktop-host
+  program_name_ = cmd_line->GetProgram().value();
+
+  if (!base::CreateDirectory(base::FilePath(kMinidumpPath))) {
+    LOG(ERROR) << "Failed to create minidump directory: " << kMinidumpPath;
+  }
+
+  google_breakpad::MinidumpDescriptor descriptor(kMinidumpPath);
+  breakpad_ = std::make_unique<google_breakpad::ExceptionHandler>(
+      descriptor, /*filter_callback=*/nullptr, MinidumpCallback,
+      /*callback_context=*/nullptr,
+      /*install_handler=*/true,
+      /*server_fd=*/-1);
+}
+
+// static
+BreakpadLinux& BreakpadLinux::GetInstance() {
+  static base::NoDestructor<BreakpadLinux> instance;
+  return *instance;
+}
+
+}  // namespace
+
 void InitializeCrashReporting() {
-  // TODO(alexeypa) Implement crash dump collection on Linux; see
-  // http://crbug.com/130678.
+  // Touch the object to make sure it is initialized.
+  BreakpadLinux::GetInstance();
 }
 
 }  // namespace remoting
diff --git a/remoting/base/breakpad_utils_linux.cc b/remoting/base/breakpad_utils_linux.cc
new file mode 100644
index 0000000..6862649
--- /dev/null
+++ b/remoting/base/breakpad_utils_linux.cc
@@ -0,0 +1,16 @@
+// 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 "remoting/base/breakpad_utils_linux.h"
+
+namespace remoting {
+
+const char kMinidumpPath[] = "/tmp/chromoting/minidumps";
+const char kBreakpadProcessIdKey[] = "process_id";
+const char kBreakpadProcessNameKey[] = "process_name";
+const char kBreakpadProcessStartTimeKey[] = "process_start_time";
+const char kBreakpadProcessUptimeKey[] = "process_uptime";
+const char kBreakpadHostVersionKey[] = "host_version";
+
+}  // namespace remoting
diff --git a/remoting/base/breakpad_utils_linux.h b/remoting/base/breakpad_utils_linux.h
new file mode 100644
index 0000000..608a3b4f
--- /dev/null
+++ b/remoting/base/breakpad_utils_linux.h
@@ -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.
+
+#ifndef REMOTING_BASE_BREAKPAD_UTILS_LINUX_H_
+#define REMOTING_BASE_BREAKPAD_UTILS_LINUX_H_
+
+#include "base/threading/thread_restrictions.h"
+
+namespace remoting {
+
+extern const char kMinidumpPath[];
+extern const char kBreakpadProcessIdKey[];
+extern const char kBreakpadProcessNameKey[];
+extern const char kBreakpadProcessStartTimeKey[];
+extern const char kBreakpadProcessUptimeKey[];
+extern const char kBreakpadHostVersionKey[];
+
+class ScopedAllowBlockingForCrashReporting : public base::ScopedAllowBlocking {
+};
+
+}  // namespace remoting
+
+#endif  // REMOTING_BASE_BREAKPAD_UTILS_LINUX_H_
diff --git a/remoting/base/version.h.in b/remoting/base/version.h.in
new file mode 100644
index 0000000..2f2de89
--- /dev/null
+++ b/remoting/base/version.h.in
@@ -0,0 +1,13 @@
+// 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.
+
+// version.h is generated from version.h.in.  Edit the source!
+
+#ifndef REMOTING_BASE_VERSION_H_
+#define REMOTING_BASE_VERSION_H_
+
+#define REMOTING_VERSION @MAJOR@,@MINOR@,@BUILD@,@PATCH@
+#define REMOTING_VERSION_STRING "@MAJOR@.@MINOR@.@BUILD@.@PATCH@"
+
+#endif  // REMOTING_BASE_VERSION_H_
diff --git a/services/resource_coordinator/public/cpp/memory_instrumentation/os_metrics_linux.cc b/services/resource_coordinator/public/cpp/memory_instrumentation/os_metrics_linux.cc
index 7ab571f..ba59484 100644
--- a/services/resource_coordinator/public/cpp/memory_instrumentation/os_metrics_linux.cc
+++ b/services/resource_coordinator/public/cpp/memory_instrumentation/os_metrics_linux.cc
@@ -95,7 +95,12 @@
     size_t build_id_length =
         base::debug::ReadElfBuildId(&__ehdr_start, true, build_id);
     if (build_id_length) {
-      module_data.path = dl_info.dli_fname;
+      base::FilePath module_data_path = base::FilePath(dl_info.dli_fname);
+      if (module_data_path.IsAbsolute()) {
+        module_data.path = dl_info.dli_fname;
+      } else {
+        module_data.path = base::MakeAbsoluteFilePath(module_data_path).value();
+      }
       module_data.build_id = std::string(build_id, build_id_length);
     }
   }
diff --git a/storage/browser/quota/client_usage_tracker.cc b/storage/browser/quota/client_usage_tracker.cc
index 2775adba..f4866fcd 100644
--- a/storage/browser/quota/client_usage_tracker.cc
+++ b/storage/browser/quota/client_usage_tracker.cc
@@ -129,26 +129,10 @@
   return usage;
 }
 
-std::map<std::string, int64_t> ClientUsageTracker::GetCachedHostsUsage() const {
+const std::map<BucketLocator, int64_t>&
+ClientUsageTracker::GetCachedBucketsUsage() const {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-  std::map<std::string, int64_t> host_usage;
-  for (const auto& bucket_and_usage : cached_bucket_usage_) {
-    const std::string& host =
-        bucket_and_usage.first.storage_key.origin().host();
-    host_usage[host] += bucket_and_usage.second;
-  }
-  return host_usage;
-}
-
-std::map<blink::StorageKey, int64_t>
-ClientUsageTracker::GetCachedStorageKeysUsage() const {
-  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-  std::map<blink::StorageKey, int64_t> storage_key_usage;
-  for (const auto& bucket_and_usage : cached_bucket_usage_) {
-    const blink::StorageKey& storage_key = bucket_and_usage.first.storage_key;
-    storage_key_usage[storage_key] += bucket_and_usage.second;
-  }
-  return storage_key_usage;
+  return cached_bucket_usage_;
 }
 
 void ClientUsageTracker::SetUsageCacheEnabled(
diff --git a/storage/browser/quota/client_usage_tracker.h b/storage/browser/quota/client_usage_tracker.h
index b360337..f2aae71 100644
--- a/storage/browser/quota/client_usage_tracker.h
+++ b/storage/browser/quota/client_usage_tracker.h
@@ -80,13 +80,10 @@
   // Accumulates all cached usage to determine storage pressure.
   int64_t GetCachedUsage() const;
 
-  // Returns cached usage organized by host. Expected to be called after
-  // GetGlobalUsage which retrieves and caches host usage.
-  std::map<std::string, int64_t> GetCachedHostsUsage() const;
-
-  // Returns cached usage organized by StorageKey. Used for histogram recording.
-  // TODO(ayui): Update to return bucket usage map.
-  std::map<blink::StorageKey, int64_t> GetCachedStorageKeysUsage() const;
+  // Returns cached usage organized by bucket. Used for histogram recording and
+  // eviction. Expected to be called after GetGlobalUsage which retrieves and
+  // caches usage.
+  const std::map<BucketLocator, int64_t>& GetCachedBucketsUsage() const;
 
   // Sets if a `storage_key` for `client_` should / should not be excluded from
   // quota restrictions.
diff --git a/storage/browser/quota/quota_database.cc b/storage/browser/quota/quota_database.cc
index 6d7346f7..449d32c 100644
--- a/storage/browser/quota/quota_database.cc
+++ b/storage/browser/quota/quota_database.cc
@@ -221,14 +221,12 @@
       GetBucket(params.storage_key, params.name, StorageType::kTemporary);
 
   if (!bucket_result.has_value()) {
-    if (bucket_result.error() == QuotaError::kNotFound) {
-      bucket_result = CreateBucketInternal(params, StorageType::kTemporary,
-                                           max_bucket_count);
-    }
-    if (!bucket_result.has_value()) {
+    if (bucket_result.error() != QuotaError::kNotFound) {
       bucket_result.error().sqlite_error = sqlite_error_code_;
+      return bucket_result;
     }
-    return bucket_result;
+    return CreateBucketInternal(params, StorageType::kTemporary,
+                                max_bucket_count);
   }
 
   // Don't bother updating anything if the bucket is expired.
@@ -624,8 +622,10 @@
   return BucketTableEntryFromSqlStatement(statement);
 }
 
-QuotaErrorOr<BucketLocator> QuotaDatabase::GetLruEvictableBucket(
+QuotaErrorOr<std::set<BucketLocator>> QuotaDatabase::GetBucketsForEviction(
     StorageType type,
+    int64_t target_usage,
+    const std::map<BucketLocator, int64_t>& usage_map,
     const std::set<BucketId>& bucket_exceptions,
     SpecialStoragePolicy* special_storage_policy) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
@@ -634,6 +634,8 @@
     return base::unexpected(open_error);
   }
 
+  std::set<BucketLocator> buckets_to_evict;
+
   // clang-format off
   static constexpr char kSql[] =
       "SELECT id, storage_key, name FROM buckets "
@@ -644,6 +646,9 @@
   sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql));
   statement.BindInt(0, static_cast<int>(type));
 
+  // The total space used by all buckets marked for eviction.
+  int64_t total_usage = 0;
+
   while (statement.Step()) {
     absl::optional<StorageKey> read_storage_key =
         StorageKey::Deserialize(statement.ColumnString(1));
@@ -664,10 +669,20 @@
          special_storage_policy->IsStorageUnlimited(read_gurl))) {
       continue;
     }
-    return BucketLocator(read_bucket_id, std::move(read_storage_key).value(),
-                         type, is_default);
+
+    BucketLocator locator(read_bucket_id, std::move(read_storage_key).value(),
+                          type, is_default);
+    const auto& bucket_usage = usage_map.find(locator);
+    total_usage += (bucket_usage == usage_map.end()) ? 1 : bucket_usage->second;
+    buckets_to_evict.insert(locator);
+    if (total_usage >= target_usage) {
+      break;
+    }
   }
-  return base::unexpected(QuotaError::kNotFound);
+  if (buckets_to_evict.empty()) {
+    return base::unexpected(QuotaError::kNotFound);
+  }
+  return buckets_to_evict;
 }
 
 QuotaErrorOr<std::set<StorageKey>> QuotaDatabase::GetStorageKeysForType(
diff --git a/storage/browser/quota/quota_database.h b/storage/browser/quota/quota_database.h
index 7b9952b1..5869857 100644
--- a/storage/browser/quota/quota_database.h
+++ b/storage/browser/quota/quota_database.h
@@ -176,12 +176,16 @@
   QuotaErrorOr<mojom::BucketTableEntryPtr> DeleteBucketData(
       const BucketLocator& bucket);
 
-  // Returns the BucketLocator for the least recently used bucket. Will exclude
-  // buckets with ids in `bucket_exceptions`, buckets marked persistent, and
-  // origins that have the special unlimited storage policy. Returns a
-  // QuotaError if the operation has failed.
-  QuotaErrorOr<BucketLocator> GetLruEvictableBucket(
+  // Returns BucketLocators for the least recently used buckets, to clear at
+  // least `target_usage` space. Will exclude buckets with ids in
+  // `bucket_exceptions`, buckets marked persistent, and origins that have the
+  // special unlimited storage policy. Returns a QuotaError if the operation has
+  // failed. `usage_map` describes the amount of space used by each bucket; any
+  // bucket missing from this map will be considered to use only 1b.
+  QuotaErrorOr<std::set<BucketLocator>> GetBucketsForEviction(
       blink::mojom::StorageType type,
+      int64_t target_usage,
+      const std::map<BucketLocator, int64_t>& usage_map,
       const std::set<BucketId>& bucket_exceptions,
       SpecialStoragePolicy* special_storage_policy);
 
diff --git a/storage/browser/quota/quota_database_unittest.cc b/storage/browser/quota/quota_database_unittest.cc
index 2c4ae28d..472fcd6f 100644
--- a/storage/browser/quota/quota_database_unittest.cc
+++ b/storage/browser/quota/quota_database_unittest.cc
@@ -517,9 +517,9 @@
   EXPECT_TRUE(EnsureOpened(db.get()));
 
   std::set<BucketId> bucket_exceptions;
-  QuotaErrorOr<BucketLocator> result =
-      db->GetLruEvictableBucket(kTemp, bucket_exceptions, nullptr);
-  ASSERT_FALSE(result.has_value());
+  QuotaErrorOr<std::set<BucketLocator>> result =
+      db->GetBucketsForEviction(kTemp, 1, {}, bucket_exceptions, nullptr);
+  EXPECT_FALSE(result.has_value());
   EXPECT_EQ(result.error(), QuotaError::kNotFound);
 
   // Insert bucket entries into BucketTable.
@@ -576,38 +576,45 @@
       db->SetBucketLastAccessTime(BucketId(777), base::Time::FromJavaTime(40)),
       QuotaError::kNone);
 
-  result = db->GetLruEvictableBucket(kTemp, bucket_exceptions, nullptr);
+  result = db->GetBucketsForEviction(kTemp, 1, {}, bucket_exceptions, nullptr);
   ASSERT_TRUE(result.has_value());
-  EXPECT_EQ(bucket_id1, result.value().id);
+  ASSERT_EQ(1U, result->size());
+  EXPECT_EQ(bucket_id1, result->begin()->id);
 
   // Test that unlimited origins are excluded from eviction, but
   // protected origins are not excluded.
   auto policy = base::MakeRefCounted<MockSpecialStoragePolicy>();
   policy->AddUnlimited(storage_key1.origin().GetURL());
   policy->AddProtected(storage_key2.origin().GetURL());
-  result = db->GetLruEvictableBucket(kTemp, bucket_exceptions, policy.get());
+  result =
+      db->GetBucketsForEviction(kTemp, 1, {}, bucket_exceptions, policy.get());
   ASSERT_TRUE(result.has_value());
-  EXPECT_EQ(bucket_id2, result.value().id);
+  ASSERT_EQ(1U, result->size());
+  EXPECT_EQ(bucket_id2, result->begin()->id);
 
   // Test that durable origins are excluded from eviction.
   policy->AddDurable(storage_key2.origin().GetURL());
-  result = db->GetLruEvictableBucket(kTemp, bucket_exceptions, policy.get());
+  result =
+      db->GetBucketsForEviction(kTemp, 1, {}, bucket_exceptions, policy.get());
   ASSERT_TRUE(result.has_value());
-  EXPECT_EQ(bucket_id3, result.value().id);
+  ASSERT_EQ(1U, result->size());
+  EXPECT_EQ(bucket_id3, result->begin()->id);
 
   // Bucket exceptions exclude specified buckets.
   bucket_exceptions.insert(bucket_id1);
-  result = db->GetLruEvictableBucket(kTemp, bucket_exceptions, nullptr);
+  result = db->GetBucketsForEviction(kTemp, 1, {}, bucket_exceptions, nullptr);
   ASSERT_TRUE(result.has_value());
-  EXPECT_EQ(bucket_id2, result.value().id);
+  ASSERT_EQ(1U, result->size());
+  EXPECT_EQ(bucket_id2, result->begin()->id);
 
   bucket_exceptions.insert(bucket_id2);
-  result = db->GetLruEvictableBucket(kTemp, bucket_exceptions, nullptr);
+  result = db->GetBucketsForEviction(kTemp, 1, {}, bucket_exceptions, nullptr);
   ASSERT_TRUE(result.has_value());
-  EXPECT_EQ(bucket_id3, result.value().id);
+  ASSERT_EQ(1U, result->size());
+  EXPECT_EQ(bucket_id3, result->begin()->id);
 
   bucket_exceptions.insert(bucket_id3);
-  result = db->GetLruEvictableBucket(kTemp, bucket_exceptions, nullptr);
+  result = db->GetBucketsForEviction(kTemp, 1, {}, bucket_exceptions, nullptr);
   ASSERT_FALSE(result.has_value());
   EXPECT_EQ(result.error(), QuotaError::kNotFound);
 
@@ -626,14 +633,16 @@
 
   // Querying again to see if the deletion has worked.
   bucket_exceptions.clear();
-  result = db->GetLruEvictableBucket(kTemp, bucket_exceptions, nullptr);
+  result = db->GetBucketsForEviction(kTemp, 1, {}, bucket_exceptions, nullptr);
   ASSERT_TRUE(result.has_value());
-  EXPECT_EQ(bucket_id2, result.value().id);
+  ASSERT_EQ(1U, result->size());
+  EXPECT_EQ(bucket_id2, result->begin()->id);
 
   bucket_exceptions.insert(bucket_id1);
   bucket_exceptions.insert(bucket_id2);
-  result = db->GetLruEvictableBucket(kTemp, bucket_exceptions, nullptr);
-  ASSERT_FALSE(result.has_value());
+  result = db->GetBucketsForEviction(kTemp, 1, {}, bucket_exceptions, nullptr);
+  EXPECT_FALSE(result.has_value());
+
   EXPECT_EQ(result.error(), QuotaError::kNotFound);
 }
 
@@ -642,9 +651,9 @@
   EXPECT_TRUE(EnsureOpened(db.get()));
 
   std::set<BucketId> bucket_exceptions;
-  QuotaErrorOr<BucketLocator> result =
-      db->GetLruEvictableBucket(kTemp, bucket_exceptions, nullptr);
-  ASSERT_FALSE(result.has_value());
+  QuotaErrorOr<std::set<BucketLocator>> result =
+      db->GetBucketsForEviction(kTemp, 1, {}, bucket_exceptions, nullptr);
+  EXPECT_FALSE(result.has_value());
   EXPECT_EQ(result.error(), QuotaError::kNotFound);
 
   // Insert bucket entries into BucketTable.
@@ -675,14 +684,16 @@
       db->SetBucketLastAccessTime(bucket_id2, base::Time::FromJavaTime(20)),
       QuotaError::kNone);
 
-  result = db->GetLruEvictableBucket(kTemp, bucket_exceptions, nullptr);
-  ASSERT_TRUE(result.has_value());
-  EXPECT_EQ(bucket_id1, result.value().id);
+  result = db->GetBucketsForEviction(kTemp, 1, {}, bucket_exceptions, nullptr);
+  EXPECT_TRUE(result.has_value());
+  ASSERT_EQ(1U, result->size());
+  EXPECT_EQ(bucket_id1, result->begin()->id);
 
   ASSERT_TRUE(db->UpdateBucketPersistence(bucket_id1, true).has_value());
-  result = db->GetLruEvictableBucket(kTemp, bucket_exceptions, nullptr);
-  ASSERT_TRUE(result.has_value());
-  EXPECT_EQ(bucket_id2, result.value().id);
+  result = db->GetBucketsForEviction(kTemp, 1, {}, bucket_exceptions, nullptr);
+  EXPECT_TRUE(result.has_value());
+  ASSERT_EQ(1U, result->size());
+  EXPECT_EQ(bucket_id2, result->begin()->id);
 }
 
 TEST_P(QuotaDatabaseTest, SetStorageKeyLastAccessTime) {
@@ -1213,17 +1224,19 @@
 
   // Get evictable bucket --- should be the default one.
   auto policy = base::MakeRefCounted<MockSpecialStoragePolicy>();
-  QuotaErrorOr<BucketLocator> lru_result =
-      db.GetLruEvictableBucket(kTemp, {}, policy.get());
+  QuotaErrorOr<std::set<BucketLocator>> lru_result =
+      db.GetBucketsForEviction(kTemp, 1, {}, {}, policy.get());
   ASSERT_TRUE(lru_result.has_value());
-  EXPECT_EQ(default_id, lru_result.value().id);
+  ASSERT_EQ(1U, lru_result->size());
+  EXPECT_EQ(default_id, lru_result->begin()->id);
 
   // Check that durable policy applies to the default bucket but not the non
   // default (non default buckets use the persist columnn in the database).
   policy->AddDurable(storage_key.origin().GetURL());
-  lru_result = db.GetLruEvictableBucket(kTemp, {}, policy.get());
+  lru_result = db.GetBucketsForEviction(kTemp, 1, {}, {}, policy.get());
   ASSERT_TRUE(lru_result.has_value());
-  EXPECT_EQ(non_default_id, lru_result.value().id);
+  ASSERT_EQ(1U, lru_result->size());
+  EXPECT_EQ(non_default_id, lru_result->begin()->id);
 }
 
 INSTANTIATE_TEST_SUITE_P(All,
diff --git a/storage/browser/quota/quota_features.cc b/storage/browser/quota/quota_features.cc
index 3e17261..310b11d0 100644
--- a/storage/browser/quota/quota_features.cc
+++ b/storage/browser/quota/quota_features.cc
@@ -13,6 +13,12 @@
 constexpr int64_t kMBytes = 1024 * 1024;
 }  // namespace
 
+// A kill switch for the new approach to storage eviction on low disk space. See
+// crbug.com/1382847
+BASE_FEATURE(kNewQuotaEvictionRoutine,
+             "NewQuotaEvictionRoutine",
+             base::FEATURE_ENABLED_BY_DEFAULT);
+
 // Enables Storage Pressure Event.
 BASE_FEATURE(kStoragePressureEvent,
              "StoragePressureEvent",
diff --git a/storage/browser/quota/quota_features.h b/storage/browser/quota/quota_features.h
index 322e02b..5f005fe 100644
--- a/storage/browser/quota/quota_features.h
+++ b/storage/browser/quota/quota_features.h
@@ -13,6 +13,9 @@
 
 namespace features {
 
+COMPONENT_EXPORT(STORAGE_BROWSER)
+BASE_DECLARE_FEATURE(kNewQuotaEvictionRoutine);
+
 COMPONENT_EXPORT(STORAGE_BROWSER) BASE_DECLARE_FEATURE(kStoragePressureEvent);
 
 COMPONENT_EXPORT(STORAGE_BROWSER) BASE_DECLARE_FEATURE(kStorageQuotaSettings);
diff --git a/storage/browser/quota/quota_manager_impl.cc b/storage/browser/quota/quota_manager_impl.cc
index 3f4d49b..d5160a9c 100644
--- a/storage/browser/quota/quota_manager_impl.cc
+++ b/storage/browser/quota/quota_manager_impl.cc
@@ -1789,9 +1789,6 @@
   }
 }
 
-QuotaManagerImpl::EvictionContext::EvictionContext() = default;
-QuotaManagerImpl::EvictionContext::~EvictionContext() = default;
-
 void QuotaManagerImpl::EnsureDatabaseOpened() {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   DCHECK(io_thread_->BelongsToCurrentThread());
@@ -2163,6 +2160,8 @@
 }
 
 void QuotaManagerImpl::DidEvictBucketData(
+    BucketId evicted_bucket_id,
+    base::RepeatingCallback<void(bool)> barrier,
     QuotaErrorOr<mojom::BucketTableEntryPtr> entry) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   DCHECK(io_thread_->BelongsToCurrentThread());
@@ -2176,16 +2175,14 @@
     base::UmaHistogramCounts1000(
         QuotaManagerImpl::kEvictedBucketDaysSinceAccessHistogram,
         (now - entry.value()->last_accessed).InDays());
-    std::move(eviction_context_.evict_bucket_data_callback)
-        .Run(QuotaError::kNone);
+    barrier.Run(true);
   } else {
     // We only try to evict buckets that are not in use, so basically deletion
     // attempt for eviction should not fail.  Let's record the bucket if we get
     // an error and exclude it from future eviction if the error happens
     // consistently (> kThresholdOfErrorsToBeDenylisted).
-    buckets_in_error_[eviction_context_.evicted_bucket.id]++;
-    std::move(eviction_context_.evict_bucket_data_callback)
-        .Run(entry.error().quota_error);
+    buckets_in_error_[evicted_bucket_id]++;
+    barrier.Run(false);
   }
 }
 
@@ -2493,33 +2490,33 @@
   return exceptions;
 }
 
-void QuotaManagerImpl::DidGetEvictionBucket(
-    GetBucketCallback callback,
-    const absl::optional<BucketLocator>& bucket) {
+void QuotaManagerImpl::DidGetEvictionBuckets(
+    GetBucketsCallback callback,
+    const std::set<BucketLocator>& buckets) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   DCHECK(callback);
 
-  // Make sure the returned bucket has not been accessed since we posted the
-  // eviction task.
-  DCHECK(!bucket.has_value() ||
-         !bucket->storage_key.origin().GetURL().is_empty());
-  if (bucket.has_value() &&
-      std::count_if(access_notified_buckets_.begin(),
-                    access_notified_buckets_.end(),
-                    [&bucket](const BucketLocator& locator) {
-                      return bucket->IsEquivalentTo(locator);
-                    })) {
-    std::move(callback).Run(absl::nullopt);
-  } else {
-    std::move(callback).Run(bucket);
-  }
-  access_notified_buckets_.clear();
+  // Filter out buckets that were accessed while getting eviction buckets.
+  auto bucket_wasnt_accessed =
+      [this](const BucketLocator& to_be_evicted_bucket) {
+        return !std::count_if(
+            access_notified_buckets_.begin(), access_notified_buckets_.end(),
+            [&to_be_evicted_bucket](const BucketLocator& accessed_bucket) {
+              return to_be_evicted_bucket.IsEquivalentTo(accessed_bucket);
+            });
+      };
 
+  std::set<BucketLocator> bucket_copies;
+  std::copy_if(buckets.begin(), buckets.end(),
+               std::inserter(bucket_copies, bucket_copies.end()),
+               bucket_wasnt_accessed);
+  std::move(callback).Run(bucket_copies);
+  access_notified_buckets_.clear();
   is_getting_eviction_bucket_ = false;
 }
 
-void QuotaManagerImpl::GetEvictionBucket(StorageType type,
-                                         GetBucketCallback callback) {
+void QuotaManagerImpl::GetEvictionBuckets(int64_t target_usage,
+                                          GetBucketsCallback callback) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   DCHECK(callback);
   EnsureDatabaseOpened();
@@ -2528,9 +2525,18 @@
   DCHECK(!is_getting_eviction_bucket_);
   is_getting_eviction_bucket_ = true;
 
-  GetLruEvictableBucket(
-      type, base::BindOnce(&QuotaManagerImpl::DidGetEvictionBucket,
-                           weak_factory_.GetWeakPtr(), std::move(callback)));
+  // The usage map should have been cached recently due to
+  // `GetEvictionRoundInfo()`.
+  std::map<BucketLocator, int64_t> usage_map;
+  if (base::FeatureList::IsEnabled(features::kNewQuotaEvictionRoutine)) {
+    usage_map =
+        GetUsageTracker(StorageType::kTemporary)->GetCachedBucketsUsage();
+  }
+
+  GetBucketsForEvictionFromDatabase(
+      target_usage, std::move(usage_map),
+      base::BindOnce(&QuotaManagerImpl::DidGetEvictionBuckets,
+                     weak_factory_.GetWeakPtr(), std::move(callback)));
 }
 
 void QuotaManagerImpl::EvictExpiredBuckets(StatusCallback done) {
@@ -2558,19 +2564,27 @@
 }
 
 void QuotaManagerImpl::EvictBucketData(
-    const BucketLocator& bucket,
-    base::OnceCallback<void(QuotaError)> callback) {
+    const std::set<BucketLocator>& buckets,
+    base::OnceCallback<void(int)> on_eviction_done) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   DCHECK(io_thread_->BelongsToCurrentThread());
-  DCHECK_EQ(bucket.type, StorageType::kTemporary);
-  DCHECK(callback);
 
-  eviction_context_.evicted_bucket = bucket;
-  eviction_context_.evict_bucket_data_callback = std::move(callback);
+  auto barrier = base::BarrierCallback<bool>(
+      buckets.size(), base::BindOnce(
+                          [](base::OnceCallback<void(int)> on_eviction_done,
+                             std::vector<bool> results) {
+                            const int evicted_count = std::count(
+                                results.begin(), results.end(), true);
+                            std::move(on_eviction_done).Run(evicted_count);
+                          },
+                          std::move(on_eviction_done)));
 
-  DeleteBucketDataInternal(bucket, AllQuotaClientTypes(),
-                           base::BindOnce(&QuotaManagerImpl::DidEvictBucketData,
-                                          weak_factory_.GetWeakPtr()));
+  for (const auto& bucket : buckets) {
+    DeleteBucketDataInternal(
+        bucket, AllQuotaClientTypes(),
+        base::BindOnce(&QuotaManagerImpl::DidEvictBucketData,
+                       weak_factory_.GetWeakPtr(), bucket.id, barrier));
+  }
 }
 
 void QuotaManagerImpl::GetEvictionRoundInfo(
@@ -2593,43 +2607,47 @@
   eviction_helper_.reset();
 }
 
-void QuotaManagerImpl::GetLruEvictableBucket(StorageType type,
-                                             GetBucketCallback callback) {
+void QuotaManagerImpl::GetBucketsForEvictionFromDatabase(
+    int64_t target_usage,
+    std::map<BucketLocator, int64_t> usage_map,
+    GetBucketsCallback callback) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   DCHECK(callback);
   EnsureDatabaseOpened();
 
-  // This must not be called while there's an in-flight task.
-  DCHECK(lru_bucket_callback_.is_null());
-  lru_bucket_callback_ = std::move(callback);
   if (db_disabled_) {
-    std::move(lru_bucket_callback_).Run(absl::nullopt);
+    std::move(callback).Run({});
     return;
   }
 
   PostTaskAndReplyWithResultForDBThread(
       base::BindOnce(
-          [](StorageType type, const std::set<BucketId>& bucket_exceptions,
+          [](int64_t target_usage, std::map<BucketLocator, int64_t> usage_map,
+             const std::set<BucketId>& bucket_exceptions,
              SpecialStoragePolicy* policy, QuotaDatabase* database) {
             DCHECK(database);
-            return database->GetLruEvictableBucket(type, bucket_exceptions,
-                                                   policy);
+            return database->GetBucketsForEviction(
+                blink::mojom::StorageType::kTemporary, target_usage, usage_map,
+                bucket_exceptions, policy);
           },
-          type, GetEvictionBucketExceptions(),
+          target_usage, std::move(usage_map), GetEvictionBucketExceptions(),
           base::RetainedRef(special_storage_policy_)),
-      base::BindOnce(&QuotaManagerImpl::DidGetLruEvictableBucket,
-                     weak_factory_.GetWeakPtr()));
+      base::BindOnce(&QuotaManagerImpl::DidGetBucketsForEvictionFromDatabase,
+                     weak_factory_.GetWeakPtr(), std::move(callback)));
 }
 
-void QuotaManagerImpl::DidGetLruEvictableBucket(
-    QuotaErrorOr<BucketLocator> result) {
+void QuotaManagerImpl::DidGetBucketsForEvictionFromDatabase(
+    GetBucketsCallback callback,
+    QuotaErrorOr<std::set<BucketLocator>> result) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   DidDatabaseWork(result.has_value() ||
                   result.error() != QuotaError::kDatabaseError);
 
-  std::move(lru_bucket_callback_)
-      .Run(result.has_value() ? absl::make_optional(std::move(result.value()))
-                              : absl::nullopt);
+  if (result.has_value()) {
+    std::move(callback).Run(result.value());
+  } else {
+    std::move(callback).Run({});
+  }
 }
 
 void QuotaManagerImpl::GetQuotaSettings(QuotaSettingsCallback callback) {
diff --git a/storage/browser/quota/quota_manager_impl.h b/storage/browser/quota/quota_manager_impl.h
index 7686559c..d65af768e 100644
--- a/storage/browser/quota/quota_manager_impl.h
+++ b/storage/browser/quota/quota_manager_impl.h
@@ -84,13 +84,13 @@
 
   // Returns the next bucket to evict, or nullopt if there are no evictable
   // buckets.
-  virtual void GetEvictionBucket(blink::mojom::StorageType type,
-                                 GetBucketCallback callback) = 0;
+  virtual void GetEvictionBuckets(int64_t target_usage,
+                                  GetBucketsCallback callback) = 0;
 
-  // Called to evict a bucket.
-  virtual void EvictBucketData(
-      const BucketLocator& bucket,
-      base::OnceCallback<void(QuotaError)> callback) = 0;
+  // Called to evict a set of buckets. The callback will be run with the number
+  // of successfully evicted buckets.
+  virtual void EvictBucketData(const std::set<BucketLocator>& buckets,
+                               base::OnceCallback<void(int)> callback) = 0;
 
  protected:
   virtual ~QuotaEvictionHandler() = default;
@@ -395,10 +395,10 @@
 
   // QuotaEvictionHandler.
   void EvictExpiredBuckets(StatusCallback done) override;
-  void GetEvictionBucket(blink::mojom::StorageType type,
-                         GetBucketCallback callback) override;
-  void EvictBucketData(const BucketLocator& bucket,
-                       base::OnceCallback<void(QuotaError)> callback) override;
+  void GetEvictionBuckets(int64_t target_usage,
+                          GetBucketsCallback callback) override;
+  void EvictBucketData(const std::set<BucketLocator>& buckets,
+                       base::OnceCallback<void(int)> callback) override;
   void GetEvictionRoundInfo(EvictionRoundInfoCallback callback) override;
 
   // Called by UI and internal modules.
@@ -551,13 +551,6 @@
   // The values returned total_space, available_space.
   using StorageCapacityCallback = base::OnceCallback<void(int64_t, int64_t)>;
 
-  struct EvictionContext {
-    EvictionContext();
-    ~EvictionContext();
-    BucketLocator evicted_bucket;
-    base::OnceCallback<void(QuotaError)> evict_bucket_data_callback;
-  };
-
   // Lazily called on the IO thread when the first quota manager API is called.
   //
   // Initialize() must be called after all quota clients are added to the
@@ -659,7 +652,11 @@
       base::OnceCallback<void(QuotaErrorOr<mojom::BucketTableEntryPtr>)>
           callback);
 
-  void DidEvictBucketData(QuotaErrorOr<mojom::BucketTableEntryPtr> entry);
+  // `barrier` should be called with true for a successful eviction or false if
+  // there's an error.
+  void DidEvictBucketData(BucketId evicted_bucket_id,
+                          base::RepeatingCallback<void(bool)> barrier,
+                          QuotaErrorOr<mojom::BucketTableEntryPtr> entry);
 
   void ReportHistogram();
   void DidGetTemporaryGlobalUsageForHistogram(int64_t usage,
@@ -672,15 +669,19 @@
   // Returns the list of bucket ids that should be excluded from eviction due to
   // consistent errors after multiple attempts.
   std::set<BucketId> GetEvictionBucketExceptions();
-  void DidGetEvictionBucket(GetBucketCallback callback,
-                            const absl::optional<BucketLocator>& bucket);
+  void DidGetEvictionBuckets(GetBucketsCallback callback,
+                             const std::set<BucketLocator>& buckets);
 
   void DidGetEvictionRoundInfo();
 
-  void GetLruEvictableBucket(blink::mojom::StorageType type,
-                             GetBucketCallback callback);
+  void GetBucketsForEvictionFromDatabase(
+      int64_t target_usage,
+      std::map<BucketLocator, int64_t> usage_map,
+      GetBucketsCallback callback);
 
-  void DidGetLruEvictableBucket(QuotaErrorOr<BucketLocator> result);
+  void DidGetBucketsForEvictionFromDatabase(
+      GetBucketsCallback callback,
+      QuotaErrorOr<std::set<BucketLocator>> result);
   void GetQuotaSettings(QuotaSettingsCallback callback);
   void DidGetSettings(absl::optional<QuotaSettings> settings);
   void GetStorageCapacity(StorageCapacityCallback callback);
@@ -807,8 +808,6 @@
   // The last time that an eviction round was started due to a full disk error.
   base::TimeTicks last_full_disk_eviction_time_;
 
-  GetBucketCallback lru_bucket_callback_;
-
   // Buckets that have been notified of access during LRU task to exclude from
   // eviction.
   std::set<BucketLocator> access_notified_buckets_;
@@ -843,7 +842,6 @@
   // reinstantiate the trackers when they're not handling requests.
 
   std::unique_ptr<QuotaTemporaryStorageEvictor> temporary_storage_evictor_;
-  EvictionContext eviction_context_;
   // Set when there is an eviction task in-flight.
   bool is_getting_eviction_bucket_ = false;
 
diff --git a/storage/browser/quota/quota_manager_unittest.cc b/storage/browser/quota/quota_manager_unittest.cc
index 1f509ad0..c2e480f 100644
--- a/storage/browser/quota/quota_manager_unittest.cc
+++ b/storage/browser/quota/quota_manager_unittest.cc
@@ -384,9 +384,9 @@
                        weak_factory_.GetWeakPtr()));
   }
 
-  QuotaError EvictBucketData(const BucketLocator& bucket) {
-    base::test::TestFuture<QuotaError> future;
-    quota_manager_impl_->EvictBucketData(bucket, future.GetCallback());
+  int EvictBucketData(const BucketLocator& bucket) {
+    base::test::TestFuture<int> future;
+    quota_manager_impl_->EvictBucketData({bucket}, future.GetCallback());
     return future.Get();
   }
 
@@ -455,13 +455,19 @@
     client->ModifyBucketAndNotify(bucket, delta);
   }
 
-  void GetEvictionBucket(StorageType type) {
+  // Gets just one bucket for eviction.
+  void GetEvictionBucket() {
     eviction_bucket_.reset();
-    // The quota manager's default eviction policy is to use an LRU eviction
-    // policy.
-    quota_manager_impl_->GetEvictionBucket(
-        type, base::BindOnce(&QuotaManagerImplTest::DidGetEvictionBucket,
-                             weak_factory_.GetWeakPtr()));
+    quota_manager_impl_->GetEvictionBuckets(
+        /*target_usage=*/1,
+        base::BindOnce(&QuotaManagerImplTest::DidGetEvictionBucket,
+                       weak_factory_.GetWeakPtr()));
+  }
+
+  std::set<BucketLocator> GetEvictionBuckets(int64_t target_usage) {
+    base::test::TestFuture<const std::set<BucketLocator>&> future;
+    quota_manager_impl_->GetEvictionBuckets(target_usage, future.GetCallback());
+    return future.Take();
   }
 
   std::set<BucketLocator> GetBucketsModifiedBetween(StorageType type,
@@ -499,10 +505,13 @@
     usage_ = global_usage;
   }
 
-  void DidGetEvictionBucket(const absl::optional<BucketLocator>& bucket) {
-    eviction_bucket_ = bucket;
-    DCHECK(!bucket.has_value() ||
-           !bucket->storage_key.origin().GetURL().is_empty());
+  void DidGetEvictionBucket(const std::set<BucketLocator>& bucket) {
+    if (1u == bucket.size()) {
+      eviction_bucket_ = *bucket.begin();
+    } else {
+      EXPECT_TRUE(bucket.empty());
+      eviction_bucket_ = {};
+    }
   }
 
   void SetStoragePressureCallback(
@@ -2215,7 +2224,7 @@
       GetBucket(ToStorageKey("http://foo.com/"), kDefaultBucketName, kTemp);
   ASSERT_TRUE(bucket.has_value());
 
-  ASSERT_EQ(EvictBucketData(bucket->ToBucketLocator()), QuotaError::kNone);
+  EvictBucketData(bucket->ToBucketLocator());
 
   bucket =
       GetBucket(ToStorageKey("http://foo.com/"), kDefaultBucketName, kTemp);
@@ -2238,7 +2247,7 @@
   bucket = GetBucket(ToStorageKey("http://foo.com"), "logs", kTemp);
   ASSERT_TRUE(bucket.has_value());
 
-  ASSERT_EQ(EvictBucketData(bucket->ToBucketLocator()), QuotaError::kNone);
+  EvictBucketData(bucket->ToBucketLocator());
 
   bucket = GetBucket(ToStorageKey("http://foo.com"), "logs", kTemp);
   ASSERT_FALSE(bucket.has_value());
@@ -2273,7 +2282,7 @@
       GetBucket(ToStorageKey("http://foo.com"), kDefaultBucketName, kTemp);
   ASSERT_TRUE(bucket.has_value());
 
-  ASSERT_EQ(EvictBucketData(bucket->ToBucketLocator()), QuotaError::kNone);
+  EvictBucketData(bucket->ToBucketLocator());
 
   // Ensure use count and time since access are recorded.
   histograms.ExpectTotalCount(
@@ -2293,7 +2302,7 @@
   bucket = GetBucket(ToStorageKey("http://bar.com"), kDefaultBucketName, kTemp);
   ASSERT_TRUE(bucket.has_value());
 
-  ASSERT_EQ(EvictBucketData(bucket->ToBucketLocator()), QuotaError::kNone);
+  EvictBucketData(bucket->ToBucketLocator());
 
   // The new use count should be logged.
   histograms.ExpectTotalCount(
@@ -2341,7 +2350,7 @@
 
   for (int i = 0; i < QuotaManagerImpl::kThresholdOfErrorsToBeDenylisted + 1;
        ++i) {
-    ASSERT_NE(EvictBucketData(bucket->ToBucketLocator()), QuotaError::kNone);
+    EvictBucketData(bucket->ToBucketLocator());
   }
 
   // The default bucket for "http://foo.com/" should still be in the database.
@@ -2350,7 +2359,7 @@
   ASSERT_TRUE(bucket.has_value());
 
   for (size_t i = 0; i < kNumberOfTemporaryBuckets - 1; ++i) {
-    GetEvictionBucket(kTemp);
+    GetEvictionBucket();
     task_environment_.RunUntilIdle();
     EXPECT_TRUE(eviction_bucket().has_value());
     // "http://foo.com/" should not be in the LRU list.
@@ -2360,7 +2369,7 @@
   }
 
   // Now the LRU list must be empty.
-  GetEvictionBucket(kTemp);
+  GetEvictionBucket();
   task_environment_.RunUntilIdle();
   EXPECT_FALSE(eviction_bucket().has_value());
 
@@ -3027,63 +3036,57 @@
                               base::Time::Now());
   task_environment_.RunUntilIdle();
 
-  GetEvictionBucket(kTemp);
+  GetEvictionBucket();
   task_environment_.RunUntilIdle();
   EXPECT_EQ("http://a.com:1/",
             eviction_bucket()->storage_key.origin().GetURL().spec());
 
   DeleteBucketData(*eviction_bucket(), AllQuotaClientTypes());
-  GetEvictionBucket(kTemp);
+  GetEvictionBucket();
   task_environment_.RunUntilIdle();
   EXPECT_EQ("http://a.com/",
             eviction_bucket()->storage_key.origin().GetURL().spec());
 
   DeleteBucketData(*eviction_bucket(), AllQuotaClientTypes());
-  GetEvictionBucket(kTemp);
+  GetEvictionBucket();
   task_environment_.RunUntilIdle();
   EXPECT_EQ("http://c.com/",
             eviction_bucket()->storage_key.origin().GetURL().spec());
 }
 
-TEST_F(QuotaManagerImplTest, GetLruEvictableBucket) {
-  StorageKey storage_key_a = ToStorageKey("http://a.com/");
-  StorageKey storage_key_b = ToStorageKey("http://b.com/");
-  StorageKey storage_key_c = ToStorageKey("http://c.com/");
+TEST_F(QuotaManagerImplTest, GetBucketsForEviction) {
+  static const ClientBucketData kData[] = {
+      {"http://a.com/", kDefaultBucketName, kTemp, 107},
+      {"http://b.com/", kDefaultBucketName, kTemp, 300},
+      {"http://c.com/", kDefaultBucketName, kTemp, 713},
+  };
+  MockQuotaClient* client =
+      CreateAndRegisterClient(QuotaClientType::kFileSystem, {kTemp});
+  RegisterClientBucketData(client, kData);
+  GetGlobalUsage(kTemp);
 
-  auto bucket =
-      CreateBucketForTesting(storage_key_a, kDefaultBucketName, kTemp);
-  ASSERT_TRUE(bucket.has_value());
-  BucketInfo bucket_a = bucket.value();
+  NotifyDefaultBucketAccessed(ToStorageKey("http://a.com/"), kTemp,
+                              base::Time::Now());
+  NotifyDefaultBucketAccessed(ToStorageKey("http://b.com/"), kTemp,
+                              base::Time::Now());
+  NotifyDefaultBucketAccessed(ToStorageKey("http://c.com/"), kTemp,
+                              base::Time::Now());
 
-  bucket = CreateBucketForTesting(storage_key_b, kDefaultBucketName, kTemp);
-  ASSERT_TRUE(bucket.has_value());
-  BucketInfo bucket_b = bucket.value();
+  auto buckets = GetEvictionBuckets(110);
+  EXPECT_THAT(buckets, testing::UnorderedElementsAre(
+                           testing::Field(&BucketLocator::storage_key,
+                                          ToStorageKey("http://a.com")),
+                           testing::Field(&BucketLocator::storage_key,
+                                          ToStorageKey("http://b.com"))));
 
-  bucket = CreateBucketForTesting(storage_key_c, kDefaultBucketName, kSync);
-  ASSERT_TRUE(bucket.has_value());
-  BucketInfo bucket_c = bucket.value();
-
-  NotifyBucketAccessed(bucket_a.ToBucketLocator());
-  NotifyBucketAccessed(bucket_b.ToBucketLocator());
-  NotifyBucketAccessed(bucket_c.ToBucketLocator());
-
-  GetEvictionBucket(kTemp);
-  task_environment_.RunUntilIdle();
-  EXPECT_EQ(bucket_a.ToBucketLocator(), eviction_bucket());
-
-  // Notify that the `bucket_a` is accessed.
-  NotifyBucketAccessed(bucket_a.ToBucketLocator());
-  GetEvictionBucket(kTemp);
-  task_environment_.RunUntilIdle();
-  EXPECT_EQ(bucket_b.ToBucketLocator(), eviction_bucket());
-
-  // Notify that the `bucket_b` is accessed while GetEvictionBucket is running.
-  GetEvictionBucket(kTemp);
-  NotifyBucketAccessed(bucket_b.ToBucketLocator());
-  task_environment_.RunUntilIdle();
-  // Post-filtering must have excluded the returned storage key, so we will
-  // see empty result here.
-  EXPECT_FALSE(eviction_bucket().has_value());
+  // Notify that the `bucket_a` is accessed. Now b is the LRU (and also happens
+  // to satisfy the desire to evict 110b of data).
+  NotifyDefaultBucketAccessed(ToStorageKey("http://a.com/"), kTemp,
+                              base::Time::Now());
+  buckets = GetEvictionBuckets(110);
+  EXPECT_THAT(buckets,
+              testing::UnorderedElementsAre(testing::Field(
+                  &BucketLocator::storage_key, ToStorageKey("http://b.com"))));
 }
 
 TEST_F(QuotaManagerImplTest, GetBucketsModifiedBetween) {
diff --git a/storage/browser/quota/quota_temporary_storage_evictor.cc b/storage/browser/quota/quota_temporary_storage_evictor.cc
index d50936bc..0160b4b 100644
--- a/storage/browser/quota/quota_temporary_storage_evictor.cc
+++ b/storage/browser/quota/quota_temporary_storage_evictor.cc
@@ -9,8 +9,10 @@
 #include <algorithm>
 
 #include "base/auto_reset.h"
+#include "base/feature_list.h"
 #include "base/functional/bind.h"
 #include "base/metrics/histogram_functions.h"
+#include "storage/browser/quota/quota_features.h"
 #include "storage/browser/quota/quota_manager_impl.h"
 #include "third_party/blink/public/common/storage_key/storage_key.h"
 #include "third_party/blink/public/mojom/quota/quota_types.mojom.h"
@@ -77,7 +79,7 @@
                      round_statistics_.usage_on_beginning_of_round -
                          round_statistics_.usage_on_end_of_round);
   base::UmaHistogramCounts1M("Quota.NumberOfEvictedBucketsPerRound",
-                             round_statistics_.num_evicted_buckets_in_round);
+                             round_statistics_.num_evicted_buckets);
 }
 
 void QuotaTemporaryStorageEvictor::ReportPerHourHistogram() {
@@ -107,7 +109,7 @@
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
 
   // Check if skipped round
-  if (round_statistics_.num_evicted_buckets_in_round) {
+  if (round_statistics_.num_evicted_buckets) {
     ReportPerRoundHistogram();
     time_of_end_of_last_nonskipped_round_ = base::Time::Now();
   } else {
@@ -158,8 +160,10 @@
 
 void QuotaTemporaryStorageEvictor::ConsiderEviction() {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-  // Only look for expired buckets once per round.
-  if (in_round()) {
+  if (base::FeatureList::IsEnabled(features::kNewQuotaEvictionRoutine)) {
+    CHECK(!in_round());
+  } else if (in_round()) {
+    // Only look for expired buckets once per round.
     OnEvictedExpiredBuckets(blink::mojom::QuotaStatusCode::kOk);
     return;
   }
@@ -216,13 +220,13 @@
 
   int64_t amount_to_evict = std::max(usage_overage, diskspace_shortage);
   if (status == blink::mojom::QuotaStatusCode::kOk && amount_to_evict > 0) {
-    // Space is getting tight. Get the least recently used storage key and
-    // continue.
     // TODO(michaeln): if the reason for eviction is low physical disk space,
     // make 'unlimited' storage keys subject to eviction too.
-    quota_eviction_handler_->GetEvictionBucket(
-        blink::mojom::StorageType::kTemporary,
-        base::BindOnce(&QuotaTemporaryStorageEvictor::OnGotEvictionBucket,
+    quota_eviction_handler_->GetEvictionBuckets(
+        base::FeatureList::IsEnabled(features::kNewQuotaEvictionRoutine)
+            ? amount_to_evict
+            : 1,
+        base::BindOnce(&QuotaTemporaryStorageEvictor::OnGotEvictionBuckets,
                        weak_factory_.GetWeakPtr()));
     return;
   }
@@ -240,36 +244,37 @@
   OnEvictionRoundFinished();
 }
 
-void QuotaTemporaryStorageEvictor::OnGotEvictionBucket(
-    const absl::optional<BucketLocator>& bucket) {
+void QuotaTemporaryStorageEvictor::OnGotEvictionBuckets(
+    const std::set<BucketLocator>& buckets) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
 
-  if (!bucket.has_value()) {
+  if (buckets.empty()) {
     StartEvictionTimerWithDelay(interval_ms_);
     OnEvictionRoundFinished();
     return;
   }
 
-  DCHECK(!bucket->storage_key.origin().GetURL().is_empty());
-
   quota_eviction_handler_->EvictBucketData(
-      bucket.value(),
-      base::BindOnce(&QuotaTemporaryStorageEvictor::OnEvictionComplete,
-                     weak_factory_.GetWeakPtr()));
+      buckets, base::BindOnce(&QuotaTemporaryStorageEvictor::OnEvictionComplete,
+                              weak_factory_.GetWeakPtr(), buckets.size()));
 }
 
-void QuotaTemporaryStorageEvictor::OnEvictionComplete(QuotaError status) {
+void QuotaTemporaryStorageEvictor::OnEvictionComplete(
+    int expected_evicted_buckets,
+    int actual_evicted_buckets) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
 
-  // Just calling ConsiderEviction() or StartEvictionTimerWithDelay() here is
-  // ok. No need to deal with the case that all of the Delete operations fail
-  // for a certain bucket. It doesn't result in trying to evict the same bucket
-  // permanently. The evictor skips buckets which had deletion errors a few
-  // times.
+  statistics_.num_evicted_buckets += actual_evicted_buckets;
+  round_statistics_.num_evicted_buckets += actual_evicted_buckets;
 
-  if (status == QuotaError::kNone) {
-    ++statistics_.num_evicted_buckets;
-    ++round_statistics_.num_evicted_buckets_in_round;
+  if (base::FeatureList::IsEnabled(features::kNewQuotaEvictionRoutine)) {
+    StartEvictionTimerWithDelay(interval_ms_);
+    OnEvictionRoundFinished();
+    return;
+  }
+
+  const bool success = expected_evicted_buckets == actual_evicted_buckets;
+  if (success) {
     // We many need to get rid of more space so reconsider immediately.
     ConsiderEviction();
   } else {
diff --git a/storage/browser/quota/quota_temporary_storage_evictor.h b/storage/browser/quota/quota_temporary_storage_evictor.h
index fc192cf..4e3d4e4 100644
--- a/storage/browser/quota/quota_temporary_storage_evictor.h
+++ b/storage/browser/quota/quota_temporary_storage_evictor.h
@@ -53,7 +53,7 @@
 
     int64_t usage_on_beginning_of_round = -1;
     int64_t usage_on_end_of_round = -1;
-    int64_t num_evicted_buckets_in_round = 0;
+    int64_t num_evicted_buckets = 0;
   };
 
   QuotaTemporaryStorageEvictor(QuotaEvictionHandler* quota_eviction_handler,
@@ -84,8 +84,9 @@
                               int64_t total_space,
                               int64_t current_usage,
                               bool current_usage_is_complete);
-  void OnGotEvictionBucket(const absl::optional<BucketLocator>& bucket);
-  void OnEvictionComplete(QuotaError status);
+  void OnGotEvictionBuckets(const std::set<BucketLocator>& buckets);
+  void OnEvictionComplete(int expected_evicted_buckets,
+                          int actual_evicted_buckets);
 
   void OnEvictionRoundStarted();
   void OnEvictionRoundFinished();
diff --git a/storage/browser/quota/quota_temporary_storage_evictor_unittest.cc b/storage/browser/quota/quota_temporary_storage_evictor_unittest.cc
index 4d5373f..8f4b15c7 100644
--- a/storage/browser/quota/quota_temporary_storage_evictor_unittest.cc
+++ b/storage/browser/quota/quota_temporary_storage_evictor_unittest.cc
@@ -10,14 +10,17 @@
 #include <utility>
 
 #include "base/containers/contains.h"
+#include "base/feature_list.h"
 #include "base/functional/bind.h"
 #include "base/functional/callback.h"
 #include "base/memory/weak_ptr.h"
 #include "base/run_loop.h"
 #include "base/task/sequenced_task_runner.h"
+#include "base/test/scoped_feature_list.h"
 #include "base/test/task_environment.h"
 #include "components/services/storage/public/cpp/buckets/bucket_id.h"
 #include "components/services/storage/public/cpp/buckets/bucket_locator.h"
+#include "storage/browser/quota/quota_features.h"
 #include "storage/browser/quota/quota_manager_impl.h"
 #include "storage/browser/quota/quota_temporary_storage_evictor.h"
 #include "testing/gtest/include/gtest/gtest.h"
@@ -42,24 +45,18 @@
     std::move(done).Run(blink::mojom::QuotaStatusCode::kOk);
   }
 
-  void EvictBucketData(const BucketLocator& bucket,
-                       base::OnceCallback<void(QuotaError)> callback) override {
-    if (error_on_evict_buckets_data_) {
-      std::move(callback).Run(QuotaError::kUnknownError);
-      return;
+  void EvictBucketData(const std::set<BucketLocator>& buckets,
+                       base::OnceCallback<void(int)> callback) override {
+    for (auto bucket : buckets) {
+      int64_t bucket_usage = EnsureBucketRemoved(bucket);
+      if (bucket_usage >= 0) {
+        available_space_ += bucket_usage;
+      }
     }
-    int64_t bucket_usage = EnsureBucketRemoved(bucket);
-    if (bucket_usage >= 0)
-      available_space_ += bucket_usage;
-    std::move(callback).Run(QuotaError::kNone);
+    std::move(callback).Run(buckets.size());
   }
 
   void GetEvictionRoundInfo(EvictionRoundInfoCallback callback) override {
-    if (error_on_get_usage_and_quota_) {
-      std::move(callback).Run(blink::mojom::QuotaStatusCode::kErrorAbort,
-                              QuotaSettings(), 0, 0, 0, false);
-      return;
-    }
     if (!task_for_get_usage_and_quota_.is_null())
       task_for_get_usage_and_quota_.Run();
     base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
@@ -69,13 +66,19 @@
                        GetUsage(), true));
   }
 
-  void GetEvictionBucket(StorageType type,
-                         GetBucketCallback callback) override {
-    if (bucket_order_.empty()) {
-      std::move(callback).Run(absl::nullopt);
-    } else {
-      std::move(callback).Run(bucket_order_.front());
+  void GetEvictionBuckets(int64_t target_usage,
+                          GetBucketsCallback callback) override {
+    int64_t usage_evicted = 0;
+    std::set<BucketLocator> buckets_to_evict;
+    for (const BucketLocator& bucket : bucket_order_) {
+      buckets_to_evict.insert(bucket);
+      usage_evicted += buckets_[bucket.id];
+      if (usage_evicted >= target_usage) {
+        break;
+      }
     }
+
+    std::move(callback).Run(buckets_to_evict);
   }
 
   int64_t GetUsage() const {
@@ -99,12 +102,6 @@
   void set_task_for_get_usage_and_quota(base::RepeatingClosure task) {
     task_for_get_usage_and_quota_ = std::move(task);
   }
-  void set_error_on_evict_buckets_data(bool error_on_evict_buckets_data) {
-    error_on_evict_buckets_data_ = error_on_evict_buckets_data;
-  }
-  void set_error_on_get_usage_and_quota(bool error_on_get_usage_and_quota) {
-    error_on_get_usage_and_quota_ = error_on_get_usage_and_quota;
-  }
   size_t get_evict_expired_buckets_count() const {
     return evict_expired_buckets_count_;
   }
@@ -147,8 +144,6 @@
   int64_t available_space_ = 0;
   std::list<BucketLocator> bucket_order_;
   std::map<BucketId, int64_t> buckets_;
-  bool error_on_evict_buckets_data_ = false;
-  bool error_on_get_usage_and_quota_ = false;
   // The number of times `EvictExpiredBuckets()` has been called.
   size_t evict_expired_buckets_count_ = 0;
 
@@ -375,6 +370,89 @@
           weak_factory_.GetWeakPtr(),
           std::make_pair(CreateBucket("http://www.e.com", /*is_default=*/false),
                          e_size),
+          absl::nullopt,
+          // First round evicts d.
+          initial_total_size - d_size,
+          // Second round evicts c and b.
+          initial_total_size - d_size + e_size - c_size - b_size));
+  EXPECT_EQ(initial_total_size, quota_eviction_handler()->GetUsage());
+  temporary_storage_evictor()->Start();
+  base::RunLoop().RunUntilIdle();
+  EXPECT_EQ(initial_total_size - d_size + e_size - c_size - b_size,
+            quota_eviction_handler()->GetUsage());
+  EXPECT_EQ(4, num_get_usage_and_quota_for_eviction());
+
+  EXPECT_EQ(0, statistics().num_errors_on_getting_usage_and_quota);
+  EXPECT_EQ(3, statistics().num_evicted_buckets);
+  EXPECT_EQ(2, statistics().num_eviction_rounds -
+                   statistics().num_skipped_eviction_rounds);
+  EXPECT_EQ(4U, quota_eviction_handler()->get_evict_expired_buckets_count());
+}
+
+TEST_F(QuotaTemporaryStorageEvictorTest, RepeatedEvictionSkippedTest) {
+  const int64_t a_size = 400;
+  const int64_t b_size = 150;
+  const int64_t c_size = 120;
+  const int64_t d_size = 292;
+  const int64_t initial_total_size = a_size + b_size + c_size + d_size;
+
+  quota_eviction_handler()->AddBucket(
+      CreateBucket("http://www.d.com", /*is_default=*/true), d_size);
+  quota_eviction_handler()->AddBucket(
+      CreateBucket("http://www.c.com", /*is_default=*/true), c_size);
+  quota_eviction_handler()->AddBucket(
+      CreateBucket("http://www.b.com", /*is_default=*/true), b_size);
+  quota_eviction_handler()->AddBucket(
+      CreateBucket("http://www.a.com", /*is_default=*/true), a_size);
+  quota_eviction_handler()->SetPoolSize(1000);
+  quota_eviction_handler()->set_available_space(1000000000);
+  quota_eviction_handler()->set_task_for_get_usage_and_quota(
+      base::BindRepeating(
+          &QuotaTemporaryStorageEvictorTest::TaskForRepeatedEvictionTest,
+          weak_factory_.GetWeakPtr(), std::make_pair(absl::nullopt, 0),
+          absl::nullopt, initial_total_size - d_size,
+          initial_total_size - d_size));
+  EXPECT_EQ(initial_total_size, quota_eviction_handler()->GetUsage());
+  temporary_storage_evictor()->Start();
+  base::RunLoop().RunUntilIdle();
+  EXPECT_EQ(initial_total_size - d_size, quota_eviction_handler()->GetUsage());
+  EXPECT_EQ(4, num_get_usage_and_quota_for_eviction());
+
+  EXPECT_EQ(0, statistics().num_errors_on_getting_usage_and_quota);
+  EXPECT_EQ(1, statistics().num_evicted_buckets);
+  EXPECT_EQ(1, statistics().num_eviction_rounds -
+                   statistics().num_skipped_eviction_rounds);
+}
+
+// Legacy test to verify eviction still works as expected when the
+// kNewQuotaEvictionRoutine feature is not enabled.
+TEST_F(QuotaTemporaryStorageEvictorTest, RepeatedEvictionTest_Rollback) {
+  base::test::ScopedFeatureList features;
+  features.InitAndDisableFeature(features::kNewQuotaEvictionRoutine);
+
+  const int64_t a_size = 400;
+  const int64_t b_size = 150;
+  const int64_t c_size = 120;
+  const int64_t d_size = 292;
+  const int64_t initial_total_size = a_size + b_size + c_size + d_size;
+  const int64_t e_size = 275;
+
+  quota_eviction_handler()->AddBucket(
+      CreateBucket("http://www.d.com", /*is_default=*/false), d_size);
+  quota_eviction_handler()->AddBucket(
+      CreateBucket("http://www.c.com", /*is_default=*/false), c_size);
+  quota_eviction_handler()->AddBucket(
+      CreateBucket("http://www.b.com", /*is_default=*/false), b_size);
+  quota_eviction_handler()->AddBucket(
+      CreateBucket("http://www.a.com", /*is_default=*/false), a_size);
+  quota_eviction_handler()->SetPoolSize(1000);
+  quota_eviction_handler()->set_available_space(1000000000);
+  quota_eviction_handler()->set_task_for_get_usage_and_quota(
+      base::BindRepeating(
+          &QuotaTemporaryStorageEvictorTest::TaskForRepeatedEvictionTest,
+          weak_factory_.GetWeakPtr(),
+          std::make_pair(CreateBucket("http://www.e.com", /*is_default=*/false),
+                         e_size),
           absl::nullopt, initial_total_size - d_size,
           initial_total_size - d_size + e_size - c_size));
   EXPECT_EQ(initial_total_size, quota_eviction_handler()->GetUsage());
@@ -391,7 +469,12 @@
   EXPECT_EQ(2U, quota_eviction_handler()->get_evict_expired_buckets_count());
 }
 
-TEST_F(QuotaTemporaryStorageEvictorTest, RepeatedEvictionSkippedTest) {
+// Legacy test to verify eviction still works as expected when the
+// kNewQuotaEvictionRoutine feature is not enabled.
+TEST_F(QuotaTemporaryStorageEvictorTest, RepeatedEvictionSkippedTest_Rollback) {
+  base::test::ScopedFeatureList features;
+  features.InitAndDisableFeature(features::kNewQuotaEvictionRoutine);
+
   const int64_t a_size = 400;
   const int64_t b_size = 150;
   const int64_t c_size = 120;
@@ -456,19 +539,22 @@
       base::BindRepeating(
           &QuotaTemporaryStorageEvictorTest::TaskForRepeatedEvictionTest,
           weak_factory_.GetWeakPtr(), std::make_pair(e_bucket, e_size),
-          c_bucket, initial_total_size - d_size,
-          initial_total_size - d_size + e_size - b_size));
+          c_bucket,
+          // First round evicts d.
+          initial_total_size - d_size,
+          // Second round evicts b and a since c was accessed.
+          initial_total_size - d_size + e_size - b_size - a_size));
   EXPECT_EQ(initial_total_size, quota_eviction_handler()->GetUsage());
   temporary_storage_evictor()->Start();
   base::RunLoop().RunUntilIdle();
   EXPECT_EQ(initial_total_size - d_size + e_size - b_size - a_size,
             quota_eviction_handler()->GetUsage());
-  EXPECT_EQ(5, num_get_usage_and_quota_for_eviction());
+  EXPECT_EQ(4, num_get_usage_and_quota_for_eviction());
 
   EXPECT_EQ(0, statistics().num_errors_on_getting_usage_and_quota);
   EXPECT_EQ(3, statistics().num_evicted_buckets);
-  EXPECT_EQ(2, statistics().num_eviction_rounds);
-  EXPECT_EQ(0, statistics().num_skipped_eviction_rounds);
+  EXPECT_EQ(2, statistics().num_eviction_rounds -
+                   statistics().num_skipped_eviction_rounds);
 }
 
 TEST_F(QuotaTemporaryStorageEvictorTest, DiskSpaceNonEvictionTest) {
@@ -592,7 +678,7 @@
   }
   EXPECT_TRUE(EvictorHasBuckets({bucket_x, bucket_w}));
 
-  EXPECT_EQ(3, num_get_usage_and_quota_for_eviction());
+  EXPECT_EQ(1, num_get_usage_and_quota_for_eviction());
   EXPECT_EQ(0, statistics().num_errors_on_getting_usage_and_quota);
   EXPECT_EQ(2, statistics().num_evicted_buckets);
   EXPECT_EQ(1, statistics().num_eviction_rounds);
diff --git a/storage/browser/quota/usage_tracker.cc b/storage/browser/quota/usage_tracker.cc
index c5ec025b..fd808ec 100644
--- a/storage/browser/quota/usage_tracker.cc
+++ b/storage/browser/quota/usage_tracker.cc
@@ -143,10 +143,11 @@
   std::map<std::string, int64_t> host_usage;
   for (const auto& client_type_and_trackers : client_tracker_map_) {
     for (const auto& client_tracker : client_type_and_trackers.second) {
-      std::map<std::string, int64_t> client_host_usage =
-          client_tracker->GetCachedHostsUsage();
-      for (const auto& host_and_usage : client_host_usage)
-        host_usage[host_and_usage.first] += host_and_usage.second;
+      const std::map<BucketLocator, int64_t>& usage_map =
+          client_tracker->GetCachedBucketsUsage();
+      for (const auto& [bucket, usage] : usage_map) {
+        host_usage[bucket.storage_key.origin().host()] += usage;
+      }
     }
   }
   return host_usage;
@@ -158,16 +159,31 @@
   std::map<blink::StorageKey, int64_t> storage_key_usage;
   for (const auto& client_type_and_trackers : client_tracker_map_) {
     for (const auto& client_tracker : client_type_and_trackers.second) {
-      std::map<blink::StorageKey, int64_t> client_storage_key_usage =
-          client_tracker->GetCachedStorageKeysUsage();
-      for (const auto& storage_key_and_usage : client_storage_key_usage)
-        storage_key_usage[storage_key_and_usage.first] +=
-            storage_key_and_usage.second;
+      const std::map<BucketLocator, int64_t>& usage_map =
+          client_tracker->GetCachedBucketsUsage();
+      for (const auto& [bucket, usage] : usage_map) {
+        storage_key_usage[bucket.storage_key] += usage;
+      }
     }
   }
   return storage_key_usage;
 }
 
+std::map<BucketLocator, int64_t> UsageTracker::GetCachedBucketsUsage() const {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  std::map<BucketLocator, int64_t> aggregated_usage;
+  for (const auto& client_type_and_trackers : client_tracker_map_) {
+    for (const auto& client_tracker : client_type_and_trackers.second) {
+      const std::map<BucketLocator, int64_t>& usage_map =
+          client_tracker->GetCachedBucketsUsage();
+      for (const auto& [bucket, usage] : usage_map) {
+        aggregated_usage[bucket] += usage;
+      }
+    }
+  }
+  return aggregated_usage;
+}
+
 void UsageTracker::SetUsageCacheEnabled(QuotaClientType client_type,
                                         const blink::StorageKey& storage_key,
                                         bool enabled) {
diff --git a/storage/browser/quota/usage_tracker.h b/storage/browser/quota/usage_tracker.h
index 808bc381..0d01f2c 100644
--- a/storage/browser/quota/usage_tracker.h
+++ b/storage/browser/quota/usage_tracker.h
@@ -97,6 +97,9 @@
   // recording.
   std::map<blink::StorageKey, int64_t> GetCachedStorageKeysUsage() const;
 
+  // Returns all cached usage organized by bucket. Used for eviction.
+  std::map<BucketLocator, int64_t> GetCachedBucketsUsage() const;
+
   // Checks if there are ongoing tasks to get usage. Used to prevent a
   // UsageTracker reset from happening before a task is complete.
   bool IsWorking() const {
diff --git a/testing/buildbot/chromium.chromiumos.json b/testing/buildbot/chromium.chromiumos.json
index fe543860..1d1fb9e78 100644
--- a/testing/buildbot/chromium.chromiumos.json
+++ b/testing/buildbot/chromium.chromiumos.json
@@ -1276,7 +1276,6 @@
           "--board=eve",
           "--flash"
         ],
-        "ci_only": true,
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
         },
@@ -1332,7 +1331,6 @@
           "--board=eve",
           "--flash"
         ],
-        "ci_only": true,
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
         },
@@ -1420,7 +1418,6 @@
           "--board=eve",
           "--flash"
         ],
-        "ci_only": true,
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
         },
diff --git a/testing/buildbot/filters/linux-lacros.browser_tests.filter b/testing/buildbot/filters/linux-lacros.browser_tests.filter
index 1b19aaa..2ba40ac 100644
--- a/testing/buildbot/filters/linux-lacros.browser_tests.filter
+++ b/testing/buildbot/filters/linux-lacros.browser_tests.filter
@@ -3,8 +3,6 @@
 -AccessContextAuditBrowserTest.RemoveRecords
 -All/WebRtcScreenCaptureBrowserTestWithPicker.ScreenCaptureVideo/*
 -All/WebRtcScreenCaptureBrowserTestWithPicker.ScreenCaptureVideoAndAudio/*
--BluetoothLowEnergyApiTest.*
--BluetoothPrivateApiTest.*
 -BrowserViewTest.GetAccessibleTabModalDialogTitle
 -BrowsingDataRemoverBrowserTest.StorageRemovedFromDisk
 -DeclarativeContentApiTest.RulesPersistence
diff --git a/testing/buildbot/test_suite_exceptions.pyl b/testing/buildbot/test_suite_exceptions.pyl
index 6a9f1b19..57c3542 100644
--- a/testing/buildbot/test_suite_exceptions.pyl
+++ b/testing/buildbot/test_suite_exceptions.pyl
@@ -1312,13 +1312,6 @@
       },
     },
   },
-  'cc_unittests eve': {
-    'modifications': {
-      'lacros-amd64-generic-rel': {
-        'ci_only': True,
-      },
-    },
-  },
   'check_static_initializers': {
     'modifications': {
       'Mac Builder': {
@@ -2765,13 +2758,6 @@
       },
     },
   },
-  'lacros_all_tast_tests eve': {
-    'modifications': {
-      'lacros-amd64-generic-rel': {
-        'ci_only': True,
-      },
-    },
-  },
   'lacros_all_tast_tests jacuzzi': {
     'modifications': {
       'lacros-arm-generic-rel': {
@@ -3153,13 +3139,6 @@
       },
     },
   },
-  'ozone_unittests eve': {
-    'modifications': {
-      'lacros-amd64-generic-rel': {
-        'ci_only': True,
-      },
-    },
-  },
   'ozone_x11_unittests': {
     # Wayland bot uses chromium_linux_gtests that includes some x11 only
     # test targets. To avoid maintaining a list of tests, remove them here.
diff --git a/testing/scripts/run_finch_smoke_tests_android.py b/testing/scripts/run_finch_smoke_tests_android.py
index ac99a56..93b9f9d 100755
--- a/testing/scripts/run_finch_smoke_tests_android.py
+++ b/testing/scripts/run_finch_smoke_tests_android.py
@@ -77,7 +77,7 @@
 from pylib.local.emulator import avd
 from py_utils.tempfile_ext import NamedTemporaryDirectory
 from scripts import common
-from skia_gold_infra.finch_skia_gold_properties import FinchSkiaGoldProperties
+from skia_gold_common.skia_gold_properties import SkiaGoldProperties
 from skia_gold_infra import finch_skia_gold_session_manager
 from skia_gold_infra import finch_skia_gold_utils
 from run_wpt_tests import get_device
@@ -335,7 +335,7 @@
     self._skia_gold_tmp_dir = tempfile.mkdtemp()
     self._skia_gold_session_manager = (
         finch_skia_gold_session_manager.FinchSkiaGoldSessionManager(
-            self._skia_gold_tmp_dir, FinchSkiaGoldProperties(self.options)))
+            self._skia_gold_tmp_dir, SkiaGoldProperties(self.options)))
     return self
 
   def __exit__(self, exc_type, exc_val, exc_tb):
@@ -499,7 +499,7 @@
                         help='Number of emulator to run.')
     common.add_emulator_args(parser)
     # Add arguments used by Skia Gold.
-    FinchSkiaGoldProperties.AddCommandLineArguments(parser)
+    SkiaGoldProperties.AddCommandLineArguments(parser)
 
   def _add_extra_arguments(self):
     parser = self._parser
diff --git a/testing/scripts/run_variations_smoke_tests.py b/testing/scripts/run_variations_smoke_tests.py
index d6b9417b..0c5c21d 100755
--- a/testing/scripts/run_variations_smoke_tests.py
+++ b/testing/scripts/run_variations_smoke_tests.py
@@ -23,7 +23,7 @@
 from threading import Thread
 
 import pkg_resources
-from skia_gold_infra.finch_skia_gold_properties import FinchSkiaGoldProperties
+from skia_gold_common.skia_gold_properties import SkiaGoldProperties
 from skia_gold_infra import finch_skia_gold_utils
 
 import variations_seed_access_helper as seed_helper
@@ -325,7 +325,7 @@
   parser = argparse.ArgumentParser()
   parser.add_argument('--isolated-script-test-output', type=str)
   parser.add_argument('--isolated-script-test-filter', type=str)
-  FinchSkiaGoldProperties.AddCommandLineArguments(parser)
+  SkiaGoldProperties.AddCommandLineArguments(parser)
   args, rest = parser.parse_known_args()
 
   temp_dir = tempfile.mkdtemp()
diff --git a/testing/scripts/skia_gold_infra/finch_skia_gold_properties.py b/testing/scripts/skia_gold_infra/finch_skia_gold_properties.py
deleted file mode 100644
index 6891230..0000000
--- a/testing/scripts/skia_gold_infra/finch_skia_gold_properties.py
+++ /dev/null
@@ -1,29 +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.
-"""Finch implementation of skia_gold_properties.py."""
-
-import os
-import subprocess
-import sys
-
-THIS_DIR = os.path.abspath(os.path.dirname(__file__))
-CHROMIUM_SRC_DIR = os.path.realpath(os.path.join(THIS_DIR, '..', '..', '..'))
-sys.path.insert(0, os.path.join(CHROMIUM_SRC_DIR, 'build'))
-from skia_gold_common import skia_gold_properties
-
-
-class FinchSkiaGoldProperties(skia_gold_properties.SkiaGoldProperties):
-  @staticmethod
-  def _GetGitOriginMainHeadSha1():
-    try:
-      return subprocess.check_output(
-          ['git', 'rev-parse', 'origin/main'],
-          shell=_IsWin(),
-          cwd=CHROMIUM_SRC_DIR).strip()
-    except subprocess.CalledProcessError:
-      return None
-
-
-def _IsWin():
-  return sys.platform == 'win32'
diff --git a/testing/scripts/skia_gold_infra/finch_skia_gold_utils.py b/testing/scripts/skia_gold_infra/finch_skia_gold_utils.py
index 430b232..bd172c8 100644
--- a/testing/scripts/skia_gold_infra/finch_skia_gold_utils.py
+++ b/testing/scripts/skia_gold_infra/finch_skia_gold_utils.py
@@ -4,9 +4,10 @@
 
 import logging
 import sys
-from .finch_skia_gold_properties import FinchSkiaGoldProperties
 from .finch_skia_gold_session_manager import FinchSkiaGoldSessionManager
 
+from skia_gold_common.skia_gold_properties import SkiaGoldProperties
+
 # This is the corpus used by skia gold to identify the data set.
 # We are not using the same corpus as the rest of the skia gold chromium tests.
 # This corpus is a dedicated one for finch smoke tests.
@@ -15,7 +16,7 @@
 
 class FinchSkiaGoldUtil:
   def __init__(self, temp_dir, args):
-    self._skia_gold_properties = FinchSkiaGoldProperties(args)
+    self._skia_gold_properties = SkiaGoldProperties(args)
     self._skia_gold_session_manager = FinchSkiaGoldSessionManager(
         temp_dir, self._skia_gold_properties)
     self._skia_gold_session = self._GetSkiaGoldSession()
diff --git a/testing/variations/fieldtrial_testing_config.json b/testing/variations/fieldtrial_testing_config.json
index 1d34fd9..8d237f5e 100644
--- a/testing/variations/fieldtrial_testing_config.json
+++ b/testing/variations/fieldtrial_testing_config.json
@@ -2827,9 +2827,9 @@
             ],
             "experiments": [
                 {
-                    "name": "Holdback",
+                    "name": "Enabled",
                     "params": {
-                        "critical_threshold_percentage": "520"
+                        "critical_threshold_percentage": "1500"
                     },
                     "enable_features": [
                         "ChromeOSMemoryPressureSignalStudyNonArc"
diff --git a/third_party/blink/perf_tests/layout/multicol/balance-tables-with-break-inside-avoidance.html b/third_party/blink/perf_tests/layout/multicol/balance-tables-with-break-inside-avoidance.html
new file mode 100644
index 0000000..f1d3f50
--- /dev/null
+++ b/third_party/blink/perf_tests/layout/multicol/balance-tables-with-break-inside-avoidance.html
@@ -0,0 +1,146 @@
+<!DOCTYPE html>
+<script src="../../resources/runner.js"></script>
+<style>
+  #target .table {
+    display: table;
+    width: 100%;
+  }
+  .table > div {
+    display: table-row;
+  }
+  .table > div > div {
+    display: table-cell;
+    padding-bottom: 8px;
+  }
+</style>
+<pre id="log"></pre>
+<div style="overflow:hidden; width:0; height:0;">
+  <div id="target" style="columns:3; orphans:1; widows:1; width:40em; line-height: 20px;">
+    <div style="break-inside: avoid;">
+      <div style="height:30px;"></div>
+      <div class="table">
+	<div><div><br></div></div>
+	<div><div><br></div></div>
+	<div><div><br></div></div>
+	<div><div><br></div></div>
+	<div><div><br></div></div>
+	<div><div><br></div></div>
+	<div><div><br></div></div>
+	<div><div><br></div></div>
+	<div><div><br></div></div>
+	<div><div><br></div></div>
+	<div><div><br></div></div>
+	<div><div><br></div></div>
+	<div><div><br></div></div>
+	<div><div><br></div></div>
+	<div><div><br></div></div>
+	<div><div><br></div></div>
+	<div><div><br></div></div>
+      </div>
+    </div>
+    <div style="break-inside: avoid;">
+      <div style="height:30px;"></div>
+      <div class="table">
+	<div><div><br></div></div>
+	<div><div><br></div></div>
+	<div><div><br></div></div>
+	<div><div><br></div></div>
+	<div><div><br></div></div>
+	<div><div><br></div></div>
+	<div><div><br></div></div>
+	<div><div><br></div></div>
+	<div><div><br></div></div>
+	<div><div><br></div></div>
+	<div><div><br></div></div>
+	<div><div><br></div></div>
+	<div><div><br></div></div>
+	<div><div><br></div></div>
+	<div><div><br></div></div>
+	<div><div><br></div></div>
+	<div><div><br></div></div>
+      </div>
+    </div>
+    <div style="break-inside: avoid;">
+      <div style="height:30px;"></div>
+      <div class="table">
+	<div><div><br></div></div>
+	<div><div><br></div></div>
+	<div><div><br></div></div>
+	<div><div><br></div></div>
+	<div><div><br></div></div>
+	<div><div><br></div></div>
+	<div><div><br></div></div>
+	<div><div><br></div></div>
+	<div><div><br></div></div>
+	<div><div><br></div></div>
+	<div><div><br></div></div>
+	<div><div><br></div></div>
+	<div><div><br></div></div>
+	<div><div><br></div></div>
+	<div><div><br></div></div>
+	<div><div><br></div></div>
+	<div><div><br></div></div>
+      </div>
+    </div>
+    <div style="break-inside: avoid;">
+      <div style="height:30px;"></div>
+      <div class="table">
+	<div><div><br></div></div>
+	<div><div><br></div></div>
+	<div><div><br></div></div>
+	<div><div><br></div></div>
+	<div><div><br></div></div>
+	<div><div><br></div></div>
+	<div><div><br></div></div>
+	<div><div><br></div></div>
+	<div><div><br></div></div>
+	<div><div><br></div></div>
+	<div><div><br></div></div>
+	<div><div><br></div></div>
+	<div><div><br></div></div>
+	<div><div><br></div></div>
+	<div><div><br></div></div>
+	<div><div><br></div></div>
+	<div><div><br></div></div>
+      </div>
+    </div>
+    <div style="break-inside: avoid;">
+      <div style="height:30px;"></div>
+      <div class="table">
+	<div><div><br></div></div>
+	<div><div><br></div></div>
+	<div><div><br></div></div>
+	<div><div><br></div></div>
+	<div><div><br></div></div>
+	<div><div><br></div></div>
+	<div><div><br></div></div>
+	<div><div><br></div></div>
+	<div><div><br></div></div>
+	<div><div><br></div></div>
+	<div><div><br></div></div>
+	<div><div><br></div></div>
+	<div><div><br></div></div>
+	<div><div><br></div></div>
+	<div><div><br></div></div>
+	<div><div><br></div></div>
+	<div><div><br></div></div>
+      </div>
+    </div>
+  </div>
+</div>
+<script>
+  var target = document.getElementById("target");
+  var style = target.style;
+
+  function test() {
+    style.display = "block";
+    PerfTestRunner.forceLayout();
+    style.display = "none";
+    PerfTestRunner.forceLayout();
+  }
+
+  PerfTestRunner.measureRunsPerSecond({
+    description: "Lists items with tall markers in balanced multicol.",
+    run: test
+  });
+</script>
diff --git a/third_party/blink/renderer/core/animation/css_color_interpolation_type.cc b/third_party/blink/renderer/core/animation/css_color_interpolation_type.cc
index 7c250f5..8096cdc 100644
--- a/third_party/blink/renderer/core/animation/css_color_interpolation_type.cc
+++ b/third_party/blink/renderer/core/animation/css_color_interpolation_type.cc
@@ -73,11 +73,10 @@
     Color color,
     Color::ColorSpace color_space) {
   color.ConvertToColorSpace(color_space);
-  return std::make_tuple(
-      param0 + fraction * color.Param0() * color.FloatAlpha(),
-      param1 + fraction * color.Param1() * color.FloatAlpha(),
-      param2 + fraction * color.Param2() * color.FloatAlpha(),
-      alpha + fraction * color.FloatAlpha());
+  return std::make_tuple(param0 + fraction * color.Param0() * color.Alpha(),
+                         param1 + fraction * color.Param1() * color.Alpha(),
+                         param2 + fraction * color.Param2() * color.Alpha(),
+                         alpha + fraction * color.Alpha());
 }
 
 std::tuple<double, double, double> UnpremultiplyColor(double param0,
diff --git a/third_party/blink/renderer/core/animation/css_color_interpolation_type_test.cc b/third_party/blink/renderer/core/animation/css_color_interpolation_type_test.cc
index bf43029..d624791 100644
--- a/third_party/blink/renderer/core/animation/css_color_interpolation_type_test.cc
+++ b/third_party/blink/renderer/core/animation/css_color_interpolation_type_test.cc
@@ -99,27 +99,27 @@
   ASSERT_EQ(100, result_color.Param0());
   ASSERT_EQ(1, result_color.Param1());
   ASSERT_EQ(1, result_color.Param2());
-  ASSERT_EQ(1, result_color.FloatAlpha());
+  ASSERT_EQ(1, result_color.Alpha());
   ASSERT_EQ(Color::ColorSpace::kOklab,
             result_color.GetColorInterpolationSpace());
 
   from->Interpolate(*to, 0.5, *result);
   result_color = CSSColorInterpolationType::GetColor(*result);
   // Everything is premultiplied.
-  ASSERT_EQ(50, result_color.Param0() * result_color.FloatAlpha());
-  ASSERT_EQ(0.5, result_color.Param1() * result_color.FloatAlpha());
-  ASSERT_EQ(0.5, result_color.Param2() * result_color.FloatAlpha());
-  ASSERT_EQ(0.75, result_color.FloatAlpha());
+  ASSERT_EQ(50, result_color.Param0() * result_color.Alpha());
+  ASSERT_EQ(0.5, result_color.Param1() * result_color.Alpha());
+  ASSERT_EQ(0.5, result_color.Param2() * result_color.Alpha());
+  ASSERT_EQ(0.75, result_color.Alpha());
   ASSERT_EQ(Color::ColorSpace::kOklab,
             result_color.GetColorInterpolationSpace());
 
   from->Interpolate(*to, 0.75, *result);
   result_color = CSSColorInterpolationType::GetColor(*result);
   // Everything is premultiplied.
-  ASSERT_EQ(25, result_color.Param0() * result_color.FloatAlpha());
-  ASSERT_EQ(0.25, result_color.Param1() * result_color.FloatAlpha());
-  ASSERT_EQ(0.25, result_color.Param2() * result_color.FloatAlpha());
-  ASSERT_EQ(0.625, result_color.FloatAlpha());
+  ASSERT_EQ(25, result_color.Param0() * result_color.Alpha());
+  ASSERT_EQ(0.25, result_color.Param1() * result_color.Alpha());
+  ASSERT_EQ(0.25, result_color.Param2() * result_color.Alpha());
+  ASSERT_EQ(0.625, result_color.Alpha());
   ASSERT_EQ(Color::ColorSpace::kOklab,
             result_color.GetColorInterpolationSpace());
 
@@ -128,7 +128,7 @@
   ASSERT_EQ(0, result_color.Param0());
   ASSERT_EQ(0, result_color.Param1());
   ASSERT_EQ(0, result_color.Param2());
-  ASSERT_EQ(0.5, result_color.FloatAlpha());
+  ASSERT_EQ(0.5, result_color.Alpha());
   ASSERT_EQ(Color::ColorSpace::kOklab,
             result_color.GetColorInterpolationSpace());
 }
diff --git a/third_party/blink/renderer/core/animation/interpolable_color.cc b/third_party/blink/renderer/core/animation/interpolable_color.cc
index 9dff7cbd..60780d4 100644
--- a/third_party/blink/renderer/core/animation/interpolable_color.cc
+++ b/third_party/blink/renderer/core/animation/interpolable_color.cc
@@ -27,10 +27,10 @@
 
   // All params are stored pre-multiplied.
   // https://www.w3.org/TR/css-color-4/#interpolation-alpha
-  result->param0_.Set(color.Param0() * color.FloatAlpha());
-  result->param1_.Set(color.Param1() * color.FloatAlpha());
-  result->param2_.Set(color.Param2() * color.FloatAlpha());
-  result->alpha_.Set(color.FloatAlpha());
+  result->param0_.Set(color.Param0() * color.Alpha());
+  result->param1_.Set(color.Param1() * color.Alpha());
+  result->param2_.Set(color.Param2() * color.Alpha());
+  result->alpha_.Set(color.Alpha());
 
   return result;
 }
@@ -162,10 +162,10 @@
 
   Color underlying_color = GetColor();
   underlying_color.ConvertToColorSpace(color_space);
-  param0_.Set(underlying_color.Param0() * underlying_color.FloatAlpha());
-  param1_.Set(underlying_color.Param1() * underlying_color.FloatAlpha());
-  param2_.Set(underlying_color.Param2() * underlying_color.FloatAlpha());
-  alpha_.Set(underlying_color.FloatAlpha());
+  param0_.Set(underlying_color.Param0() * underlying_color.Alpha());
+  param1_.Set(underlying_color.Param1() * underlying_color.Alpha());
+  param2_.Set(underlying_color.Param2() * underlying_color.Alpha());
+  alpha_.Set(underlying_color.Alpha());
 
   color_space_ = color_space;
 }
diff --git a/third_party/blink/renderer/core/css/css_properties.json5 b/third_party/blink/renderer/core/css/css_properties.json5
index 875329b4..88ec8c8 100644
--- a/third_party/blink/renderer/core/css/css_properties.json5
+++ b/third_party/blink/renderer/core/css/css_properties.json5
@@ -3112,7 +3112,6 @@
       inherited: false,
       keywords: ["drop", "normal", "raise"],
       property_methods: ["ParseSingleValue", "CSSValueFromComputedStyleInternal"],
-      runtime_flag: "CSSInitialLetter",
       type_name: "StyleInitialLetter",
       valid_for_first_letter: true,
     },
diff --git a/third_party/blink/renderer/core/dom/first_letter_pseudo_element_test.cc b/third_party/blink/renderer/core/dom/first_letter_pseudo_element_test.cc
index f74d493a..5c7f01c 100644
--- a/third_party/blink/renderer/core/dom/first_letter_pseudo_element_test.cc
+++ b/third_party/blink/renderer/core/dom/first_letter_pseudo_element_test.cc
@@ -65,7 +65,6 @@
 }
 
 TEST_F(FirstLetterPseudoElementTest, InitialLetter) {
-  ScopedCSSInitialLetterForTest enable_initial_letter_scope(true);
   LoadAhem();
   InsertStyleElement(
       "p { font: 20px/24px Ahem; }"
diff --git a/third_party/blink/renderer/core/html/html_element.cc b/third_party/blink/renderer/core/html/html_element.cc
index 42069b8..741a85f 100644
--- a/third_party/blink/renderer/core/html/html_element.cc
+++ b/third_party/blink/renderer/core/html/html_element.cc
@@ -1542,9 +1542,11 @@
   original_document.UpdateStyleAndLayoutTreeForNode(this);
 
   // Queue a delayed hide event, if necessary.
-  if (!GetDocument().HoverElement() ||
-      !IsNodePopoverDescendant(*GetDocument().HoverElement())) {
-    MaybeQueuePopoverHideEvent();
+  if (RuntimeEnabledFeatures::HTMLPopoverHintEnabled()) {
+    if (!GetDocument().HoverElement() ||
+        !IsNodePopoverDescendant(*GetDocument().HoverElement())) {
+      MaybeQueuePopoverHideEvent();
+    }
   }
 
   SetPopoverFocusOnShow();
@@ -2148,9 +2150,7 @@
 }
 
 void HTMLElement::MaybeQueuePopoverHideEvent() {
-  if (!RuntimeEnabledFeatures::HTMLPopoverHintEnabled()) {
-    return;
-  }
+  CHECK(RuntimeEnabledFeatures::HTMLPopoverHintEnabled());
   CHECK(HasPopoverAttribute());
   // If the popover isn't showing, or it has an infinite PopoverHideDelay, do
   // nothing.
diff --git a/third_party/blink/renderer/core/inspector/inspector_contrast.cc b/third_party/blink/renderer/core/inspector/inspector_contrast.cc
index 83e2370..ba16499 100644
--- a/third_party/blink/renderer/core/inspector/inspector_contrast.cc
+++ b/third_party/blink/renderer/core/inspector/inspector_contrast.cc
@@ -228,7 +228,7 @@
   Color text_color =
       static_cast<const cssvalue::CSSColor*>(text_color_value)->Value();
 
-  text_color.SetAlpha(text_opacity * text_color.FloatAlpha());
+  text_color.SetAlpha(text_opacity * text_color.Alpha());
 
   float contrast_ratio = color_utils::GetContrastRatio(
       bgcolors.at(0).Blend(text_color).toSkColor4f(),
@@ -338,8 +338,7 @@
 
     // Opacity applies to the entire element so mix it with the alpha channel.
     if (style->HasOpacity()) {
-      background_color.SetAlpha(background_color.FloatAlpha() *
-                                style->Opacity());
+      background_color.SetAlpha(background_color.Alpha() * style->Opacity());
       // If the background element is the ancestor of the top element or is the
       // top element, the opacity affects the text color of the top element.
       if (element == top_element ||
diff --git a/third_party/blink/renderer/core/layout/layout_box_hot.cc b/third_party/blink/renderer/core/layout/layout_box_hot.cc
index 8386aa89..5b1f648 100644
--- a/third_party/blink/renderer/core/layout/layout_box_hot.cc
+++ b/third_party/blink/renderer/core/layout/layout_box_hot.cc
@@ -374,6 +374,12 @@
         if (is_fragmented)
           return nullptr;
 
+        if (cached_layout_result->MinimalSpaceShortage()) {
+          // The fragmentation line has moved, and there was space shortage
+          // reported. This value is no longer valid.
+          return nullptr;
+        }
+
         // Fragmentation inside a nested multicol container depends on the
         // amount of remaining space in the outer fragmentation context, so if
         // this has changed, we cannot necessarily re-use it. To keep things
diff --git a/third_party/blink/renderer/core/layout/layout_replaced.h b/third_party/blink/renderer/core/layout/layout_replaced.h
index 7f0dad7..9ef311c2 100644
--- a/third_party/blink/renderer/core/layout/layout_replaced.h
+++ b/third_party/blink/renderer/core/layout/layout_replaced.h
@@ -202,6 +202,12 @@
       const LayoutSize size,
       const NGPhysicalBoxStrut& border_padding) const;
 
+  // ReplacedPainter doesn't support CompositeBackgroundAttachmentFixed yet.
+  bool ComputeCanCompositeBackgroundAttachmentFixed() const override {
+    NOT_DESTROYED();
+    return false;
+  }
+
  private:
   // Computes a rect, relative to the element's content's natural size, that
   // should be used as the content source when rendering this element. This
diff --git a/third_party/blink/renderer/core/layout/ng/inline/ng_inline_node_test.cc b/third_party/blink/renderer/core/layout/ng/inline/ng_inline_node_test.cc
index 60801146..e4e1519 100644
--- a/third_party/blink/renderer/core/layout/ng/inline/ng_inline_node_test.cc
+++ b/third_party/blink/renderer/core/layout/ng/inline/ng_inline_node_test.cc
@@ -1573,7 +1573,6 @@
 }
 
 TEST_F(NGInlineNodeTest, InitialLetter) {
-  ScopedCSSInitialLetterForTest enable_initial_letter_scope(true);
   LoadAhem();
   InsertStyleElement(
       "p { font: 20px/24px Ahem; }"
diff --git a/third_party/blink/renderer/core/layout/ng/ng_layout_result.h b/third_party/blink/renderer/core/layout/ng/ng_layout_result.h
index 09cd092..4b7364b 100644
--- a/third_party/blink/renderer/core/layout/ng/ng_layout_result.h
+++ b/third_party/blink/renderer/core/layout/ng/ng_layout_result.h
@@ -298,15 +298,18 @@
   }
 
   absl::optional<LayoutUnit> MinimalSpaceShortage() const {
-    if (!HasRareData() || rare_data_->minimal_space_shortage == kIndefiniteSize)
+    if (!HasRareData() || space_.IsInitialColumnBalancingPass() ||
+        rare_data_->minimal_space_shortage == kIndefiniteSize) {
       return absl::nullopt;
+    }
     return rare_data_->minimal_space_shortage;
   }
 
   LayoutUnit TallestUnbreakableBlockSize() const {
-    if (!HasRareData() ||
-        rare_data_->tallest_unbreakable_block_size == kIndefiniteSize)
+    if (!HasRareData() || !space_.IsInitialColumnBalancingPass() ||
+        rare_data_->tallest_unbreakable_block_size == kIndefiniteSize) {
       return LayoutUnit();
+    }
     return rare_data_->tallest_unbreakable_block_size;
   }
 
diff --git a/third_party/blink/renderer/core/paint/svg_object_painter.cc b/third_party/blink/renderer/core/paint/svg_object_painter.cc
index 1e67954..945d104 100644
--- a/third_party/blink/renderer/core/paint/svg_object_painter.cc
+++ b/third_party/blink/renderer/core/paint/svg_object_painter.cc
@@ -85,7 +85,7 @@
                                    ? To<Longhand>(GetCSSPropertyFill())
                                    : To<Longhand>(GetCSSPropertyStroke());
     Color flag_color = style.VisitedDependentColor(property);
-    flag_color.SetAlpha(flag_color.FloatAlpha() * alpha);
+    flag_color.SetAlpha(flag_color.Alpha() * alpha);
     flags.setColor(flag_color.toSkColor4f());
     flags.setShader(nullptr);
     ApplyColorInterpolation(style, flags);
diff --git a/third_party/blink/renderer/core/style/computed_style.cc b/third_party/blink/renderer/core/style/computed_style.cc
index d84e15da..0a599e8 100644
--- a/third_party/blink/renderer/core/style/computed_style.cc
+++ b/third_party/blink/renderer/core/style/computed_style.cc
@@ -2261,8 +2261,7 @@
   // TODO(dazabani@igalia.com) improve behaviour where unvisited is currentColor
   return Color::FromColorSpace(visited_color.GetColorSpace(),
                                visited_color.Param0(), visited_color.Param1(),
-                               visited_color.Param2(),
-                               unvisited_color.FloatAlpha());
+                               visited_color.Param2(), unvisited_color.Alpha());
 }
 
 blink::Color ComputedStyle::ResolvedColor(const StyleColor& color,
diff --git a/third_party/blink/renderer/core/svg/svg_stop_element.cc b/third_party/blink/renderer/core/svg/svg_stop_element.cc
index 7f6e4f48f..e49a0577 100644
--- a/third_party/blink/renderer/core/svg/svg_stop_element.cc
+++ b/third_party/blink/renderer/core/svg/svg_stop_element.cc
@@ -86,7 +86,7 @@
     return Color::kBlack;
 
   Color base_color = style->VisitedDependentColor(GetCSSPropertyStopColor());
-  base_color.SetAlpha(style->StopOpacity() * base_color.FloatAlpha());
+  base_color.SetAlpha(style->StopOpacity() * base_color.Alpha());
   return base_color;
 }
 
diff --git a/third_party/blink/renderer/modules/canvas/canvas2d/canvas_style.h b/third_party/blink/renderer/modules/canvas/canvas2d/canvas_style.h
index b7a568e..7045619 100644
--- a/third_party/blink/renderer/modules/canvas/canvas2d/canvas_style.h
+++ b/third_party/blink/renderer/modules/canvas/canvas2d/canvas_style.h
@@ -122,7 +122,7 @@
   DCHECK(type_ == kColor);
   flags.setShader(nullptr);
   Color color = color_;
-  color.SetAlpha(color.FloatAlpha() * global_alpha);
+  color.SetAlpha(color.Alpha() * global_alpha);
   flags.setColor(color.toSkColor4f());
 }
 
diff --git a/third_party/blink/renderer/modules/webcodecs/video_encoder.cc b/third_party/blink/renderer/modules/webcodecs/video_encoder.cc
index e5db481..a624fc22 100644
--- a/third_party/blink/renderer/modules/webcodecs/video_encoder.cc
+++ b/third_party/blink/renderer/modules/webcodecs/video_encoder.cc
@@ -1196,6 +1196,11 @@
 
   if (first_output_after_configure_ || codec_desc.has_value() ||
       output_color_space != last_output_color_space_) {
+    if (active_config->codec == media::VideoCodec::kH264) {
+      DCHECK(active_config->options.avc.produce_annexb ||
+             codec_desc.has_value());
+    }
+
     first_output_after_configure_ = false;
 
     if (output_color_space != last_output_color_space_) {
diff --git a/third_party/blink/renderer/platform/graphics/canvas_resource_provider.h b/third_party/blink/renderer/platform/graphics/canvas_resource_provider.h
index f7ec471e..37742d7 100644
--- a/third_party/blink/renderer/platform/graphics/canvas_resource_provider.h
+++ b/third_party/blink/renderer/platform/graphics/canvas_resource_provider.h
@@ -19,6 +19,7 @@
 #include "third_party/blink/renderer/platform/wtf/thread_specific.h"
 #include "third_party/blink/renderer/platform/wtf/vector.h"
 #include "third_party/skia/include/core/SkSurface.h"
+#include "third_party/skia/include/gpu/GrTypes.h"
 
 class GrDirectContext;
 
diff --git a/third_party/blink/renderer/platform/graphics/color.h b/third_party/blink/renderer/platform/graphics/color.h
index 38afa98..0409f3e1 100644
--- a/third_party/blink/renderer/platform/graphics/color.h
+++ b/third_party/blink/renderer/platform/graphics/color.h
@@ -271,7 +271,7 @@
   float Param0() const { return param0_; }
   float Param1() const { return param1_; }
   float Param2() const { return param2_; }
-  float FloatAlpha() const { return alpha_; }
+  float Alpha() const { return alpha_; }
 
   void SetAlpha(float alpha) { alpha_ = alpha; }
 
diff --git a/third_party/blink/renderer/platform/graphics/filters/fe_drop_shadow.cc b/third_party/blink/renderer/platform/graphics/filters/fe_drop_shadow.cc
index fe303a3..ff8b2f0 100644
--- a/third_party/blink/renderer/platform/graphics/filters/fe_drop_shadow.cc
+++ b/third_party/blink/renderer/platform/graphics/filters/fe_drop_shadow.cc
@@ -71,7 +71,7 @@
   float std_x = GetFilter()->ApplyHorizontalScale(std_x_);
   float std_y = GetFilter()->ApplyVerticalScale(std_y_);
   Color drop_shadow_color = shadow_color_;
-  drop_shadow_color.SetAlpha(shadow_opacity_ * drop_shadow_color.FloatAlpha());
+  drop_shadow_color.SetAlpha(shadow_opacity_ * drop_shadow_color.Alpha());
   drop_shadow_color =
       AdaptColorToOperatingInterpolationSpace(drop_shadow_color);
   absl::optional<PaintFilter::CropRect> crop_rect = GetCropRect();
diff --git a/third_party/blink/renderer/platform/runtime_enabled_features.json5 b/third_party/blink/renderer/platform/runtime_enabled_features.json5
index 238c4fd6..65801c6 100644
--- a/third_party/blink/renderer/platform/runtime_enabled_features.json5
+++ b/third_party/blink/renderer/platform/runtime_enabled_features.json5
@@ -899,11 +899,6 @@
       base_feature: "none",
     },
     {
-      // http://crbug.com/1276900
-      name: "CSSInitialLetter",
-      status: "stable",
-    },
-    {
       name: "CSSInitialPseudo",
       status: "experimental",
       base_feature: "none",
diff --git a/third_party/blink/renderer/platform/widget/widget_base.cc b/third_party/blink/renderer/platform/widget/widget_base.cc
index 7b92b2c2..637732a 100644
--- a/third_party/blink/renderer/platform/widget/widget_base.cc
+++ b/third_party/blink/renderer/platform/widget/widget_base.cc
@@ -9,7 +9,6 @@
 #include "base/metrics/histogram_macros.h"
 #include "base/ranges/algorithm.h"
 #include "base/task/single_thread_task_runner.h"
-#include "base/timer/elapsed_timer.h"
 #include "build/build_config.h"
 #include "cc/animation/animation_host.h"
 #include "cc/animation/animation_id_provider.h"
@@ -408,7 +407,6 @@
   //   See also:
   //   https://docs.google.com/document/d/1G_fR1D_0c1yke8CqDMddoKrDGr3gy5t_ImEH4hKNIII/edit#
 
-  base::ElapsedTimer update_timer;
   VisualProperties visual_properties = visual_properties_from_browser;
   auto& screen_info = visual_properties.screen_infos.mutable_current();
 
@@ -437,8 +435,6 @@
                              screen_info.device_scale_factor));
 
   client_->UpdateVisualProperties(visual_properties);
-
-  LayerTreeHost()->IncrementVisualUpdateDuration(update_timer.Elapsed());
 }
 
 void WidgetBase::UpdateScreenRects(const gfx::Rect& widget_screen_rect,
@@ -880,7 +876,6 @@
 }
 
 void WidgetBase::UpdateVisualState() {
-  base::ElapsedTimer update_timer;
   // When recording main frame metrics set the lifecycle reason to
   // kBeginMainFrame, because this is the calller of UpdateLifecycle
   // for the main frame. Otherwise, set the reason to kTests, which is
@@ -891,7 +886,6 @@
           : DocumentUpdateReason::kTest;
   client_->UpdateLifecycle(WebLifecycleUpdate::kAll, lifecycle_reason);
   client_->SetSuppressFrameRequestsWorkaroundFor704763Only(false);
-  LayerTreeHost()->IncrementVisualUpdateDuration(update_timer.Elapsed());
 }
 
 void WidgetBase::BeginMainFrame(base::TimeTicks frame_time) {
diff --git a/third_party/blink/tools/blinkpy/web_tests/port/base.py b/third_party/blink/tools/blinkpy/web_tests/port/base.py
index 97e6ff4..0f06ebe 100644
--- a/third_party/blink/tools/blinkpy/web_tests/port/base.py
+++ b/third_party/blink/tools/blinkpy/web_tests/port/base.py
@@ -70,9 +70,10 @@
 from blinkpy.web_tests.servers import apache_http
 from blinkpy.web_tests.servers import pywebsocket
 from blinkpy.web_tests.servers import wptserve
-from blinkpy.web_tests.skia_gold import blink_skia_gold_properties as sgp
 from blinkpy.web_tests.skia_gold import blink_skia_gold_session_manager as sgsm
 
+from skia_gold_common import skia_gold_properties as sgp
+
 _log = logging.getLogger(__name__)
 
 # Path relative to the build directory.
@@ -427,8 +428,7 @@
 
     def skia_gold_properties(self):
         if not self._skia_gold_properties:
-            self._skia_gold_properties = sgp.BlinkSkiaGoldProperties(
-                self._options)
+            self._skia_gold_properties = sgp.SkiaGoldProperties(self._options)
         return self._skia_gold_properties
 
     def skia_gold_session_manager(self):
diff --git a/third_party/blink/tools/blinkpy/web_tests/skia_gold/blink_skia_gold_properties.py b/third_party/blink/tools/blinkpy/web_tests/skia_gold/blink_skia_gold_properties.py
deleted file mode 100644
index 613efc80..0000000
--- a/third_party/blink/tools/blinkpy/web_tests/skia_gold/blink_skia_gold_properties.py
+++ /dev/null
@@ -1,27 +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.
-"""Blink implementation of //build/skia_gold_common/skia_gold_properties.py"""
-
-import subprocess
-import sys
-
-from blinkpy.common import path_finder
-from skia_gold_common import skia_gold_properties
-
-
-class BlinkSkiaGoldProperties(skia_gold_properties.SkiaGoldProperties):
-    @staticmethod
-    def _GetGitOriginMainHeadSha1():
-        try:
-            return subprocess.check_output(
-                ['git', 'rev-parse', 'origin/main'],
-                shell=_IsWin(),
-                cwd=path_finder.get_chromium_src_dir()).decode(
-                    'utf-8').strip()
-        except subprocess.CalledProcessError:
-            return None
-
-
-def _IsWin():
-    return sys.platform == 'win32'
diff --git a/third_party/blink/web_tests/VirtualTestSuites b/third_party/blink/web_tests/VirtualTestSuites
index 07556a53..656511b8 100644
--- a/third_party/blink/web_tests/VirtualTestSuites
+++ b/third_party/blink/web_tests/VirtualTestSuites
@@ -152,6 +152,7 @@
               "fast/backgrounds",
               "paint/background",
               "paint/invalidation/background",
+              "external/wpt/acid/acid2/reftest.html",
               "external/wpt/css/CSS2/backgrounds",
               "external/wpt/css/css-backgrounds",
               "external/wpt/css/css-break/background-image-000.html",
@@ -1263,7 +1264,7 @@
   {
     "prefix": "popover-hint-disabled",
     "platforms": ["Linux", "Mac", "Win"],
-    "bases": [],
+    "bases": ["external/wpt/html/semantics/popovers/"],
     "args": ["--enable-blink-features=HTMLPopoverAttribute",
       "--disable-blink-features=HTMLPopoverHint"],
     "expires": "Jan 1, 2024"
diff --git a/third_party/blink/web_tests/virtual/popover-hint-disabled/external/wpt/html/semantics/popovers/popover-css-properties.tentative-expected.txt b/third_party/blink/web_tests/virtual/popover-hint-disabled/external/wpt/html/semantics/popovers/popover-css-properties.tentative-expected.txt
new file mode 100644
index 0000000..af9613a
--- /dev/null
+++ b/third_party/blink/web_tests/virtual/popover-hint-disabled/external/wpt/html/semantics/popovers/popover-css-properties.tentative-expected.txt
@@ -0,0 +1,80 @@
+This is a testharness.js-based test.
+Found 76 tests; 10 PASS, 66 FAIL, 0 TIMEOUT, 0 NOTRUN.
+FAIL Property popover-show-delay value '0s' assert_true: popover-show-delay doesn't seem to be supported in the computed style expected true got false
+FAIL Property popover-show-delay value '0ms' assert_true: popover-show-delay doesn't seem to be supported in the computed style expected true got false
+FAIL Property popover-show-delay value '32s' assert_true: popover-show-delay doesn't seem to be supported in the computed style expected true got false
+FAIL Property popover-show-delay value '123ms' assert_true: popover-show-delay doesn't seem to be supported in the computed style expected true got false
+FAIL e.style['popover-show-delay'] = "0s" should set the property value assert_not_equals: property should be set got disallowed value ""
+FAIL e.style['popover-show-delay'] = "0ms" should set the property value assert_not_equals: property should be set got disallowed value ""
+FAIL e.style['popover-show-delay'] = "32s" should set the property value assert_not_equals: property should be set got disallowed value ""
+FAIL e.style['popover-show-delay'] = "123ms" should set the property value assert_not_equals: property should be set got disallowed value ""
+FAIL e.style['popover-show-delay'] = "inherit" should set the property value assert_not_equals: property should be set got disallowed value ""
+PASS e.style['popover-show-delay'] = "0" should not set the property value
+PASS e.style['popover-show-delay'] = "foo" should not set the property value
+PASS e.style['popover-show-delay'] = "-1s" should not set the property value
+PASS e.style['popover-show-delay'] = "none" should not set the property value
+PASS e.style['popover-show-delay'] = "auto" should not set the property value
+FAIL CSS Transitions: property <popover-show-delay> from [1s] to [2000ms] at (-1.5) should be [0s] assert_true: 'from' value should be supported expected true got false
+FAIL CSS Transitions: property <popover-show-delay> from [1s] to [2000ms] at (-0.3) should be [0.7s] assert_true: 'from' value should be supported expected true got false
+FAIL CSS Transitions: property <popover-show-delay> from [1s] to [2000ms] at (0) should be [1s] assert_true: 'from' value should be supported expected true got false
+FAIL CSS Transitions: property <popover-show-delay> from [1s] to [2000ms] at (0.5) should be [1.5s] assert_true: 'from' value should be supported expected true got false
+FAIL CSS Transitions: property <popover-show-delay> from [1s] to [2000ms] at (1) should be [2s] assert_true: 'from' value should be supported expected true got false
+FAIL CSS Transitions: property <popover-show-delay> from [1s] to [2000ms] at (1.5) should be [2.5s] assert_true: 'from' value should be supported expected true got false
+FAIL CSS Transitions with transition: all: property <popover-show-delay> from [1s] to [2000ms] at (-1.5) should be [0s] assert_true: 'from' value should be supported expected true got false
+FAIL CSS Transitions with transition: all: property <popover-show-delay> from [1s] to [2000ms] at (-0.3) should be [0.7s] assert_true: 'from' value should be supported expected true got false
+FAIL CSS Transitions with transition: all: property <popover-show-delay> from [1s] to [2000ms] at (0) should be [1s] assert_true: 'from' value should be supported expected true got false
+FAIL CSS Transitions with transition: all: property <popover-show-delay> from [1s] to [2000ms] at (0.5) should be [1.5s] assert_true: 'from' value should be supported expected true got false
+FAIL CSS Transitions with transition: all: property <popover-show-delay> from [1s] to [2000ms] at (1) should be [2s] assert_true: 'from' value should be supported expected true got false
+FAIL CSS Transitions with transition: all: property <popover-show-delay> from [1s] to [2000ms] at (1.5) should be [2.5s] assert_true: 'from' value should be supported expected true got false
+FAIL CSS Animations: property <popover-show-delay> from [1s] to [2000ms] at (-1.5) should be [0s] assert_true: 'from' value should be supported expected true got false
+FAIL CSS Animations: property <popover-show-delay> from [1s] to [2000ms] at (-0.3) should be [0.7s] assert_true: 'from' value should be supported expected true got false
+FAIL CSS Animations: property <popover-show-delay> from [1s] to [2000ms] at (0) should be [1s] assert_true: 'from' value should be supported expected true got false
+FAIL CSS Animations: property <popover-show-delay> from [1s] to [2000ms] at (0.5) should be [1.5s] assert_true: 'from' value should be supported expected true got false
+FAIL CSS Animations: property <popover-show-delay> from [1s] to [2000ms] at (1) should be [2s] assert_true: 'from' value should be supported expected true got false
+FAIL CSS Animations: property <popover-show-delay> from [1s] to [2000ms] at (1.5) should be [2.5s] assert_true: 'from' value should be supported expected true got false
+FAIL Web Animations: property <popover-show-delay> from [1s] to [2000ms] at (-1.5) should be [0s] assert_true: 'from' value should be supported expected true got false
+FAIL Web Animations: property <popover-show-delay> from [1s] to [2000ms] at (-0.3) should be [0.7s] assert_true: 'from' value should be supported expected true got false
+FAIL Web Animations: property <popover-show-delay> from [1s] to [2000ms] at (0) should be [1s] assert_true: 'from' value should be supported expected true got false
+FAIL Web Animations: property <popover-show-delay> from [1s] to [2000ms] at (0.5) should be [1.5s] assert_true: 'from' value should be supported expected true got false
+FAIL Web Animations: property <popover-show-delay> from [1s] to [2000ms] at (1) should be [2s] assert_true: 'from' value should be supported expected true got false
+FAIL Web Animations: property <popover-show-delay> from [1s] to [2000ms] at (1.5) should be [2.5s] assert_true: 'from' value should be supported expected true got false
+FAIL Property popover-hide-delay value '0s' assert_true: popover-hide-delay doesn't seem to be supported in the computed style expected true got false
+FAIL Property popover-hide-delay value '0ms' assert_true: popover-hide-delay doesn't seem to be supported in the computed style expected true got false
+FAIL Property popover-hide-delay value '32s' assert_true: popover-hide-delay doesn't seem to be supported in the computed style expected true got false
+FAIL Property popover-hide-delay value '123ms' assert_true: popover-hide-delay doesn't seem to be supported in the computed style expected true got false
+FAIL e.style['popover-hide-delay'] = "0s" should set the property value assert_not_equals: property should be set got disallowed value ""
+FAIL e.style['popover-hide-delay'] = "0ms" should set the property value assert_not_equals: property should be set got disallowed value ""
+FAIL e.style['popover-hide-delay'] = "32s" should set the property value assert_not_equals: property should be set got disallowed value ""
+FAIL e.style['popover-hide-delay'] = "123ms" should set the property value assert_not_equals: property should be set got disallowed value ""
+FAIL e.style['popover-hide-delay'] = "inherit" should set the property value assert_not_equals: property should be set got disallowed value ""
+PASS e.style['popover-hide-delay'] = "0" should not set the property value
+PASS e.style['popover-hide-delay'] = "foo" should not set the property value
+PASS e.style['popover-hide-delay'] = "-1s" should not set the property value
+PASS e.style['popover-hide-delay'] = "none" should not set the property value
+PASS e.style['popover-hide-delay'] = "auto" should not set the property value
+FAIL CSS Transitions: property <popover-hide-delay> from [1s] to [2000ms] at (-1.5) should be [0s] assert_true: 'from' value should be supported expected true got false
+FAIL CSS Transitions: property <popover-hide-delay> from [1s] to [2000ms] at (-0.3) should be [0.7s] assert_true: 'from' value should be supported expected true got false
+FAIL CSS Transitions: property <popover-hide-delay> from [1s] to [2000ms] at (0) should be [1s] assert_true: 'from' value should be supported expected true got false
+FAIL CSS Transitions: property <popover-hide-delay> from [1s] to [2000ms] at (0.5) should be [1.5s] assert_true: 'from' value should be supported expected true got false
+FAIL CSS Transitions: property <popover-hide-delay> from [1s] to [2000ms] at (1) should be [2s] assert_true: 'from' value should be supported expected true got false
+FAIL CSS Transitions: property <popover-hide-delay> from [1s] to [2000ms] at (1.5) should be [2.5s] assert_true: 'from' value should be supported expected true got false
+FAIL CSS Transitions with transition: all: property <popover-hide-delay> from [1s] to [2000ms] at (-1.5) should be [0s] assert_true: 'from' value should be supported expected true got false
+FAIL CSS Transitions with transition: all: property <popover-hide-delay> from [1s] to [2000ms] at (-0.3) should be [0.7s] assert_true: 'from' value should be supported expected true got false
+FAIL CSS Transitions with transition: all: property <popover-hide-delay> from [1s] to [2000ms] at (0) should be [1s] assert_true: 'from' value should be supported expected true got false
+FAIL CSS Transitions with transition: all: property <popover-hide-delay> from [1s] to [2000ms] at (0.5) should be [1.5s] assert_true: 'from' value should be supported expected true got false
+FAIL CSS Transitions with transition: all: property <popover-hide-delay> from [1s] to [2000ms] at (1) should be [2s] assert_true: 'from' value should be supported expected true got false
+FAIL CSS Transitions with transition: all: property <popover-hide-delay> from [1s] to [2000ms] at (1.5) should be [2.5s] assert_true: 'from' value should be supported expected true got false
+FAIL CSS Animations: property <popover-hide-delay> from [1s] to [2000ms] at (-1.5) should be [0s] assert_true: 'from' value should be supported expected true got false
+FAIL CSS Animations: property <popover-hide-delay> from [1s] to [2000ms] at (-0.3) should be [0.7s] assert_true: 'from' value should be supported expected true got false
+FAIL CSS Animations: property <popover-hide-delay> from [1s] to [2000ms] at (0) should be [1s] assert_true: 'from' value should be supported expected true got false
+FAIL CSS Animations: property <popover-hide-delay> from [1s] to [2000ms] at (0.5) should be [1.5s] assert_true: 'from' value should be supported expected true got false
+FAIL CSS Animations: property <popover-hide-delay> from [1s] to [2000ms] at (1) should be [2s] assert_true: 'from' value should be supported expected true got false
+FAIL CSS Animations: property <popover-hide-delay> from [1s] to [2000ms] at (1.5) should be [2.5s] assert_true: 'from' value should be supported expected true got false
+FAIL Web Animations: property <popover-hide-delay> from [1s] to [2000ms] at (-1.5) should be [0s] assert_true: 'from' value should be supported expected true got false
+FAIL Web Animations: property <popover-hide-delay> from [1s] to [2000ms] at (-0.3) should be [0.7s] assert_true: 'from' value should be supported expected true got false
+FAIL Web Animations: property <popover-hide-delay> from [1s] to [2000ms] at (0) should be [1s] assert_true: 'from' value should be supported expected true got false
+FAIL Web Animations: property <popover-hide-delay> from [1s] to [2000ms] at (0.5) should be [1.5s] assert_true: 'from' value should be supported expected true got false
+FAIL Web Animations: property <popover-hide-delay> from [1s] to [2000ms] at (1) should be [2s] assert_true: 'from' value should be supported expected true got false
+FAIL Web Animations: property <popover-hide-delay> from [1s] to [2000ms] at (1.5) should be [2.5s] assert_true: 'from' value should be supported expected true got false
+Harness: the test ran to completion.
+
diff --git a/third_party/blink/web_tests/virtual/popover-hint-disabled/external/wpt/html/semantics/popovers/popover-hover-hide.tentative-expected.txt b/third_party/blink/web_tests/virtual/popover-hint-disabled/external/wpt/html/semantics/popovers/popover-hover-hide.tentative-expected.txt
new file mode 100644
index 0000000..effa688
--- /dev/null
+++ b/third_party/blink/web_tests/virtual/popover-hint-disabled/external/wpt/html/semantics/popovers/popover-hover-hide.tentative-expected.txt
@@ -0,0 +1,7 @@
+This is a testharness.js-based test.
+FAIL The popover-hide-delay causes a popover to be hidden after a delay assert_false: expected false got true
+FAIL hovering the popover keeps it from being hidden assert_false: expected false got true
+FAIL hovering a popovertargetaction=hover invoking element keeps the popover from being hidden assert_false: expected false got true
+FAIL hovering a popovertargetaction=toggle invoking element keeps the popover from being hidden promise_test: Unhandled rejection with value: object "TypeError: Cannot read properties of undefined (reading 'slice')"
+Harness: the test ran to completion.
+
diff --git a/third_party/blink/web_tests/virtual/popover-hint-disabled/external/wpt/html/semantics/popovers/popover-target-action-hover.tentative-expected.txt b/third_party/blink/web_tests/virtual/popover-hint-disabled/external/wpt/html/semantics/popovers/popover-target-action-hover.tentative-expected.txt
new file mode 100644
index 0000000..1584d5a
--- /dev/null
+++ b/third_party/blink/web_tests/virtual/popover-hint-disabled/external/wpt/html/semantics/popovers/popover-target-action-hover.tentative-expected.txt
@@ -0,0 +1,58 @@
+This is a testharness.js-based test.
+Found 54 tests; 0 PASS, 54 FAIL, 0 TIMEOUT, 0 NOTRUN.
+FAIL popovertargetaction=hover shows a popover with popover=auto, invokerType=plain promise_test: Unhandled rejection with value: object "TypeError: Cannot read properties of undefined (reading 'slice')"
+FAIL popovertargetaction=hover should also allow click activation, for popover=auto, invokerType=plain promise_test: Unhandled rejection with value: object "TypeError: Cannot read properties of undefined (reading 'slice')"
+FAIL popovertargetaction=hover popover-show-delay is respected (popover=auto, invokerType=plain) promise_test: Unhandled rejection with value: object "TypeError: Cannot read properties of undefined (reading 'slice')"
+FAIL popovertargetaction=hover does nothing when popover is already showing (popover=auto, invokerType=plain) promise_test: Unhandled rejection with value: object "TypeError: Cannot read properties of undefined (reading 'slice')"
+FAIL popovertargetaction=hover does nothing when popover is moved out of the document (popover=auto, invokerType=plain) promise_test: Unhandled rejection with value: object "TypeError: Cannot read properties of undefined (reading 'slice')"
+FAIL popovertargetaction=hover does nothing when target changes (popover=auto, invokerType=plain) promise_test: Unhandled rejection with value: object "TypeError: Cannot read properties of undefined (reading 'slice')"
+FAIL popovertargetaction=hover shows a popover with popover=auto, invokerType=nested promise_test: Unhandled rejection with value: object "TypeError: Cannot read properties of undefined (reading 'slice')"
+FAIL popovertargetaction=hover should also allow click activation, for popover=auto, invokerType=nested promise_test: Unhandled rejection with value: object "TypeError: Cannot read properties of undefined (reading 'slice')"
+FAIL popovertargetaction=hover popover-show-delay is respected (popover=auto, invokerType=nested) promise_test: Unhandled rejection with value: object "TypeError: Cannot read properties of undefined (reading 'slice')"
+FAIL popovertargetaction=hover does nothing when popover is already showing (popover=auto, invokerType=nested) promise_test: Unhandled rejection with value: object "TypeError: Cannot read properties of undefined (reading 'slice')"
+FAIL popovertargetaction=hover does nothing when popover is moved out of the document (popover=auto, invokerType=nested) promise_test: Unhandled rejection with value: object "TypeError: Cannot read properties of undefined (reading 'slice')"
+FAIL popovertargetaction=hover does nothing when target changes (popover=auto, invokerType=nested) promise_test: Unhandled rejection with value: object "TypeError: Cannot read properties of undefined (reading 'slice')"
+FAIL popovertargetaction=hover shows a popover with popover=auto, invokerType=nested-offset promise_test: Unhandled rejection with value: object "TypeError: Cannot read properties of undefined (reading 'slice')"
+FAIL popovertargetaction=hover should also allow click activation, for popover=auto, invokerType=nested-offset promise_test: Unhandled rejection with value: object "TypeError: Cannot read properties of undefined (reading 'slice')"
+FAIL popovertargetaction=hover popover-show-delay is respected (popover=auto, invokerType=nested-offset) promise_test: Unhandled rejection with value: object "TypeError: Cannot read properties of undefined (reading 'slice')"
+FAIL popovertargetaction=hover does nothing when popover is already showing (popover=auto, invokerType=nested-offset) promise_test: Unhandled rejection with value: object "TypeError: Cannot read properties of undefined (reading 'slice')"
+FAIL popovertargetaction=hover does nothing when popover is moved out of the document (popover=auto, invokerType=nested-offset) promise_test: Unhandled rejection with value: object "TypeError: Cannot read properties of undefined (reading 'slice')"
+FAIL popovertargetaction=hover does nothing when target changes (popover=auto, invokerType=nested-offset) promise_test: Unhandled rejection with value: object "TypeError: Cannot read properties of undefined (reading 'slice')"
+FAIL popovertargetaction=hover shows a popover with popover=hint, invokerType=plain promise_test: Unhandled rejection with value: object "TypeError: Cannot read properties of undefined (reading 'slice')"
+FAIL popovertargetaction=hover should also allow click activation, for popover=hint, invokerType=plain promise_test: Unhandled rejection with value: object "TypeError: Cannot read properties of undefined (reading 'slice')"
+FAIL popovertargetaction=hover popover-show-delay is respected (popover=hint, invokerType=plain) promise_test: Unhandled rejection with value: object "TypeError: Cannot read properties of undefined (reading 'slice')"
+FAIL popovertargetaction=hover does nothing when popover is already showing (popover=hint, invokerType=plain) promise_test: Unhandled rejection with value: object "TypeError: Cannot read properties of undefined (reading 'slice')"
+FAIL popovertargetaction=hover does nothing when popover is moved out of the document (popover=hint, invokerType=plain) promise_test: Unhandled rejection with value: object "TypeError: Cannot read properties of undefined (reading 'slice')"
+FAIL popovertargetaction=hover does nothing when target changes (popover=hint, invokerType=plain) promise_test: Unhandled rejection with value: object "TypeError: Cannot read properties of undefined (reading 'slice')"
+FAIL popovertargetaction=hover shows a popover with popover=hint, invokerType=nested promise_test: Unhandled rejection with value: object "TypeError: Cannot read properties of undefined (reading 'slice')"
+FAIL popovertargetaction=hover should also allow click activation, for popover=hint, invokerType=nested promise_test: Unhandled rejection with value: object "TypeError: Cannot read properties of undefined (reading 'slice')"
+FAIL popovertargetaction=hover popover-show-delay is respected (popover=hint, invokerType=nested) promise_test: Unhandled rejection with value: object "TypeError: Cannot read properties of undefined (reading 'slice')"
+FAIL popovertargetaction=hover does nothing when popover is already showing (popover=hint, invokerType=nested) promise_test: Unhandled rejection with value: object "TypeError: Cannot read properties of undefined (reading 'slice')"
+FAIL popovertargetaction=hover does nothing when popover is moved out of the document (popover=hint, invokerType=nested) promise_test: Unhandled rejection with value: object "TypeError: Cannot read properties of undefined (reading 'slice')"
+FAIL popovertargetaction=hover does nothing when target changes (popover=hint, invokerType=nested) promise_test: Unhandled rejection with value: object "TypeError: Cannot read properties of undefined (reading 'slice')"
+FAIL popovertargetaction=hover shows a popover with popover=hint, invokerType=nested-offset promise_test: Unhandled rejection with value: object "TypeError: Cannot read properties of undefined (reading 'slice')"
+FAIL popovertargetaction=hover should also allow click activation, for popover=hint, invokerType=nested-offset promise_test: Unhandled rejection with value: object "TypeError: Cannot read properties of undefined (reading 'slice')"
+FAIL popovertargetaction=hover popover-show-delay is respected (popover=hint, invokerType=nested-offset) promise_test: Unhandled rejection with value: object "TypeError: Cannot read properties of undefined (reading 'slice')"
+FAIL popovertargetaction=hover does nothing when popover is already showing (popover=hint, invokerType=nested-offset) promise_test: Unhandled rejection with value: object "TypeError: Cannot read properties of undefined (reading 'slice')"
+FAIL popovertargetaction=hover does nothing when popover is moved out of the document (popover=hint, invokerType=nested-offset) promise_test: Unhandled rejection with value: object "TypeError: Cannot read properties of undefined (reading 'slice')"
+FAIL popovertargetaction=hover does nothing when target changes (popover=hint, invokerType=nested-offset) promise_test: Unhandled rejection with value: object "TypeError: Cannot read properties of undefined (reading 'slice')"
+FAIL popovertargetaction=hover shows a popover with popover=manual, invokerType=plain promise_test: Unhandled rejection with value: object "TypeError: Cannot read properties of undefined (reading 'slice')"
+FAIL popovertargetaction=hover should also allow click activation, for popover=manual, invokerType=plain promise_test: Unhandled rejection with value: object "TypeError: Cannot read properties of undefined (reading 'slice')"
+FAIL popovertargetaction=hover popover-show-delay is respected (popover=manual, invokerType=plain) promise_test: Unhandled rejection with value: object "TypeError: Cannot read properties of undefined (reading 'slice')"
+FAIL popovertargetaction=hover does nothing when popover is already showing (popover=manual, invokerType=plain) promise_test: Unhandled rejection with value: object "TypeError: Cannot read properties of undefined (reading 'slice')"
+FAIL popovertargetaction=hover does nothing when popover is moved out of the document (popover=manual, invokerType=plain) promise_test: Unhandled rejection with value: object "TypeError: Cannot read properties of undefined (reading 'slice')"
+FAIL popovertargetaction=hover does nothing when target changes (popover=manual, invokerType=plain) promise_test: Unhandled rejection with value: object "TypeError: Cannot read properties of undefined (reading 'slice')"
+FAIL popovertargetaction=hover shows a popover with popover=manual, invokerType=nested promise_test: Unhandled rejection with value: object "TypeError: Cannot read properties of undefined (reading 'slice')"
+FAIL popovertargetaction=hover should also allow click activation, for popover=manual, invokerType=nested promise_test: Unhandled rejection with value: object "TypeError: Cannot read properties of undefined (reading 'slice')"
+FAIL popovertargetaction=hover popover-show-delay is respected (popover=manual, invokerType=nested) promise_test: Unhandled rejection with value: object "TypeError: Cannot read properties of undefined (reading 'slice')"
+FAIL popovertargetaction=hover does nothing when popover is already showing (popover=manual, invokerType=nested) promise_test: Unhandled rejection with value: object "TypeError: Cannot read properties of undefined (reading 'slice')"
+FAIL popovertargetaction=hover does nothing when popover is moved out of the document (popover=manual, invokerType=nested) promise_test: Unhandled rejection with value: object "TypeError: Cannot read properties of undefined (reading 'slice')"
+FAIL popovertargetaction=hover does nothing when target changes (popover=manual, invokerType=nested) promise_test: Unhandled rejection with value: object "TypeError: Cannot read properties of undefined (reading 'slice')"
+FAIL popovertargetaction=hover shows a popover with popover=manual, invokerType=nested-offset promise_test: Unhandled rejection with value: object "TypeError: Cannot read properties of undefined (reading 'slice')"
+FAIL popovertargetaction=hover should also allow click activation, for popover=manual, invokerType=nested-offset promise_test: Unhandled rejection with value: object "TypeError: Cannot read properties of undefined (reading 'slice')"
+FAIL popovertargetaction=hover popover-show-delay is respected (popover=manual, invokerType=nested-offset) promise_test: Unhandled rejection with value: object "TypeError: Cannot read properties of undefined (reading 'slice')"
+FAIL popovertargetaction=hover does nothing when popover is already showing (popover=manual, invokerType=nested-offset) promise_test: Unhandled rejection with value: object "TypeError: Cannot read properties of undefined (reading 'slice')"
+FAIL popovertargetaction=hover does nothing when popover is moved out of the document (popover=manual, invokerType=nested-offset) promise_test: Unhandled rejection with value: object "TypeError: Cannot read properties of undefined (reading 'slice')"
+FAIL popovertargetaction=hover does nothing when target changes (popover=manual, invokerType=nested-offset) promise_test: Unhandled rejection with value: object "TypeError: Cannot read properties of undefined (reading 'slice')"
+Harness: the test ran to completion.
+
diff --git a/third_party/blink/web_tests/virtual/popover-hint-disabled/external/wpt/html/semantics/popovers/popover-types-with-hints.tentative-expected.txt b/third_party/blink/web_tests/virtual/popover-hint-disabled/external/wpt/html/semantics/popovers/popover-types-with-hints.tentative-expected.txt
new file mode 100644
index 0000000..be96f20be
--- /dev/null
+++ b/third_party/blink/web_tests/virtual/popover-hint-disabled/external/wpt/html/semantics/popovers/popover-types-with-hints.tentative-expected.txt
@@ -0,0 +1,10 @@
+This is a testharness.js-based test.
+PASS manuals do not close popovers
+FAIL autos close hints but not manuals assert_equals: hint open state is incorrect expected false but got true
+FAIL hint is not closed by pre-existing auto assert_equals: popover3 open state is incorrect expected false but got true
+FAIL If a popover=hint is shown, it should hide any other open popover=hint popovers, including ancestral popovers. (You can't nest popover=hint) assert_false: expected false got true
+FAIL If a popover=auto is shown, it should hide any open popover=hint, including if the popover=hint is an ancestral popover of the popover=auto. (You can't nest a popover=auto inside a popover=hint) assert_false: expected false got true
+FAIL If you: a) show a popover=auto (call it D), then b) show a descendent popover=hint of D (call it T), then c) hide D, then T should be hidden. (A popover=hint can be nested inside a popover=auto) assert_false: expected false got true
+PASS If you: a) show a popover=auto (call it D), then b) show a non-descendent popover=hint of D (call it T), then c) hide D, then T should be left showing. (Non-nested popover=hint can stay open when unrelated popover=autos are hidden)
+Harness: the test ran to completion.
+
diff --git a/third_party/harfbuzz-ng/BUILD.gn b/third_party/harfbuzz-ng/BUILD.gn
index 216f825a..57bac5d 100644
--- a/third_party/harfbuzz-ng/BUILD.gn
+++ b/third_party/harfbuzz-ng/BUILD.gn
@@ -276,6 +276,7 @@
       "src/src/hb-subset-cff2.hh",
       "src/src/hb-subset-input.cc",
       "src/src/hb-subset-input.hh",
+      "src/src/hb-subset-plan-member-list.hh",
       "src/src/hb-subset-plan.cc",
       "src/src/hb-subset-plan.hh",
       "src/src/hb-subset-repacker.cc",
diff --git a/third_party/harfbuzz-ng/README.chromium b/third_party/harfbuzz-ng/README.chromium
index bdcdac5..548e859 100644
--- a/third_party/harfbuzz-ng/README.chromium
+++ b/third_party/harfbuzz-ng/README.chromium
@@ -1,10 +1,10 @@
 Name: harfbuzz-ng
 Short Name: harfbuzz-ng
 URL: http://harfbuzz.org
-Version: 7.1.0-101
-CPEPrefix: cpe:/a:harfbuzz_project:harfbuzz:7.1.0
-Date: 20230425
-Revision: 2175f5d050743317c563ec9414e0f83a47f7fbc4
+Version: 7.2.0-125
+CPEPrefix: cpe:/a:harfbuzz_project:harfbuzz:7.2.0
+Date: 20230508
+Revision: 8df5cdbcda495a582e72a7e2ce35d6106401edce
 Security Critical: yes
 License: MIT
 License File: src/COPYING
diff --git a/third_party/tflite/README.chromium b/third_party/tflite/README.chromium
index cb433e9e3..137efc4 100644
--- a/third_party/tflite/README.chromium
+++ b/third_party/tflite/README.chromium
@@ -1,8 +1,8 @@
 Name: TensorFlow Lite
 Short Name: tflite
 URL: https://github.com/tensorflow/tensorflow
-Version: 28399abb7f7381b5862ea5baf1ce6bd04f9486d1
-Date: 2023/05/01
+Version: 9becb72d9b656f48de2bf00094910189434bc21e
+Date: 2023/05/08
 License: Apache 2.0
 License File: LICENSE
 Security Critical: Yes
diff --git a/tools/clang/rewrite_templated_container_fields/RewriteTemplatedPtrFields.cpp b/tools/clang/rewrite_templated_container_fields/RewriteTemplatedPtrFields.cpp
index 796f6bd..4cf7bc8 100644
--- a/tools/clang/rewrite_templated_container_fields/RewriteTemplatedPtrFields.cpp
+++ b/tools/clang/rewrite_templated_container_fields/RewriteTemplatedPtrFields.cpp
@@ -736,6 +736,126 @@
   OutputHelper& output_helper_;
 };
 
+class ExprVisitor
+    : public clang::ast_matchers::internal::BoundNodesTreeBuilder::Visitor {
+ public:
+  void visitMatch(
+      const clang::ast_matchers::BoundNodes& BoundNodesView) override {
+    expr_ = BoundNodesView.getNodeAs<clang::Expr>("expr");
+  }
+  const clang::Expr* expr_;
+};
+
+const clang::Expr* getExpr(
+    clang::ast_matchers::internal::BoundNodesTreeBuilder& matches) {
+  ExprVisitor v;
+  matches.visitMatches(&v);
+  return v.expr_;
+}
+
+// The goal of this matcher is to handle all possible combinations of matching
+// expressions. This works by unpacking the expression nodes recursively (in any
+// order they appear in) until we reach the matching lhs_expr/rhs_expr. This
+// allows to handle cases like the following:
+// std::map<int, std::vector<S*>> member;
+// std::vector<S*>::iterator it = member.begin()->second;
+AST_MATCHER_P(clang::Expr,
+              expr_variations,
+              clang::ast_matchers::internal::Matcher<clang::Expr>,
+              InnerMatcher) {
+  auto iterator = cxxMemberCallExpr(
+      callee(functionDecl(
+          anyOf(hasName("begin"), hasName("cbegin"), hasName("rbegin"),
+                hasName("crbegin"), hasName("end"), hasName("cend"),
+                hasName("rend"), hasName("crend"), hasName("find"),
+                hasName("upper_bound"), hasName("lower_bound"),
+                hasName("equal_range"), hasName("emplace"), hasName("Get")))),
+      has(memberExpr(has(expr().bind("expr")))));
+
+  auto search_calls = callExpr(callee(functionDecl(matchesName("find"))),
+                               hasArgument(0, expr().bind("expr")));
+
+  auto unary_op = unaryOperator(has(expr().bind("expr")));
+
+  auto reversed_expr = callExpr(callee(functionDecl(hasName("base::Reversed"))),
+                                hasArgument(0, expr().bind("expr")));
+
+  auto bracket_op_call = cxxOperatorCallExpr(
+      has(declRefExpr(to(cxxMethodDecl(hasName("operator[]"))))),
+      has(expr(unless(declRefExpr(to(cxxMethodDecl(hasName("operator[]"))))))
+              .bind("expr")));
+
+  auto arrow_op_call = cxxOperatorCallExpr(
+      has(declRefExpr(to(cxxMethodDecl(hasName("operator->"))))),
+      has(expr(unless(declRefExpr(to(cxxMethodDecl(hasName("operator->"))))))
+              .bind("expr")));
+
+  auto star_op_call = cxxOperatorCallExpr(
+      has(declRefExpr(to(cxxMethodDecl(hasName("operator*"))))),
+      has(expr(unless(declRefExpr(to(cxxMethodDecl(hasName("operator*"))))))
+              .bind("expr")));
+
+  auto second_member =
+      memberExpr(member(hasName("second")), has(expr().bind("expr")));
+
+  auto items = {iterator,        search_calls,  unary_op,     reversed_expr,
+                bracket_op_call, arrow_op_call, star_op_call, second_member};
+  clang::ast_matchers::internal::BoundNodesTreeBuilder matches;
+  const clang::Expr* n = nullptr;
+  std::any_of(items.begin(), items.end(), [&](auto& item) {
+    if (item.matches(Node, Finder, &matches)) {
+      n = getExpr(matches);
+      return true;
+    }
+    return false;
+  });
+
+  if (n) {
+    auto matcher = expr_variations(InnerMatcher);
+    return matcher.matches(*n, Finder, Builder);
+  }
+  return InnerMatcher.matches(Node, Finder, Builder);
+}
+
+class DeclVisitor
+    : public clang::ast_matchers::internal::BoundNodesTreeBuilder::Visitor {
+ public:
+  void visitMatch(
+      const clang::ast_matchers::BoundNodes& BoundNodesView) override {
+    decl_ = BoundNodesView.getNodeAs<clang::TypedefNameDecl>("decl");
+  }
+  const clang::TypedefNameDecl* decl_;
+};
+const clang::TypedefNameDecl* getDecl(
+    clang::ast_matchers::internal::BoundNodesTreeBuilder& matches) {
+  DeclVisitor v;
+  matches.visitMatches(&v);
+  return v.decl_;
+}
+// This allows us to unpack typedefs recursively until we reach the node
+// matching InnerMatcher.
+// Example:
+// using VECTOR = std::vector<S*>;
+// using MAP = std::map<int, VECTOR>;
+// MAP member; => this will lead to VECTOR being rewritten.
+AST_MATCHER_P(clang::TypedefNameDecl,
+              type_def_name_decl,
+              clang::ast_matchers::internal::Matcher<clang::TypedefNameDecl>,
+              InnerMatcher) {
+  auto type_def_matcher = typedefNameDecl(
+      hasDescendant(loc(qualType(hasDeclaration(
+          typedefNameDecl(unless(isExpansionInSystemHeader())).bind("decl"))))),
+      unless(isExpansionInSystemHeader()));
+
+  clang::ast_matchers::internal::BoundNodesTreeBuilder matches;
+  if (type_def_matcher.matches(Node, Finder, &matches)) {
+    const clang::TypedefNameDecl* n = getDecl(matches);
+    auto matcher = type_def_name_decl(InnerMatcher);
+    return matcher.matches(*n, Finder, Builder);
+  }
+  return InnerMatcher.matches(Node, Finder, Builder);
+}
+
 class VectorRawPtrRewriter {
  public:
   explicit VectorRawPtrRewriter(
@@ -782,16 +902,14 @@
 
     // Supports typedefs as well.
     auto lhs_type_loc =
-        anyOf(hasDescendant(loc(qualType(hasDeclaration(
-                  typedefNameDecl(hasDescendant(lhs_location),
-                                  unless(isExpansionInSystemHeader())))))),
+        anyOf(hasDescendant(loc(qualType(hasDeclaration(typedefNameDecl(
+                  type_def_name_decl(hasDescendant(lhs_location))))))),
               hasDescendant(lhs_location));
 
     // Supports typedefs as well.
     auto rhs_type_loc =
-        anyOf(hasDescendant(loc(qualType(hasDeclaration(
-                  typedefNameDecl(hasDescendant(rhs_location),
-                                  unless(isExpansionInSystemHeader())))))),
+        anyOf(hasDescendant(loc(qualType(hasDeclaration(typedefNameDecl(
+                  type_def_name_decl(hasDescendant(rhs_location))))))),
               hasDescendant(rhs_location));
 
     auto lhs_field =
@@ -801,44 +919,12 @@
         fieldDecl(hasExplicitFieldDecl(rhs_type_loc), unless(field_exclusions))
             .bind("rhs_field");
 
-    // To handle statements of the form:
-    // auto var = member/var/fct(); fct_call(var);
-    // We need to propagate the rewrite to fct_call signature.
-
-    auto lhs_type_def_name_decl = typedefNameDecl(
-        hasDescendant(lhs_location), unless(isExpansionInSystemHeader()));
-
-    auto lhs_type_def_var =
-        anyOf(varDecl(hasType(lhs_type_def_name_decl)),
-              varDecl(hasType(references(lhs_type_def_name_decl))),
-              varDecl(hasType(pointsTo(lhs_type_def_name_decl))));
-
-    auto rhs_type_def_name_decl = typedefNameDecl(
-        hasDescendant(rhs_location), unless(isExpansionInSystemHeader()));
-
-    auto rhs_type_def_var =
-        anyOf(varDecl(hasType(rhs_type_def_name_decl)),
-              varDecl(hasType(references(rhs_type_def_name_decl))),
-              varDecl(hasType(pointsTo(rhs_type_def_name_decl))));
-
-    auto v_decl_variations = varDecl(anyOf(
-        varDecl(hasType(class_temp_spec_decl)),
-        varDecl(hasType(references(class_temp_spec_decl))),
-        varDecl(hasType(pointsTo(class_temp_spec_decl))),
-        varDecl(hasType(decl(hasParent(class_temp_spec_decl)))),
-        varDecl(hasType(pointsTo(decl(hasParent(class_temp_spec_decl))))),
-        varDecl(hasType(references(decl(hasParent(class_temp_spec_decl)))))));
-
     auto lhs_var = anyOf(
-        varDecl(allOf(
-            anyOf(v_decl_variations, lhs_type_def_var),
-            hasDescendant(loc(qualType(autoType())).bind("lhs_auto_loc")))),
+        varDecl(hasDescendant(loc(qualType(autoType())).bind("lhs_auto_loc"))),
         varDecl(lhs_type_loc).bind("lhs_var"));
 
     auto rhs_var = anyOf(
-        varDecl(allOf(
-            anyOf(v_decl_variations, rhs_type_def_var),
-            hasDescendant(loc(qualType(autoType())).bind("rhs_auto_loc")))),
+        varDecl(hasDescendant(loc(qualType(autoType())).bind("rhs_auto_loc"))),
         varDecl(rhs_type_loc).bind("rhs_var"));
 
     auto lhs_param =
@@ -890,21 +976,6 @@
     auto lhs_move_call =
         callExpr(callee(functionDecl(ref_cref_move)), hasArgument(0, lhs_expr));
 
-    // TODO: check vector<S*> it = get()[0];
-    auto rhs_iterator_call = cxxMemberCallExpr(
-        callee(functionDecl(anyOf(hasName("begin"), hasName("cbegin"),
-                                  hasName("rbegin"), hasName("crbegin"),
-                                  hasName("end"), hasName("cend"),
-                                  hasName("rend"), hasName("crend")))),
-        has(memberExpr(has(rhs_expr))));
-
-    auto lhs_iterator_call = cxxMemberCallExpr(
-        callee(functionDecl(anyOf(hasName("begin"), hasName("cbegin"),
-                                  hasName("rbegin"), hasName("crbegin"),
-                                  hasName("end"), hasName("cend"),
-                                  hasName("rend"), hasName("crend")))),
-        has(memberExpr(has(lhs_expr))));
-
     auto rhs_cxx_temp_expr = cxxTemporaryObjectExpr(rhs_type_loc);
 
     auto lhs_cxx_temp_expr = cxxTemporaryObjectExpr(lhs_type_loc);
@@ -914,14 +985,10 @@
     // as callExpr argument. Examples: rhs_expr, &rhs_expr, *rhs_expr,
     // fct_call(),*fct_call(), &fct_call(), std::move(), .begin();
     auto rhs_expr_variations =
-        anyOf(rhs_expr, unaryOperator(has(rhs_expr)), rhs_call_expr,
-              rhs_move_call, rhs_iterator_call, rhs_cxx_temp_expr);
+        expr_variations(anyOf(rhs_expr, rhs_move_call, rhs_cxx_temp_expr));
 
-    // needed for ternary operator expr: (cond) ? true_expr : false_expr;
-    // true_expr => lhs; false_expr => rhs;
     auto lhs_expr_variations =
-        anyOf(lhs_expr, unaryOperator(has(lhs_expr)), lhs_call_expr,
-              lhs_move_call, lhs_iterator_call, lhs_cxx_temp_expr);
+        expr_variations(anyOf(lhs_expr, lhs_move_call, lhs_cxx_temp_expr));
 
     // rewrite affected expressions
     {
@@ -962,6 +1029,8 @@
       match_finder_.addMatcher(affected_op_call, &affected_ptr_expr_rewriter_);
     }
 
+    // needed for ternary operator expr: (cond) ? true_expr : false_expr;
+    // true_expr => lhs; false_expr => rhs;
     // creates a link between false_expr and true_expr of a ternary conditional
     // operator;
     // handles:
@@ -1128,6 +1197,18 @@
                 lhs_param)));
     match_finder_.addMatcher(make_unique_call, &potentail_nodes_);
 
+    // This creates a link between lhs and an argument passed to emplace.
+    // Example:
+    // std::map<int, std::vector<S*>> m;
+    // m.emplace(index, o.member);
+    // where member has type std::vector<S*>;
+    auto emplace_call_with_arg =
+        traverse(clang::TK_IgnoreUnlessSpelledInSource,
+                 cxxMemberCallExpr(callee(functionDecl(hasName("emplace"))),
+                                   has(memberExpr(has(rhs_expr_variations))),
+                                   hasAnyArgument(lhs_expr_variations)));
+    match_finder_.addMatcher(emplace_call_with_arg, &potentail_nodes_);
+
     // Handle BindOnce/BindRepeating;
     auto first_arg = hasParent(callExpr(hasArgument(
         0, anyOf(lambdaExpr().bind("lambda_expr"),
@@ -1142,6 +1223,20 @@
             forEachBindArg(expr(rhs_expr_variations, first_arg), lhs_param)));
     match_finder_.addMatcher(bind_args, &potentail_nodes_);
 
+    // This is useful to handle iteration over maps with vector as value.
+    // Example:
+    // std::vector<int, std::vector<S*>> m;
+    // for (auto& p : m){
+    // ...
+    // }
+    // This creates a link between p and m.
+    auto for_range_stmts = traverse(
+        clang::TK_IgnoreUnlessSpelledInSource,
+        cxxForRangeStmt(
+            hasLoopVariable(decl(lhs_var, unless(has(pointerTypeLoc())))),
+            hasRangeInit(rhs_expr_variations)));
+    match_finder_.addMatcher(for_range_stmts, &potentail_nodes_);
+
     // Map function declaration signature to function definition signature;
     // This is problematic in the case of callbacks defined in function.
     auto fct_decls_params = traverse(
diff --git a/tools/clang/rewrite_templated_container_fields/tests/assignment-tests-expected.cc b/tools/clang/rewrite_templated_container_fields/tests/assignment-tests-expected.cc
index 5522516f..3066a4f 100644
--- a/tools/clang/rewrite_templated_container_fields/tests/assignment-tests-expected.cc
+++ b/tools/clang/rewrite_templated_container_fields/tests/assignment-tests-expected.cc
@@ -1,6 +1,7 @@
 // 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 <map>
 #include <vector>
 
 #include "base/memory/raw_ptr.h"
@@ -10,12 +11,18 @@
 
 struct S {};
 
+// Expected rewrite: using VECTOR = std::vector<raw_ptr<S>>;
+using VECTOR = std::vector<raw_ptr<S>>;
+
 struct obj {
   // Expected rewrite: const std::vector<raw_ptr<S>>& get();
   const std::vector<raw_ptr<S>>& get() { return member; }
 
   // Expected rewrite: std::vector<raw_ptr<S>> member;
   std::vector<raw_ptr<S>> member;
+
+  typedef std::map<int, VECTOR> MAP;
+  MAP member2;
 };
 
 // Expected rewrite: std::vector<raw_ptr<S>> get_value();
@@ -33,10 +40,55 @@
   return {};
 }
 
+// Expected rewrite: void call_on_vector(std::vector<raw_ptr<S>>* v)
+void call_on_vector(std::vector<raw_ptr<S>>* v) {}
+
 void fct() {
   obj o;
 
   {
+    // Expected rewrite: std::map<int, std::vector<raw_ptr<S>>> m;
+    std::map<int, std::vector<raw_ptr<S>>> m;
+    m[0] = o.member;
+  }
+
+  {
+    // Expected rewrite: o.member2.emplace(0, std::vector<raw_ptr<S>>{});
+    o.member2.emplace(0, std::vector<raw_ptr<S>>{});
+  }
+
+  {
+    // Expected rewrite: std::vector<raw_ptr<S>> temp;
+    std::vector<raw_ptr<S>> temp;
+    temp = (*o.member2.begin()).second;
+  }
+
+  {
+    // Expected rewrite: std::map<int, std::vector<raw_ptr<S>>>::iterator it;
+    std::map<int, std::vector<raw_ptr<S>>>::iterator it;
+    it = o.member2.begin();
+  }
+
+  {
+    // Expected rewrite: std::map<int, std::vector<raw_ptr<S>>>::iterator it;
+    std::map<int, std::vector<raw_ptr<S>>>::iterator it;
+    it = o.member2.find(0);
+  }
+
+  {
+    for (auto& p : o.member2) {
+      // call_on_vector signature will be rewritten due to this call.
+      call_on_vector(&(p.second));
+    }
+  }
+
+  {
+    std::vector<raw_ptr<S>> temp;
+    // Expected rewrite: std::vector<raw_ptr<S>> temp;
+    temp = o.member2.find(0)->second;
+  }
+
+  {
     // create a link with a member to propagate the rewrite.
     // Expected rewrite: std::vector<raw_ptr<S>> a = o.member;
     std::vector<raw_ptr<S>> a = o.member;
diff --git a/tools/clang/rewrite_templated_container_fields/tests/assignment-tests-original.cc b/tools/clang/rewrite_templated_container_fields/tests/assignment-tests-original.cc
index ef137db1..061d987 100644
--- a/tools/clang/rewrite_templated_container_fields/tests/assignment-tests-original.cc
+++ b/tools/clang/rewrite_templated_container_fields/tests/assignment-tests-original.cc
@@ -1,6 +1,7 @@
 // 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 <map>
 #include <vector>
 
 #define EXPECT_EQ(x, y) x == y
@@ -8,12 +9,18 @@
 
 struct S {};
 
+// Expected rewrite: using VECTOR = std::vector<raw_ptr<S>>;
+using VECTOR = std::vector<S*>;
+
 struct obj {
   // Expected rewrite: const std::vector<raw_ptr<S>>& get();
   const std::vector<S*>& get() { return member; }
 
   // Expected rewrite: std::vector<raw_ptr<S>> member;
   std::vector<S*> member;
+
+  typedef std::map<int, VECTOR> MAP;
+  MAP member2;
 };
 
 // Expected rewrite: std::vector<raw_ptr<S>> get_value();
@@ -31,10 +38,55 @@
   return {};
 }
 
+// Expected rewrite: void call_on_vector(std::vector<raw_ptr<S>>* v)
+void call_on_vector(std::vector<S*>* v) {}
+
 void fct() {
   obj o;
 
   {
+    // Expected rewrite: std::map<int, std::vector<raw_ptr<S>>> m;
+    std::map<int, std::vector<S*>> m;
+    m[0] = o.member;
+  }
+
+  {
+    // Expected rewrite: o.member2.emplace(0, std::vector<raw_ptr<S>>{});
+    o.member2.emplace(0, std::vector<S*>{});
+  }
+
+  {
+    // Expected rewrite: std::vector<raw_ptr<S>> temp;
+    std::vector<S*> temp;
+    temp = (*o.member2.begin()).second;
+  }
+
+  {
+    // Expected rewrite: std::map<int, std::vector<raw_ptr<S>>>::iterator it;
+    std::map<int, std::vector<S*>>::iterator it;
+    it = o.member2.begin();
+  }
+
+  {
+    // Expected rewrite: std::map<int, std::vector<raw_ptr<S>>>::iterator it;
+    std::map<int, std::vector<S*>>::iterator it;
+    it = o.member2.find(0);
+  }
+
+  {
+    for (auto& p : o.member2) {
+      // call_on_vector signature will be rewritten due to this call.
+      call_on_vector(&(p.second));
+    }
+  }
+
+  {
+    std::vector<S*> temp;
+    // Expected rewrite: std::vector<raw_ptr<S>> temp;
+    temp = o.member2.find(0)->second;
+  }
+
+  {
     // create a link with a member to propagate the rewrite.
     // Expected rewrite: std::vector<raw_ptr<S>> a = o.member;
     std::vector<S*> a = o.member;
diff --git a/tools/metrics/histograms/enums.xml b/tools/metrics/histograms/enums.xml
index ecaadb8..2a9b7f8b 100644
--- a/tools/metrics/histograms/enums.xml
+++ b/tools/metrics/histograms/enums.xml
@@ -32739,6 +32739,9 @@
   <int value="1099" label="ShowDisplaySizeScreenEnabled"/>
   <int value="1100" label="AnonymousSearchEnabled"/>
   <int value="1101" label="LegacyTechReportAllowlist"/>
+  <int value="1102" label="ReportAppInventory"/>
+  <int value="1103" label="ReportAppUsage"/>
+  <int value="1104" label="ReportAppUsageCollectionRateMs"/>
 </enum>
 
 <enum name="EnterprisePoliciesSources">
@@ -88626,6 +88629,17 @@
   <int value="6" label="Failed to create directory"/>
 </enum>
 
+<enum name="QuotaError">
+  <int value="0" label="None"/>
+  <int value="1" label="Unknown Error"/>
+  <int value="2" label="Database Error"/>
+  <int value="3" label="Not Found Error"/>
+  <int value="4" label="Entry Exists Error"/>
+  <int value="5" label="File Operation Error"/>
+  <int value="6" label="Invalid Expiration Error"/>
+  <int value="7" label="Quota Exceeded Error"/>
+</enum>
+
 <enum name="QuotaOriginTypes">
   <obsolete>
     Removed from code July 2018.
diff --git a/tools/metrics/histograms/metadata/android/histograms.xml b/tools/metrics/histograms/metadata/android/histograms.xml
index fed4d99..b18eb801e 100644
--- a/tools/metrics/histograms/metadata/android/histograms.xml
+++ b/tools/metrics/histograms/metadata/android/histograms.xml
@@ -1706,16 +1706,6 @@
   </summary>
 </histogram>
 
-<histogram name="Android.Intent.IntentUriWithSelector" enum="Boolean"
-    expires_after="2023-06-18">
-  <owner>mthiesse@chromium.org</owner>
-  <owner>yfriedman@chromium.org</owner>
-  <summary>
-    When a site attempts to navigate an intent: or android-app: URI, records
-    whether or not the URI contained an intent selector.
-  </summary>
-</histogram>
-
 <histogram name="Android.Intent.MainFrameIntentLaunch"
     enum="MainFrameIntentLaunch" expires_after="2023-10-15">
   <owner>mthiesse@chromium.org</owner>
diff --git a/tools/metrics/histograms/metadata/compositing/histograms.xml b/tools/metrics/histograms/metadata/compositing/histograms.xml
index 93af534..3e687d8 100644
--- a/tools/metrics/histograms/metadata/compositing/histograms.xml
+++ b/tools/metrics/histograms/metadata/compositing/histograms.xml
@@ -661,31 +661,6 @@
   </summary>
 </histogram>
 
-<histogram
-    name="Compositing.Renderer.FirstSurfaceActivationUpdateDuration.{Surface}"
-    units="ms" expires_after="2023-04-28">
-  <owner>jonross@chromium.org</owner>
-  <owner>chrome-gpu-metrics@google.com</owner>
-  <summary>
-    The cumulative time spent performing visual updates for the {Surface}.
-
-    The time is measured in the Renderer. The result is reported in the Browser
-    when it is notified that the first Surface for a Renderer has been activated
-    by the GPU process. This is only reported for the first Surface that is
-    activated, and not for subsequent Surfaces.
-  </summary>
-  <token key="Surface">
-    <variant name="CurrentSurface"
-        summary="current surface which was activated. This time overlaps with
-                 when paint holding is deferring commits, and so is an upper
-                 bound"/>
-    <variant name="PreviousSurfaces"
-        summary="previous surfaces which were not activated. This time might
-                 overlap with when paint holding is deferring commits, and so
-                 is an upper bound"/>
-  </token>
-</histogram>
-
 <histogram name="Compositing.Renderer.LayersUpdateTime" units="microseconds"
     expires_after="2023-08-27">
   <owner>pdr@chromium.org</owner>
@@ -1242,6 +1217,8 @@
     This is a new implementation of the older
     Graphics.Smoothness.PercentDroppedFrames.AllInteractions metric.
 
+    See http://shortn/_ItBDdHoyCf (internal only) for more details.
+
     This histogram is of special interest to the chrome-analysis-team@. Do not
     change its semantics or retire it without talking to them first.
   </summary>
diff --git a/tools/metrics/histograms/metadata/media/histograms.xml b/tools/metrics/histograms/metadata/media/histograms.xml
index 6f6a77e..a38e623c0 100644
--- a/tools/metrics/histograms/metadata/media/histograms.xml
+++ b/tools/metrics/histograms/metadata/media/histograms.xml
@@ -2521,6 +2521,16 @@
   </summary>
 </histogram>
 
+<histogram name="Media.EME.MediaLicenseDatabaseOpenQuotaError"
+    enum="QuotaError" expires_after="2024-04-11">
+  <owner>vpasupathy@chromium.org</owner>
+  <owner>media-dev@chromium.org</owner>
+  <summary>
+    Quota errors reported while attempting to unsuccessfully open the Media
+    License Database.
+  </summary>
+</histogram>
+
 <histogram name="Media.EME.MediaLicenseDatabaseOpenSQLiteError"
     enum="SqliteLoggedResultCode" expires_after="2024-04-11">
   <owner>vpasupathy@chromium.org</owner>
diff --git a/tools/metrics/histograms/metadata/oobe/histograms.xml b/tools/metrics/histograms/metadata/oobe/histograms.xml
index 51b68a9f..60e05e5a 100644
--- a/tools/metrics/histograms/metadata/oobe/histograms.xml
+++ b/tools/metrics/histograms/metadata/oobe/histograms.xml
@@ -47,6 +47,7 @@
   <variant name="Enroll"/>
   <variant name="Family-link-notice"/>
   <variant name="Fingerprint-setup"/>
+  <variant name="Gaia-info"/>
   <variant name="Gaia-password-changed"/>
   <variant name="Gaia-signin"/>
   <variant name="Gesture-navigation"/>
@@ -149,6 +150,7 @@
   <variant name="Gaia-password-changed.CryptohomeError"/>
   <variant name="Gaia-password-changed.RecreateUser"/>
   <variant name="Gaia-signin.Back"/>
+  <variant name="Gaia-signin.BackChild"/>
   <variant name="Gaia-signin.Cancel"/>
   <variant name="Gaia-signin.EnterpriseEnroll"/>
   <variant name="Gaia-signin.StartConsumerKiosk"/>
diff --git a/tools/metrics/histograms/metadata/safe_browsing/histograms.xml b/tools/metrics/histograms/metadata/safe_browsing/histograms.xml
index 390b29c42..5440384c 100644
--- a/tools/metrics/histograms/metadata/safe_browsing/histograms.xml
+++ b/tools/metrics/histograms/metadata/safe_browsing/histograms.xml
@@ -2188,7 +2188,7 @@
 
 <histogram
     name="SafeBrowsing.TailoredSecurityConsented{Status}{PromptType}Outcome"
-    enum="SafeBrowsingTailoredSecurityOutcome" expires_after="2023-05-22">
+    enum="SafeBrowsingTailoredSecurityOutcome" expires_after="2023-11-22">
   <owner>jacastro@chromium.org</owner>
   <owner>chrome-counter-abuse-alerts@google.com</owner>
   <summary>
diff --git a/tools/perf/page_sets/data/credentials.json.sha1 b/tools/perf/page_sets/data/credentials.json.sha1
index 01e1378f..81f1437 100644
--- a/tools/perf/page_sets/data/credentials.json.sha1
+++ b/tools/perf/page_sets/data/credentials.json.sha1
@@ -1 +1 @@
-9aefd832456afa8800ccb1613a54028b6cb8cad4
\ No newline at end of file
+5bf0208a1c67fbce8d5d9007e9212ea569bfee0d
\ No newline at end of file
diff --git a/tools/visual_debugger/app.html b/tools/visual_debugger/app.html
index 463f5d2..b1d3c34 100644
--- a/tools/visual_debugger/app.html
+++ b/tools/visual_debugger/app.html
@@ -269,7 +269,7 @@
   <div class='section'>
     <!--We need to fix viewer size to avoid scroll position change when
       multiple displays are present. crbug.com/1358526-->
-    <div style="height:4000px;  border: 1px dotted gray;">
+    <div style="height:4000px;  border: 1px dotted gray; background-color: #f0f0f0">
         <canvas id='canvas' style="top :0px"></canvas>
     </div>
   </div>
diff --git a/tools/visual_debugger/filter-ui.js b/tools/visual_debugger/filter-ui.js
index 1917c80..d4f5a80 100644
--- a/tools/visual_debugger/filter-ui.js
+++ b/tools/visual_debugger/filter-ui.js
@@ -433,6 +433,11 @@
 
 const defaultFilters = [
     {
+      selector_: { filename: "", func: "", anno: "frame.render_pass.meta" },
+      action_: { skipDraw: false },
+      enabled_: false
+    },
+    {
       selector_: { filename: "", func: "", anno: "frame.render_pass.quad" },
       action_: { skipDraw: false, color: '#000000', alpha: "10" },
       enabled_: true
@@ -443,6 +448,15 @@
       enabled_: true
     },
     {
+      selector_: {
+        filename: "",
+        func: "",
+        anno: "frame.render_pass.output_rect",
+      },
+      action_: { skipDraw: false },
+      enabled_: false,
+    },
+    {
       selector_: { filename: "", func: "", anno: "overlay.selected.rect" },
       action_: { skipDraw: false, color: '#22FF22', alpha: "20" },
       enabled_: true
diff --git a/tools/visual_debugger/frame.js b/tools/visual_debugger/frame.js
index d7700face..26703bc 100644
--- a/tools/visual_debugger/frame.js
+++ b/tools/visual_debugger/frame.js
@@ -137,7 +137,7 @@
       (this.submissionCount() - 1);
   }
 
-  updateCanvasSize(canvas, scale, orientationDeg) {
+  updateCanvasSize(canvas, context, scale, orientationDeg) {
     // Swap canvas width/height for 90 or 270 deg rotations
     if (orientationDeg === 90 || orientationDeg === 270) {
       canvas.width = this.size_.height * scale;
@@ -153,6 +153,17 @@
     const padding = 20;
     canvas.width += padding * 2;
     canvas.height += padding * 2;
+
+    // Fill the actual frame bounds to an opaque color.
+    context.save();
+    context.fillStyle = "white";
+    context.fillRect(
+      padding,
+      padding,
+      canvas.width - padding * 2,
+      canvas.height - padding * 2
+    );
+    context.restore();
   }
 
   getFilter(source_index) {
@@ -390,7 +401,10 @@
   redrawCurrentFrame_() {
     const frame = this.getCurrentFrame();
     if (!frame) return;
-    frame.updateCanvasSize(this.canvas_, this.viewScale, this.viewOrientation);
+    frame.updateCanvasSize(this.canvas_,
+                           this.drawContext_,
+                           this.viewScale,
+                           this.viewOrientation);
     frame.draw(this.canvas_,
                this.drawContext_,
                this.viewScale,
diff --git a/ui/accessibility/ax_range.h b/ui/accessibility/ax_range.h
index 0562fe0..12e6f58 100644
--- a/ui/accessibility/ax_range.h
+++ b/ui/accessibility/ax_range.h
@@ -400,10 +400,15 @@
         break;
       }
 
+      ax::mojom::Role prev_role = start->GetAnchor()->GetRole();
       start = start->CreateNextLeafTextPosition();
+      // We should not mark `cross_paragraph_boundary` as true if the start
+      // anchor is a `kListMarker` since there should be no newline added
+      // by default after the `kListMarker` node.
       if (concatenation_behavior ==
               AXTextConcatenationBehavior::kWithParagraphBreaks &&
-          !crossed_paragraph_boundary && !is_first_non_whitespace_leaf) {
+          !crossed_paragraph_boundary && !is_first_non_whitespace_leaf &&
+          prev_role != ax::mojom::Role::kListMarker) {
         crossed_paragraph_boundary = start->AtStartOfParagraph();
       }
     }
diff --git a/ui/accessibility/ax_range_unittest.cc b/ui/accessibility/ax_range_unittest.cc
index 78b2de4..1f6b1584 100644
--- a/ui/accessibility/ax_range_unittest.cc
+++ b/ui/accessibility/ax_range_unittest.cc
@@ -20,6 +20,7 @@
 #include "ui/accessibility/ax_tree_update.h"
 #include "ui/accessibility/single_ax_tree_manager.h"
 #include "ui/accessibility/test_ax_node_helper.h"
+#include "ui/accessibility/test_ax_tree_update.h"
 
 namespace ui {
 
@@ -822,6 +823,73 @@
   TestRangeIterator(entire_test_backward_range);
 }
 
+TEST_F(AXRangeTest, GetTextWithContainersInsideListItems) {
+  // Testing 3 scenarios for list item accessible name:
+  // 1. <div> inside list item
+  // 2. Nested <div> inside list item
+  // 3. Forced line break inside list item.
+  TestAXTreeUpdate initial_state(std::string(R"HTML(
+    ++1 kRootWebArea
+    ++++2 kList boolAttribute=kIsLineBreakingObject,true
+    ++++++3 kListItem boolAttribute=kIsLineBreakingObject,true
+    ++++++++4 kListMarker intAttribute=kNameFrom,4
+    ++++++++++5 kStaticText state=kIgnored
+    ++++++++6 kGenericContainer boolAttribute=kIsLineBreakingObject,true
+    ++++++++++7 kStaticText name="hello"
+    ++++++++++++8 kInlineTextBox name="hello"
+    ++++++9 kListItem boolAttribute=kIsLineBreakingObject,true
+    ++++++++10 kListMarker intAttribute=kNameFrom,4
+    ++++++++++11 kStaticText state=kIgnored
+    ++++++++12 kGenericContainer boolAttribute=kIsLineBreakingObject,true
+    ++++++++++13 kGenericContainer boolAttribute=kIsLineBreakingObject,true
+    ++++++++++++14 kStaticText name="world"
+    ++++++++++++++15 kInlineTextBox name="world"
+     ++++++16 kListItem boolAttribute=kIsLineBreakingObject,true
+    ++++++++17 kListMarker intAttribute=kNameFrom,4
+    ++++++++++18 kStaticText state=kIgnored
+    ++++++++19 kLineBreak intAttribute=kNameFrom,4 boolAttribute=kIsLineBreakingObject,true
+    ++++++++++20 kInlineTextBox intAttribute=kNameFrom,4 boolAttribute=kIsLineBreakingObject,true
+    ++++++++21 kStaticText name="good"
+    ++++++++++22 kInlineTextBox name="good"
+  )HTML"));
+
+  initial_state.nodes[3].SetName("1. ");
+  initial_state.nodes[4].SetName("1. ");
+
+  initial_state.nodes[9].SetName("2. ");
+  initial_state.nodes[10].SetName("2. ");
+
+  initial_state.nodes[16].SetName("3. ");
+  initial_state.nodes[17].SetName("3. ");
+
+  SetTree(std::make_unique<AXTree>(initial_state));
+
+  AXNode* list_marker = ax_tree()->GetFromId(4);
+  AXNode* inline_tb = ax_tree()->GetFromId(22);
+
+  TestPositionInstance start =
+      CreateTextPosition(list_marker->data(), 0 /* text_offset */,
+                         ax::mojom::TextAffinity::kDownstream);
+  TestPositionInstance end =
+      CreateTextPosition(inline_tb->data(), 4 /* text_offset */,
+                         ax::mojom::TextAffinity::kDownstream);
+  TestPositionRange forward_range(start->Clone(), end->Clone());
+  std::u16string part1 = u"1. hello";
+  std::u16string part2 = u"2. world";
+  std::u16string part3 = u"3. ";
+  std::u16string part4 = u"good";
+  std::u16string text = part1.substr()
+                            .append(NEWLINE)
+                            .append(part2)
+                            .append(NEWLINE)
+                            .append(part3)
+                            .append(NEWLINE)
+                            .append(part4);
+  EXPECT_EQ(text, forward_range.GetText(
+                      AXTextConcatenationBehavior::kWithParagraphBreaks,
+                      AXEmbeddedObjectBehavior::kExposeCharacter));
+}
+
 TEST_F(AXRangeTest, GetTextWithWholeObjects) {
   // Create a range starting from the button object and ending at the last
   // character of the root, i.e. at the last character of the second line in the
diff --git a/ui/accessibility/platform/ax_platform_node_win.cc b/ui/accessibility/platform/ax_platform_node_win.cc
index 06818550..59522f6 100644
--- a/ui/accessibility/platform/ax_platform_node_win.cc
+++ b/ui/accessibility/platform/ax_platform_node_win.cc
@@ -8105,12 +8105,17 @@
     case UIA_TogglePatternId:
       // According to the CoreAAM spec [1], TogglePattern should be exposed for
       // all aria-checkable roles. However, the UIA documentation [2] specifies
-      // the RadioButton control does not implement IToggleProvider.
-      // [1] https://w3c.github.io/core-aam/#mapping_state-property_table
-      // [2]
-      // https://docs.microsoft.com/en-us/dotnet/framework/ui-automation/implementing-the-ui-automation-toggle-control-pattern
+      // the RadioButton control does not implement IToggleProvider. Also, the
+      // UIA documentation [3] and Accessibility Insights [4] seem to indicate
+      // that the Toggle control pattern should not be exposed when the
+      // ExpandCollapse control pattern  is already exposed for a button.
+      //
+      // [1]:https://w3c.github.io/core-aam/#mapping_state-property_table
+      // [2]:https://docs.microsoft.com/en-us/dotnet/framework/ui-automation/implementing-the-ui-automation-toggle-control-pattern
+      // [3]:https://learn.microsoft.com/en-us/windows/win32/winauto/uiauto-supportbuttoncontroltype#required-control-patterns
+      // [4]:https://github.com/microsoft/axe-windows/blob/main/src/Rules/Library/ButtonInvokeAndExpandeCollapsePatterns.cs
       if ((IsPlatformCheckable() || SupportsToggle(GetRole())) &&
-          !IsRadio(GetRole())) {
+          !IsRadio(GetRole()) && !GetData().SupportsExpandCollapse()) {
         return &PatternProvider<IToggleProvider>;
       }
       break;
diff --git a/ui/accessibility/platform/ax_platform_node_win_unittest.cc b/ui/accessibility/platform/ax_platform_node_win_unittest.cc
index bd10aa5..af30682 100644
--- a/ui/accessibility/platform/ax_platform_node_win_unittest.cc
+++ b/ui/accessibility/platform/ax_platform_node_win_unittest.cc
@@ -6151,12 +6151,13 @@
   constexpr AXNodeID tree_item_unchecked_id = 24;
   constexpr AXNodeID tree_item_id = 25;
   constexpr AXNodeID tab_id = 26;
+  constexpr AXNodeID toggle_button_with_popup_id = 27;
 
   AXTreeUpdate update;
   update.tree_data.tree_id = ui::AXTreeID::CreateNewAXTreeID();
   update.has_tree_data = true;
   update.root_id = root_id;
-  update.nodes.resize(26);
+  update.nodes.resize(27);
   update.nodes[0].id = root_id;
   update.nodes[0].role = ax::mojom::Role::kRootWebArea;
   update.nodes[0].child_ids = {text_field_with_combo_box_id,
@@ -6173,7 +6174,8 @@
                                tree_item_checked_id,
                                tree_item_unchecked_id,
                                tree_item_id,
-                               tab_id};
+                               tab_id,
+                               toggle_button_with_popup_id};
   update.nodes[1].id = text_field_with_combo_box_id;
   update.nodes[1].role = ax::mojom::Role::kTextFieldWithComboBox;
   update.nodes[1].AddState(ax::mojom::State::kEditable);
@@ -6250,6 +6252,11 @@
   update.nodes[24].role = ax::mojom::Role::kTreeItem;
   update.nodes[25].id = tab_id;
   update.nodes[25].role = ax::mojom::Role::kTab;
+  update.nodes[26].id = toggle_button_with_popup_id;
+  update.nodes[26].role = ax::mojom::Role::kToggleButton;
+  update.nodes[26].AddIntAttribute(
+      ax::mojom::IntAttribute::kHasPopup,
+      static_cast<int32_t>(ax::mojom::HasPopup::kTrue));
 
   Init(update);
 
@@ -6330,11 +6337,11 @@
   EXPECT_EQ(PatternSet({UIA_ScrollItemPatternId, UIA_InvokePatternId,
                         UIA_TextChildPatternId}),
             GetSupportedPatternsFromNodeId(button_without_value));
-  EXPECT_EQ(PatternSet({UIA_ScrollItemPatternId, UIA_TogglePatternId,
-                        UIA_ExpandCollapsePatternId, UIA_TextChildPatternId}),
+  EXPECT_EQ(PatternSet({UIA_ScrollItemPatternId, UIA_ExpandCollapsePatternId,
+                        UIA_TextChildPatternId}),
             GetSupportedPatternsFromNodeId(tree_item_checked_id));
-  EXPECT_EQ(PatternSet({UIA_ScrollItemPatternId, UIA_TogglePatternId,
-                        UIA_ExpandCollapsePatternId, UIA_TextChildPatternId}),
+  EXPECT_EQ(PatternSet({UIA_ScrollItemPatternId, UIA_ExpandCollapsePatternId,
+                        UIA_TextChildPatternId}),
             GetSupportedPatternsFromNodeId(tree_item_checked_id));
   EXPECT_EQ(PatternSet({UIA_ScrollItemPatternId, UIA_ExpandCollapsePatternId,
                         UIA_TextChildPatternId}),
@@ -6342,6 +6349,9 @@
   EXPECT_EQ(PatternSet({UIA_SelectionItemPatternId, UIA_ScrollItemPatternId,
                         UIA_TextChildPatternId}),
             GetSupportedPatternsFromNodeId(tab_id));
+  EXPECT_EQ(PatternSet({UIA_ScrollItemPatternId, UIA_ExpandCollapsePatternId,
+                        UIA_TextChildPatternId}),
+            GetSupportedPatternsFromNodeId(toggle_button_with_popup_id));
 }
 
 TEST_F(AXPlatformNodeWinTest, GetPatternProviderExpandCollapsePattern) {
diff --git a/ui/events/ash/event_rewriter_ash.cc b/ui/events/ash/event_rewriter_ash.cc
index 863b3ffe9..6c920ea 100644
--- a/ui/events/ash/event_rewriter_ash.cc
+++ b/ui/events/ash/event_rewriter_ash.cc
@@ -517,13 +517,20 @@
 
 // Records metrics for the Alt and Search based variants of keys in the
 // "six pack" eg. Home, End, PageUp, PageDown, Delete, Insert.
-void RecordSixPackEventRewrites(ui::EventType event_type,
+void RecordSixPackEventRewrites(EventRewriterAsh::Delegate* delegate,
+                                ui::EventType event_type,
                                 ui::KeyboardCode key_code,
                                 bool legacy_variant) {
   if (event_type != ET_KEY_PRESSED) {
     return;
   }
 
+  // The "Insert" key is omitted since the (Search+Shift+Backspace) rewrite is
+  // the only way to emit an "Insert" key event.
+  if (delegate && key_code != ui::VKEY_INSERT) {
+    delegate->RecordSixPackEventRewrite(key_code, /*alt_based=*/legacy_variant);
+  }
+
   if (!legacy_variant) {
     switch (key_code) {
       case ui::VKEY_DELETE:
@@ -1442,7 +1449,8 @@
           RewriteWithKeyboardRemappings(kNewInsertRemapping,
                                         std::size(kNewInsertRemapping),
                                         incoming, state, strict)) {
-        RecordSixPackEventRewrites(key_event.type(), state->key_code,
+        RecordSixPackEventRewrites(/*delegate=*/nullptr, key_event.type(),
+                                   state->key_code,
                                    /*legacy_variant=*/false);
         return;
       }
@@ -1460,7 +1468,7 @@
           RewriteWithKeyboardRemappings(kOldInsertRemapping,
                                         std::size(kOldInsertRemapping),
                                         incoming, state, strict)) {
-        RecordSixPackEventRewrites(key_event.type(), state->key_code,
+        RecordSixPackEventRewrites(delegate_, key_event.type(), state->key_code,
                                    /*legacy_variant=*/true);
         return;
       }
@@ -1487,7 +1495,7 @@
         RewriteWithKeyboardRemappings(kSixPackRemappings,
                                       std::size(kSixPackRemappings), incoming,
                                       state, strict)) {
-      RecordSixPackEventRewrites(key_event.type(), state->key_code,
+      RecordSixPackEventRewrites(delegate_, key_event.type(), state->key_code,
                                  /*legacy_variant=*/false);
       return;
     }
@@ -1516,7 +1524,7 @@
       if (RewriteWithKeyboardRemappings(kLegacySixPackRemappings,
                                         std::size(kLegacySixPackRemappings),
                                         incoming, state)) {
-        RecordSixPackEventRewrites(key_event.type(), state->key_code,
+        RecordSixPackEventRewrites(delegate_, key_event.type(), state->key_code,
                                    /*legacy_variant=*/true);
         return;
       }
@@ -1777,6 +1785,7 @@
         base::RecordAction(
             base::UserMetricsAction("AltClickMappedToRightClick"));
       }
+      delegate_->RecordEventRemappedToRightClick();
     } else {
       pressed_as_right_button_device_ids_.erase(mouse_event.source_device_id());
     }
diff --git a/ui/events/ash/event_rewriter_ash.h b/ui/events/ash/event_rewriter_ash.h
index 92738d7..05be55e7 100644
--- a/ui/events/ash/event_rewriter_ash.h
+++ b/ui/events/ash/event_rewriter_ash.h
@@ -132,6 +132,21 @@
     // is only sent once per user session, and this function returns true if
     // the notification was shown.
     virtual bool NotifyDeprecatedSixPackKeyRewrite(KeyboardCode key_code) = 0;
+
+    // Used to record when either Alt+Click or Search+Click is remapped to a
+    // right click event. The `kEventRemappedToRightClick` pref will be used
+    // to determine the default behavior for simulating a right click.
+    virtual void RecordEventRemappedToRightClick() = 0;
+
+    // Used to record Alt/Search based key event rewrites for Six Pack keys.
+    // `alt_based` tells us whether this "six pack" event was produced by an
+    // Alt or Search/Launcher based keyboard shortcut. The corresponding
+    // "six pack" key pref will be incremented when the Alt variant is used and
+    // decremented when the Search/Launcher variant is used. This information
+    // will determine the default behavior for rewriting a key event to a
+    // "six pack" key.
+    virtual void RecordSixPackEventRewrite(KeyboardCode key_code,
+                                           bool alt_based) = 0;
   };
 
   // Enum used to record the usage of the modifier keys on all devices. Do not
diff --git a/ui/webui/resources/images/BUILD.gn b/ui/webui/resources/images/BUILD.gn
index e6f6d39..36e4c2ee 100644
--- a/ui/webui/resources/images/BUILD.gn
+++ b/ui/webui/resources/images/BUILD.gn
@@ -53,6 +53,7 @@
       "icon_arrow_back.svg",
       "icon_arrow_dropdown.svg",
       "icon_bookmark.svg",
+      "icon_checkmark.svg",
       "icon_clear.svg",
       "icon_clock.svg",
       "icon_delete_gray.svg",
diff --git a/ui/webui/resources/images/icon_checkmark.svg b/ui/webui/resources/images/icon_checkmark.svg
new file mode 100644
index 0000000..3911c02
--- /dev/null
+++ b/ui/webui/resources/images/icon_checkmark.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 0 24 24" width="24"><path d="M0 0h24v24H0z" fill="none"/><path d="M9 16.2 4.8 12l-1.4 1.4L9 19 21 7l-1.4-1.4L9 16.2z"/></svg>
\ No newline at end of file