diff --git a/DEPS b/DEPS
index 0f257d45..e59ab7c9 100644
--- a/DEPS
+++ b/DEPS
@@ -299,11 +299,11 @@
   # 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': 'a164720807546097088f7f3aaafaa3abcb76ca7f',
+  'skia_revision': '658d34de2b2d7c931797a74a29bfa83406abcf3d',
   # 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': 'b39d2f97cf09c6a9e07b49329c8be08eb7ea1184',
+  'v8_revision': '13a25f01c72fb3e8f467e5ee7752136b86ed9614',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling ANGLE
   # and whatever else without interference from each other.
@@ -315,7 +315,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling PDFium
   # and whatever else without interference from each other.
-  'pdfium_revision': 'bad2c6c8f9549ac9bb0ec4e9e18e7cd2fc9f8d53',
+  'pdfium_revision': 'f8963b1c1903358c2cd000d02c4d7074f3836cfe',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling BoringSSL
   # and whatever else without interference from each other.
@@ -326,7 +326,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling Fuchsia sdk
   # and whatever else without interference from each other.
-  'fuchsia_version': 'version:11.20230221.0.1',
+  'fuchsia_version': 'version:11.20230221.2.1',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling google-toolbox-for-mac
   # and whatever else without interference from each other.
@@ -386,7 +386,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling devtools-frontend
   # and whatever else without interference from each other.
-  'devtools_frontend_revision': '628c4c4d103b1c6466f33f9a6ce497435307376a',
+  'devtools_frontend_revision': '16124603ab4d4529993ae499e9582185d332eaf6',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling libprotobuf-mutator
   # and whatever else without interference from each other.
@@ -450,7 +450,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling nearby
   # and whatever else without interference from each other.
-  'nearby_revision': 'a977a6bd7c85b417919111bf98c139be782ce6cc',
+  'nearby_revision': 'b59ed0b70f86c0dc1496c3145fa114bc38965f51',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling securemessage
   # and whatever else without interference from each other.
@@ -764,7 +764,7 @@
 
   'src/clank': {
     'url': 'https://chrome-internal.googlesource.com/clank/internal/apps.git' + '@' +
-    '05019a17e835afa456f4fd6e6accb0196d543c08',
+    '695223e1068af367f337a71c7b0705495c2cf146',
     'condition': 'checkout_android and checkout_src_internal',
   },
 
@@ -885,7 +885,7 @@
       'packages': [
         {
           'package': 'chromium/rts/model/windows-amd64',
-          'version': '2JIE-BRRHOhSxU-CGPDobqdw0siUDCZjIrNHZigVEKcC',
+          'version': 'lzCdoy2dU1rdSMf3suv0OwbFV_lJfXqYPfSZN9WnptAC',
         },
       ],
       'dep_type': 'cipd',
@@ -991,7 +991,7 @@
       'packages': [
           {
               'package': 'chromium/third_party/android_build_tools/aapt2',
-              'version': 'cbNG7g8Sinh-lsT8hWsU-RyXqLT_uh4jIb1fjCdhrzIC',
+              'version': '36NqCian2RIwuM6SFfizdUgKoXyZhy3q6pFfsws0szYC',
           },
       ],
       'condition': 'checkout_android',
@@ -1024,7 +1024,7 @@
       'packages': [
           {
                'package': 'chromium/third_party/android_build_tools/lint',
-               'version': '6R1spS-Itpxh7oLzwUptWcZyFwQeEH6aFwtkuTo8ROoC',
+               'version': 'MSpv-kFDDSPO0SY0dLdHegUJcJT1Yy8cL9r3vlAZ9vkC',
           },
       ],
       'condition': 'checkout_android',
@@ -1035,7 +1035,7 @@
       'packages': [
           {
                'package': 'chromium/third_party/android_build_tools/manifest_merger',
-               'version': 'EnlN2b-khJhe8B9hSfh7UxvglJXEwWDKaMm4ixhLYTMC',
+               'version': 'EbRaK62t9grqlZqL-JTd_zwM4t1u9fm1x4c2rLE0cqQC',
           },
       ],
       'condition': 'checkout_android',
@@ -1212,7 +1212,7 @@
     Var('chromium_git') + '/devtools/devtools-frontend' + '@' + Var('devtools_frontend_revision'),
 
   'src/third_party/devtools-frontend-internal': {
-      'url': 'https://chrome-internal.googlesource.com/devtools/devtools-internal.git' + '@' + 'aef22f715178521044bb96955acd32e8632b7f0d',
+      'url': 'https://chrome-internal.googlesource.com/devtools/devtools-internal.git' + '@' + '499ec7a60ca03f78c80ea8e25a2188cc9838611c',
     'condition': 'checkout_src_internal',
   },
 
@@ -1684,7 +1684,7 @@
   },
 
   'src/third_party/perfetto':
-    Var('android_git') + '/platform/external/perfetto.git' + '@' + 'e85c60ec550a5b4f09e5e8efcad6b355f5a7c52d',
+    Var('android_git') + '/platform/external/perfetto.git' + '@' + '55985e77ff4f3e023d321c7f7236e8cfe098e545',
 
   'src/third_party/perl': {
       'url': Var('chromium_git') + '/chromium/deps/perl.git' + '@' + '6f3e5028eb65d0b4c5fdd792106ac4c84eee1eb3',
@@ -1738,7 +1738,7 @@
       'packages': [
           {
               'package': 'chromium/third_party/r8',
-              'version': 'qGtBu6TtxyR5XNy4cmsslb7c946YtkZF5_QCjVP-wc8C',
+              'version': 'PwglNZFRNPkBBXdnY9NfrZFk2ULWDTRxhV9rl2kvkpUC',
           },
       ],
       'condition': 'checkout_android',
diff --git a/ash/capture_mode/capture_mode_camera_unittests.cc b/ash/capture_mode/capture_mode_camera_unittests.cc
index ac03eab..d79223ae 100644
--- a/ash/capture_mode/capture_mode_camera_unittests.cc
+++ b/ash/capture_mode/capture_mode_camera_unittests.cc
@@ -35,6 +35,7 @@
 #include "ash/strings/grit/ash_strings.h"
 #include "ash/style/ash_color_id.h"
 #include "ash/system/accessibility/autoclick_menu_bubble_controller.h"
+#include "ash/system/privacy/privacy_indicators_controller.h"
 #include "ash/system/privacy/privacy_indicators_tray_item_view.h"
 #include "ash/system/unified/unified_system_tray.h"
 #include "ash/test/ash_test_base.h"
@@ -63,6 +64,7 @@
 #include "ui/gfx/geometry/vector2d.h"
 #include "ui/gfx/image/image_unittest_util.h"
 #include "ui/gfx/paint_vector_icon.h"
+#include "ui/message_center/message_center.h"
 #include "ui/views/controls/image_view.h"
 #include "ui/views/view.h"
 #include "ui/views/widget/widget.h"
@@ -77,6 +79,12 @@
 constexpr char kDefaultCameraDisplayName[] = "Default Cam";
 constexpr char kDefaultCameraModelId[] = "0def:c000";
 
+// The app IDs used for the capture mode camera and microphone recording privacy
+// indicators.
+constexpr char kCameraPrivacyIndicatorId[] = "system-capture-mode-camera";
+constexpr char kMicrophonePrivacyIndicatorId[] =
+    "system-capture-mode-microphone";
+
 TestCaptureModeDelegate* GetTestDelegate() {
   return static_cast<TestCaptureModeDelegate*>(
       CaptureModeController::Get()->delegate_for_testing());
@@ -3864,17 +3872,8 @@
   // Update display size big enough to make sure when capture source is
   // `kWindow`, the selected window is not system tray.
   UpdateDisplay("1366x768");
-  // Enable autoclick bar.
-  auto* autoclick_controller = Shell::Get()->autoclick_controller();
-  autoclick_controller->SetEnabled(true, /*show_confirmation_dialog=*/false);
-  Shell::Get()
-      ->accessibility_controller()
-      ->GetFeature(A11yFeatureType::kAutoclick)
-      .SetEnabled(true);
 
-  views::Widget* autoclick_bubble_widget =
-      autoclick_controller->GetMenuBubbleControllerForTesting()
-          ->GetBubbleWidgetForTesting();
+  views::Widget* autoclick_bubble_widget = EnableAndGetAutoClickBubbleWidget();
   EXPECT_TRUE(autoclick_bubble_widget->IsVisible());
   const gfx::Rect origin_autoclick_bar_bounds =
       autoclick_bubble_widget->GetWindowBoundsInScreen();
@@ -4529,6 +4528,12 @@
   ui::ScopedAnimationDurationScaleMode animation_scale(
       ui::ScopedAnimationDurationScaleMode::NORMAL_DURATION);
 
+  auto* message_center = message_center::MessageCenter::Get();
+  auto camera_notification_id =
+      GetPrivacyIndicatorsNotificationId(kCameraPrivacyIndicatorId);
+  auto microphone_notification_id =
+      GetPrivacyIndicatorsNotificationId(kMicrophonePrivacyIndicatorId);
+
   // Initially the session doesn't show any camera preview since the camera
   // hasn't connected yet. There should be no privacy indicators.
   StartCaptureSession(CaptureModeSource::kFullscreen, CaptureModeType::kVideo);
@@ -4537,13 +4542,20 @@
   EXPECT_FALSE(camera_controller->camera_preview_widget());
   EXPECT_FALSE(IsCameraIndicatorIconVisible());
   EXPECT_FALSE(IsMicrophoneIndicatorIconVisible());
+  EXPECT_FALSE(message_center->FindNotificationById(camera_notification_id));
+  EXPECT_FALSE(
+      message_center->FindNotificationById(microphone_notification_id));
 
-  // Once the camera gets connected, the camera privacy indicator icon should
-  // show. No microphone yet (not until recording starts with audio).
+  // Once the camera gets connected, the camera privacy indicator
+  // icon/notification should show. No microphone yet (not until recording
+  // starts with audio).
   AddDefaultCamera();
   EXPECT_TRUE(camera_controller->camera_preview_widget());
   EXPECT_TRUE(IsCameraIndicatorIconVisible());
   EXPECT_FALSE(IsMicrophoneIndicatorIconVisible());
+  EXPECT_TRUE(message_center->FindNotificationById(camera_notification_id));
+  EXPECT_FALSE(
+      message_center->FindNotificationById(microphone_notification_id));
 
   // If the camera gets disconnected for some reason, the indicator should go
   // away, and come back once it reconnects again.
@@ -4553,16 +4565,29 @@
   base::RunLoop().RunUntilIdle();
   EXPECT_FALSE(IsCameraIndicatorIconVisible());
   EXPECT_FALSE(IsMicrophoneIndicatorIconVisible());
+  EXPECT_FALSE(message_center->FindNotificationById(camera_notification_id));
+  EXPECT_FALSE(
+      message_center->FindNotificationById(microphone_notification_id));
+
   AddDefaultCamera();
   EXPECT_TRUE(camera_controller->camera_preview_widget());
   EXPECT_TRUE(IsCameraIndicatorIconVisible());
   EXPECT_FALSE(IsMicrophoneIndicatorIconVisible());
+  EXPECT_TRUE(message_center->FindNotificationById(camera_notification_id));
+  EXPECT_FALSE(
+      message_center->FindNotificationById(microphone_notification_id));
 }
 
 TEST_F(CaptureModePrivacyIndicatorsTest, DuringRecordingPrivacyIndicators) {
   ui::ScopedAnimationDurationScaleMode animation_scale(
       ui::ScopedAnimationDurationScaleMode::NORMAL_DURATION);
 
+  auto* message_center = message_center::MessageCenter::Get();
+  auto camera_notification_id =
+      GetPrivacyIndicatorsNotificationId(kCameraPrivacyIndicatorId);
+  auto microphone_notification_id =
+      GetPrivacyIndicatorsNotificationId(kMicrophonePrivacyIndicatorId);
+
   // Even with the selected camera present, no indicators will show until the
   // capture session starts.
   auto* camera_controller = GetCameraController();
@@ -4571,21 +4596,31 @@
   EXPECT_FALSE(camera_controller->camera_preview_widget());
   EXPECT_FALSE(IsCameraIndicatorIconVisible());
   EXPECT_FALSE(IsMicrophoneIndicatorIconVisible());
+  EXPECT_FALSE(message_center->FindNotificationById(camera_notification_id));
+  EXPECT_FALSE(
+      message_center->FindNotificationById(microphone_notification_id));
 
   auto* capture_controller = StartCaptureSession(CaptureModeSource::kFullscreen,
                                                  CaptureModeType::kVideo);
   EXPECT_TRUE(camera_controller->camera_preview_widget());
   EXPECT_TRUE(IsCameraIndicatorIconVisible());
   EXPECT_FALSE(IsMicrophoneIndicatorIconVisible());
+  EXPECT_TRUE(message_center->FindNotificationById(camera_notification_id));
+  EXPECT_FALSE(
+      message_center->FindNotificationById(microphone_notification_id));
 
   // When the user selects audio recording, the idicators won't change.
   // Recording has to start first.
   capture_controller->EnableAudioRecording(true);
   EXPECT_FALSE(IsMicrophoneIndicatorIconVisible());
+  EXPECT_FALSE(
+      message_center->FindNotificationById(microphone_notification_id));
 
   StartRecordingFromSource(CaptureModeSource::kFullscreen);
   EXPECT_TRUE(IsCameraIndicatorIconVisible());
   EXPECT_TRUE(IsMicrophoneIndicatorIconVisible());
+  EXPECT_TRUE(message_center->FindNotificationById(camera_notification_id));
+  EXPECT_TRUE(message_center->FindNotificationById(microphone_notification_id));
 
   // Once recording ends, both indicators should disappear.
   capture_controller->EndVideoRecording(
@@ -4593,6 +4628,9 @@
   WaitForCaptureFileToBeSaved();
   EXPECT_FALSE(IsCameraIndicatorIconVisible());
   EXPECT_FALSE(IsMicrophoneIndicatorIconVisible());
+  EXPECT_FALSE(message_center->FindNotificationById(camera_notification_id));
+  EXPECT_FALSE(
+      message_center->FindNotificationById(microphone_notification_id));
 }
 
 }  // namespace ash
diff --git a/ash/capture_mode/capture_mode_controller.cc b/ash/capture_mode/capture_mode_controller.cc
index 8c9ddc8..c0d8971 100644
--- a/ash/capture_mode/capture_mode_controller.cc
+++ b/ash/capture_mode/capture_mode_controller.cc
@@ -5,6 +5,7 @@
 #include "ash/capture_mode/capture_mode_controller.h"
 
 #include <utility>
+#include <vector>
 
 #include "ash/capture_mode/capture_mode_ash_notification_view.h"
 #include "ash/capture_mode/capture_mode_camera_controller.h"
@@ -61,6 +62,7 @@
 #include "ui/message_center/public/cpp/notification.h"
 #include "ui/message_center/public/cpp/notification_delegate.h"
 #include "ui/snapshot/snapshot.h"
+#include "ui/views/widget/widget.h"
 
 namespace ash {
 
@@ -797,6 +799,33 @@
   return gfx::Rect();
 }
 
+std::vector<aura::Window*>
+CaptureModeController::GetWindowsForCollisionAvoidance() const {
+  std::vector<aura::Window*> windows_to_be_avoided;
+  if (IsActive()) {
+    aura::Window* capture_bar_window =
+        capture_mode_session_->capture_mode_bar_widget()->GetNativeWindow();
+    windows_to_be_avoided.push_back(capture_bar_window);
+  }
+
+  auto* camera_preview_widget = camera_controller_->camera_preview_widget();
+  if (camera_preview_widget && camera_preview_widget->IsVisible()) {
+    windows_to_be_avoided.push_back(camera_preview_widget->GetNativeView());
+  }
+
+  if (video_recording_watcher_ &&
+      !video_recording_watcher_->is_shutting_down() &&
+      video_recording_watcher_->recording_source() !=
+          CaptureModeSource::kWindow) {
+    if (auto* key_combo_widget =
+            video_recording_watcher_->GetKeyComboWidgetIfVisible()) {
+      windows_to_be_avoided.push_back(key_combo_widget->GetNativeWindow());
+    }
+  }
+
+  return windows_to_be_avoided;
+}
+
 void CaptureModeController::OnRecordingEnded(
     recording::mojom::RecordingStatus status,
     const gfx::ImageSkia& thumbnail) {
diff --git a/ash/capture_mode/capture_mode_controller.h b/ash/capture_mode/capture_mode_controller.h
index 77b69420..717ea75b 100644
--- a/ash/capture_mode/capture_mode_controller.h
+++ b/ash/capture_mode/capture_mode_controller.h
@@ -7,6 +7,7 @@
 
 #include <memory>
 #include <string>
+#include <vector>
 
 #include "ash/ash_export.h"
 #include "ash/capture_mode/capture_mode_metrics.h"
@@ -245,6 +246,10 @@
   // 'kRegion', but in window's coordinate when it is 'kWindow' type.
   gfx::Rect GetCaptureSurfaceConfineBounds() const;
 
+  // Returns the windows that to be avoided for collision with other system
+  // windows such as the PIP window and the automatic click bubble menu.
+  std::vector<aura::Window*> GetWindowsForCollisionAvoidance() const;
+
   // recording::mojom::RecordingServiceClient:
   void OnRecordingEnded(recording::mojom::RecordingStatus status,
                         const gfx::ImageSkia& thumbnail) override;
diff --git a/ash/capture_mode/capture_mode_demo_tools_controller.cc b/ash/capture_mode/capture_mode_demo_tools_controller.cc
index 87369a8..0a17a4e 100644
--- a/ash/capture_mode/capture_mode_demo_tools_controller.cc
+++ b/ash/capture_mode/capture_mode_demo_tools_controller.cc
@@ -6,7 +6,10 @@
 
 #include <memory>
 
+#include "ash/accessibility/accessibility_controller_impl.h"
 #include "ash/capture_mode/capture_mode_constants.h"
+#include "ash/capture_mode/capture_mode_controller.h"
+#include "ash/capture_mode/capture_mode_session.h"
 #include "ash/capture_mode/capture_mode_util.h"
 #include "ash/capture_mode/key_combo_view.h"
 #include "ash/capture_mode/pointer_highlight_layer.h"
@@ -47,6 +50,7 @@
 constexpr base::TimeDelta kMouseScaleUpDuration = base::Milliseconds(1500);
 constexpr base::TimeDelta kTouchDownScaleUpDuration = base::Milliseconds(200);
 constexpr base::TimeDelta kTouchUpScaleUpDuration = base::Milliseconds(1000);
+constexpr int kSpaceBetweenKeyComboAndCaptureBar = 8;
 
 int GetModifierFlagForKeyCode(ui::KeyboardCode key_code) {
   switch (key_code) {
@@ -173,6 +177,12 @@
 void CaptureModeDemoToolsController::RefreshBounds() {
   if (key_combo_widget_) {
     key_combo_widget_->SetBounds(CalculateKeyComboWidgetBounds());
+
+    // Update the autoclick menu bounds and sticky overlay bounds if it collides
+    // with the bounds of the `key_combo_widget_`.
+    Shell::Get()
+        ->accessibility_controller()
+        ->UpdateFloatingPanelBoundsIfNeeded();
   }
 }
 
@@ -297,9 +307,26 @@
           ? confine_bounds.right() - preferred_size.width() -
                 capture_mode::kKeyWidgetBorderPadding
           : confine_bounds.CenterPoint().x() - preferred_size.width() / 2;
-  const int key_combo_y = confine_bounds.bottom() -
-                          capture_mode::kKeyWidgetDistanceFromBottom -
-                          preferred_size.height();
+
+  int key_combo_y = confine_bounds.bottom() -
+                    capture_mode::kKeyWidgetDistanceFromBottom -
+                    preferred_size.height();
+
+  // Check the existence of capture mode bar and re-calculate `key_combo_y` to
+  // avoid collision.
+  auto* capture_mode_controller = CaptureModeController::Get();
+  if (capture_mode_controller->IsActive() &&
+      video_recording_watcher_->recording_source() !=
+          CaptureModeSource::kWindow) {
+    auto* capture_bar_widget = capture_mode_controller->capture_mode_session()
+                                   ->capture_mode_bar_widget();
+    DCHECK(capture_bar_widget);
+    key_combo_y = std::min(key_combo_y,
+                           capture_bar_widget->GetWindowBoundsInScreen().y() -
+                               kSpaceBetweenKeyComboAndCaptureBar -
+                               preferred_size.height());
+  }
+
   return gfx::Rect(gfx::Point(key_combo_x, key_combo_y), preferred_size);
 }
 
diff --git a/ash/capture_mode/capture_mode_demo_tools_controller.h b/ash/capture_mode/capture_mode_demo_tools_controller.h
index 2d2a7ff4a..37c027d 100644
--- a/ash/capture_mode/capture_mode_demo_tools_controller.h
+++ b/ash/capture_mode/capture_mode_demo_tools_controller.h
@@ -12,6 +12,7 @@
 #include "ui/base/ime/text_input_client.h"
 #include "ui/events/keycodes/keyboard_codes_posix.h"
 #include "ui/views/widget/unique_widget_ptr.h"
+#include "ui/views/widget/widget.h"
 
 namespace ui {
 class KeyEvent;
@@ -44,6 +45,10 @@
       const CaptureModeDemoToolsController&) = delete;
   ~CaptureModeDemoToolsController() override;
 
+  const views::Widget* key_combo_widget() const {
+    return key_combo_widget_.get();
+  }
+
   // Decides whether to show a helper widget for the `event` or not.
   void OnKeyEvent(ui::KeyEvent* event);
 
@@ -51,7 +56,7 @@
   // grow-and-fade-out animation on it.
   void PerformMousePressAnimation(const gfx::PointF& event_location_in_window);
 
-  // Refreshes the bounds of the key combo viewer.
+  // Refreshes the bounds of `key_combo_widget_`.
   void RefreshBounds();
 
   // Decides whether to show the highlight for the touch event or not.
@@ -82,7 +87,7 @@
   // can not be displayed independently.
   bool ShouldResetKeyComboWidget() const;
 
-  // Resets the `key_combo_widget_` when the `hide_timer_` expires.
+  // Resets the `key_combo_widget_` when the `key_up_refresh_timer_` expires.
   void AnimateToResetKeyComboWidget();
 
   void UpdateTextInputType(const ui::TextInputClient* client);
diff --git a/ash/capture_mode/capture_mode_demo_tools_unittests.cc b/ash/capture_mode/capture_mode_demo_tools_unittests.cc
index f8bd7c7..85380a3 100644
--- a/ash/capture_mode/capture_mode_demo_tools_unittests.cc
+++ b/ash/capture_mode/capture_mode_demo_tools_unittests.cc
@@ -944,6 +944,81 @@
             expected_modifier_key_vector);
 }
 
+// Tests that if a new capture mode session gets triggered by keyboard shortcut
+// while in video recording with demo tools on, the bounds of the key combo
+// widget will be updated to avoid collision.
+TEST_F(CaptureModeDemoToolsTest, KeyComboWidgetDeIntersectsWithCaptureBar) {
+  UpdateDisplay("800x700");
+  CaptureModeController* controller = StartCaptureSession(
+      CaptureModeSource::kFullscreen, CaptureModeType::kVideo);
+  controller->EnableDemoTools(true);
+  StartVideoRecordingImmediately();
+  EXPECT_TRUE(controller->is_recording_in_progress());
+  CaptureModeDemoToolsController* demo_tools_controller =
+      GetCaptureModeDemoToolsController();
+  EXPECT_TRUE(demo_tools_controller);
+  CaptureModeDemoToolsTestApi demo_tools_test_api(demo_tools_controller);
+
+  // Start a new capture mode session with keyboard shortcut.
+  PressAndReleaseKey(ui::VKEY_MEDIA_LAUNCH_APP1,
+                     ui::EF_SHIFT_DOWN | ui::EF_CONTROL_DOWN);
+  auto* key_combo_widget = demo_tools_test_api.GetKeyComboWidget();
+  EXPECT_TRUE(key_combo_widget);
+  const gfx::Rect original_bounds = key_combo_widget->GetWindowBoundsInScreen();
+
+  const auto* capture_bar_view = GetCaptureModeBarView();
+  EXPECT_TRUE(capture_bar_view);
+  const auto capture_bar_bounds = capture_bar_view->GetBoundsInScreen();
+  const int capture_bar_y = capture_bar_bounds.y();
+  EXPECT_LT(capture_bar_y, original_bounds.bottom());
+
+  PressAndReleaseKey(ui::VKEY_MEDIA_LAUNCH_APP1,
+                     ui::EF_SHIFT_DOWN | ui::EF_CONTROL_DOWN);
+  key_combo_widget = demo_tools_test_api.GetKeyComboWidget();
+  const gfx::Rect new_bounds = key_combo_widget->GetWindowBoundsInScreen();
+  EXPECT_GT(capture_bar_y, new_bounds.bottom());
+}
+
+// Tests that the auto click bar will be repositioned once there is a collision
+// with the key combo widget.
+TEST_F(CaptureModeDemoToolsTest, KeyComboWidgetDeIntersectsWithAutoClickBar) {
+  auto* autoclick_bubble_widget = EnableAndGetAutoClickBubbleWidget();
+  const gfx::Rect original_auto_click_widget_bounds =
+      autoclick_bubble_widget->GetWindowBoundsInScreen();
+
+  CaptureModeController* controller =
+      StartCaptureSession(CaptureModeSource::kRegion, CaptureModeType::kVideo);
+
+  // Intentionally create a `capture_region` within which the bounds of the key
+  // combo widget generated will mostly likely to collide with the
+  // `autoclick_bubble_widget`.
+  gfx::Rect capture_region = original_auto_click_widget_bounds;
+  capture_region.Inset(-16);
+
+  controller->SetUserCaptureRegion(capture_region,
+                                   /*by_user=*/true);
+  controller->EnableDemoTools(true);
+  StartVideoRecordingImmediately();
+
+  auto* event_generator = GetEventGenerator();
+  event_generator->PressKey(ui::VKEY_CONTROL, ui::EF_NONE);
+  event_generator->PressKey(ui::VKEY_C, ui::EF_NONE);
+
+  CaptureModeDemoToolsTestApi demo_tools_test_api(
+      GetCaptureModeDemoToolsController());
+  auto* key_combo_widget = demo_tools_test_api.GetKeyComboWidget();
+  ASSERT_TRUE(key_combo_widget);
+  const gfx::Rect key_combo_widget_bounds =
+      key_combo_widget->GetWindowBoundsInScreen();
+  EXPECT_TRUE(
+      key_combo_widget_bounds.Intersects(original_auto_click_widget_bounds));
+
+  const gfx::Rect new_autoclick_widget_bounds =
+      autoclick_bubble_widget->GetWindowBoundsInScreen();
+  EXPECT_FALSE(key_combo_widget_bounds.Intersects(new_autoclick_widget_bounds));
+  controller->EndVideoRecording(EndRecordingReason::kStopRecordingButton);
+}
+
 // Tests that the metrics that record if a recording starts with demo tools
 // feature enabled are recorded correctly in a capture session both in clamshell
 // and tablet mode.
diff --git a/ash/capture_mode/capture_mode_test_util.cc b/ash/capture_mode/capture_mode_test_util.cc
index fc8d57e..a318f3f 100644
--- a/ash/capture_mode/capture_mode_test_util.cc
+++ b/ash/capture_mode/capture_mode_test_util.cc
@@ -4,6 +4,9 @@
 
 #include "ash/capture_mode/capture_mode_test_util.h"
 
+#include "ash/accessibility/a11y_feature_type.h"
+#include "ash/accessibility/accessibility_controller_impl.h"
+#include "ash/accessibility/autoclick/autoclick_controller.h"
 #include "ash/capture_mode/capture_mode_bar_view.h"
 #include "ash/capture_mode/capture_mode_controller.h"
 #include "ash/capture_mode/capture_mode_session_test_api.h"
@@ -16,7 +19,7 @@
 #include "ash/public/cpp/projector/speech_recognition_availability.h"
 #include "ash/shell.h"
 #include "ash/style/icon_button.h"
-#include "ash/wm/cursor_manager_chromeos.h"
+#include "ash/system/accessibility/autoclick_menu_bubble_controller.h"
 #include "ash/wm/tablet_mode/tablet_mode_controller_test_api.h"
 #include "base/files/file_path.h"
 #include "base/files/file_util.h"
@@ -192,6 +195,21 @@
   }
 }
 
+views::Widget* EnableAndGetAutoClickBubbleWidget() {
+  auto* autoclick_controller = Shell::Get()->autoclick_controller();
+  autoclick_controller->SetEnabled(true, /*show_confirmation_dialog=*/false);
+  Shell::Get()
+      ->accessibility_controller()
+      ->GetFeature(A11yFeatureType::kAutoclick)
+      .SetEnabled(true);
+
+  views::Widget* autoclick_bubble_widget =
+      autoclick_controller->GetMenuBubbleControllerForTesting()
+          ->GetBubbleWidgetForTesting();
+  EXPECT_TRUE(autoclick_bubble_widget->IsVisible());
+  return autoclick_bubble_widget;
+}
+
 // -----------------------------------------------------------------------------
 // ProjectorCaptureModeIntegrationHelper:
 
diff --git a/ash/capture_mode/capture_mode_test_util.h b/ash/capture_mode/capture_mode_test_util.h
index e846465..92b0ce7c 100644
--- a/ash/capture_mode/capture_mode_test_util.h
+++ b/ash/capture_mode/capture_mode_test_util.h
@@ -110,6 +110,10 @@
 // Sets the device scale factor for only the first available display.
 void SetDeviceScaleFactor(float dsf);
 
+// Enables the auto click accessibility feature, and returns the auto click
+// bubble widget.
+views::Widget* EnableAndGetAutoClickBubbleWidget();
+
 // Defines a helper class to allow setting up and testing the Projector feature
 // in multiple test fixtures. Note that this helper initializes the Projector-
 // related features in its constructor, so test fixtures that use this should
diff --git a/ash/capture_mode/capture_mode_util.cc b/ash/capture_mode/capture_mode_util.cc
index 3b3ce5464..519dc25 100644
--- a/ash/capture_mode/capture_mode_util.cc
+++ b/ash/capture_mode/capture_mode_util.cc
@@ -504,15 +504,26 @@
 
 void MaybeUpdateCameraPrivacyIndicator(bool camera_on) {
   if (features::IsPrivacyIndicatorsEnabled()) {
-    UpdatePrivacyIndicatorsView(kCameraPrivacyIndicatorId, camera_on,
-                                /*is_microphone_used=*/false);
+    UpdatePrivacyIndicators(
+        /*app_id=*/kCameraPrivacyIndicatorId,
+        /*app_name=*/
+        l10n_util::GetStringUTF16(
+            IDS_ASH_STATUS_TRAY_CAPTURE_MODE_BUTTON_LABEL),
+        camera_on,
+        /*is_microphone_used=*/false, /*delegate=*/
+        base::MakeRefCounted<PrivacyIndicatorsNotificationDelegate>());
   }
 }
 
 void MaybeUpdateMicrophonePrivacyIndicator(bool mic_on) {
   if (features::IsPrivacyIndicatorsEnabled()) {
-    UpdatePrivacyIndicatorsView(kMicrophonePrivacyIndicatorId,
-                                /*is_camera_used=*/false, mic_on);
+    UpdatePrivacyIndicators(
+        /*app_id=*/kMicrophonePrivacyIndicatorId,
+        /*app_name=*/
+        l10n_util::GetStringUTF16(
+            IDS_ASH_STATUS_TRAY_CAPTURE_MODE_BUTTON_LABEL),
+        /*is_camera_used=*/false, mic_on, /*delegate=*/
+        base::MakeRefCounted<PrivacyIndicatorsNotificationDelegate>());
   }
 }
 
diff --git a/ash/capture_mode/video_recording_watcher.cc b/ash/capture_mode/video_recording_watcher.cc
index a199202..624937e40 100644
--- a/ash/capture_mode/video_recording_watcher.cc
+++ b/ash/capture_mode/video_recording_watcher.cc
@@ -42,6 +42,7 @@
 #include "ui/gfx/geometry/size_f.h"
 #include "ui/gfx/native_widget_types.h"
 #include "ui/gfx/scoped_canvas.h"
+#include "ui/views/widget/widget.h"
 #include "ui/wm/core/coordinate_conversion.h"
 #include "ui/wm/public/activation_client.h"
 
@@ -327,6 +328,27 @@
   }
 }
 
+gfx::Rect VideoRecordingWatcher::GetEffectivePartialRegionBounds() const {
+  DCHECK_EQ(recording_source_, CaptureModeSource::kRegion);
+  // TODO(afakhry): Consider having the region to anchor to the nearest corner,
+  // so that screen rotation doesn't result in the apparent change of the region
+  // position. Discussion with PM/UX determined that this is a low priority for
+  // now.
+  gfx::Rect result = partial_region_bounds_;
+  result.AdjustToFit(current_root_->bounds());
+  return result;
+}
+
+const views::Widget* VideoRecordingWatcher::GetKeyComboWidgetIfVisible() const {
+  if (demo_tools_controller_) {
+    const auto* key_combo_widget = demo_tools_controller_->key_combo_widget();
+    if (key_combo_widget && key_combo_widget->IsVisible()) {
+      return key_combo_widget;
+    }
+  }
+  return nullptr;
+}
+
 void VideoRecordingWatcher::OnWindowParentChanged(aura::Window* window,
                                                   aura::Window* parent) {
   DCHECK_EQ(window, window_being_recorded_);
@@ -588,17 +610,6 @@
           : GetCursorLocationInWindow(window_being_recorded_));
 }
 
-gfx::Rect VideoRecordingWatcher::GetEffectivePartialRegionBounds() const {
-  DCHECK_EQ(recording_source_, CaptureModeSource::kRegion);
-  // TODO(afakhry): Consider having the region to anchor to the nearest corner,
-  // so that screen rotation doesn't result in the apparent change of the region
-  // position. Discussion with PM/UX determined that this is a low priority for
-  // now.
-  gfx::Rect result = partial_region_bounds_;
-  result.AdjustToFit(current_root_->bounds());
-  return result;
-}
-
 bool VideoRecordingWatcher::IsWindowDimmedForTesting(
     aura::Window* window) const {
   return dimmers_.contains(window);
diff --git a/ash/capture_mode/video_recording_watcher.h b/ash/capture_mode/video_recording_watcher.h
index cddb2de..d5f06beb 100644
--- a/ash/capture_mode/video_recording_watcher.h
+++ b/ash/capture_mode/video_recording_watcher.h
@@ -71,6 +71,7 @@
   bool is_in_projector_mode() const { return is_in_projector_mode_; }
   bool should_paint_layer() const { return should_paint_layer_; }
   bool is_shutting_down() const { return is_shutting_down_; }
+  CaptureModeSource recording_source() const { return recording_source_; }
 
   // Toggles the Projector mode's overlay widget on or off. Can only be called
   // if |is_in_projector_mode()| is true.
@@ -90,6 +91,14 @@
   // when recording is in progress.
   gfx::Rect GetCaptureSurfaceConfineBounds() const;
 
+  // Returns the `partial_region_bounds_` clamped to the bounds of the
+  // `current_root_`. It should only be called if `recording_source_` is
+  // `kRegion`.
+  gfx::Rect GetEffectivePartialRegionBounds() const;
+
+  // Returns the `key_combo_widget_` if it is visible.
+  const views::Widget* GetKeyComboWidgetIfVisible() const;
+
   // aura::WindowObserver:
   void OnWindowParentChanged(aura::Window* window,
                              aura::Window* parent) override;
@@ -136,11 +145,6 @@
   // CursorWindowController::Observer:
   void OnCursorCompositingStateChanged(bool enabled) override;
 
-  // Returns the `partial_region_bounds_` clamped to the bounds of the
-  // `current_root_`. It should only be called if `recording_source_` is
-  // `kRegion`.
-  gfx::Rect GetEffectivePartialRegionBounds() const;
-
   bool IsWindowDimmedForTesting(aura::Window* window) const;
 
   void BindCursorOverlayForTesting(
diff --git a/ash/clipboard/clipboard_history.cc b/ash/clipboard/clipboard_history.cc
index b205494e..4aa9e59 100644
--- a/ash/clipboard/clipboard_history.cc
+++ b/ash/clipboard/clipboard_history.cc
@@ -44,6 +44,10 @@
   return history_list_;
 }
 
+std::list<ClipboardHistoryItem>& ClipboardHistory::GetItems() {
+  return history_list_;
+}
+
 void ClipboardHistory::Clear() {
   history_list_ = std::list<ClipboardHistoryItem>();
   for (auto& observer : observers_)
diff --git a/ash/clipboard/clipboard_history.h b/ash/clipboard/clipboard_history.h
index 955545ad..5820f4d 100644
--- a/ash/clipboard/clipboard_history.h
+++ b/ash/clipboard/clipboard_history.h
@@ -17,7 +17,6 @@
 #include "ui/base/clipboard/clipboard_observer.h"
 
 namespace ash {
-
 class ScopedClipboardHistoryPauseImpl;
 
 namespace clipboard_history_util {
@@ -55,6 +54,7 @@
   // Returns the list of most recent items. The returned list is sorted by
   // recency.
   const std::list<ClipboardHistoryItem>& GetItems() const;
+  std::list<ClipboardHistoryItem>& GetItems();
 
   // Deletes clipboard history. Does not modify content stored in the clipboard.
   void Clear();
@@ -73,8 +73,9 @@
   base::WeakPtr<ClipboardHistory> GetWeakPtr();
 
  private:
-  // Friended to allow ScopedClipboardHistoryPauseImpl to `Pause()` and
+  // Friended to allow `ScopedClipboardHistoryPauseImpl` to `Pause()` and
   // `Resume()`.
+  // TODO(b/269470292): Use a `PassKey` for this.
   friend class ScopedClipboardHistoryPauseImpl;
 
   // Ensures that the clipboard buffer contains the same data as the item at the
diff --git a/ash/clipboard/clipboard_history_item.cc b/ash/clipboard/clipboard_history_item.cc
index 95e9dd2..e725d01 100644
--- a/ash/clipboard/clipboard_history_item.cc
+++ b/ash/clipboard/clipboard_history_item.cc
@@ -6,8 +6,6 @@
 
 #include <vector>
 
-#include "ash/clipboard/clipboard_history_controller_impl.h"
-#include "ash/clipboard/clipboard_history_resource_manager.h"
 #include "ash/clipboard/clipboard_history_util.h"
 #include "ash/shell.h"
 #include "ash/style/color_util.h"
@@ -22,6 +20,7 @@
 #include "ui/base/models/image_model.h"
 #include "ui/base/webui/web_ui_util.h"
 #include "ui/color/color_provider_source.h"
+#include "ui/gfx/image/image.h"
 #include "ui/strings/grit/ui_strings.h"
 
 namespace ash {
@@ -115,7 +114,13 @@
       time_copied_(base::Time::Now()),
       main_format_(clipboard_history_util::CalculateMainFormat(data_).value()),
       display_format_(CalculateDisplayFormat(main_format_, data_)),
-      display_text_(DetermineDisplayText(data_)) {}
+      display_text_(DetermineDisplayText(data_)) {
+  if (display_format_ == DisplayFormat::kHtml) {
+    // The `ClipboardHistoryResourceManager` will update this preview once an
+    // image model is rendered.
+    html_preview_ = clipboard_history_util::GetHtmlPreviewPlaceholder();
+  }
+}
 
 ClipboardHistoryItem::ClipboardHistoryItem(const ClipboardHistoryItem&) =
     default;
@@ -147,15 +152,9 @@
       }
       break;
     case DisplayFormat::kHtml: {
-      // TODO(b/267677307): Make cached image an item field, set and updated
-      // directly by the resource manager.
-      const SkBitmap& bitmap = *(Shell::Get()
-                                     ->clipboard_history_controller()
-                                     ->resource_manager()
-                                     ->GetImageModel(*this)
-                                     .GetImage()
-                                     .ToSkBitmap());
-      maybe_url = webui::GetBitmapDataUrl(bitmap);
+      DCHECK(html_preview_.has_value());
+      maybe_url =
+          webui::GetBitmapDataUrl(*html_preview_->GetImage().ToSkBitmap());
       break;
     }
     case DisplayFormat::kFile: {
diff --git a/ash/clipboard/clipboard_history_item.h b/ash/clipboard/clipboard_history_item.h
index 966b0d5c..b51e2d7 100644
--- a/ash/clipboard/clipboard_history_item.h
+++ b/ash/clipboard/clipboard_history_item.h
@@ -12,6 +12,7 @@
 #include "base/unguessable_token.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
 #include "ui/base/clipboard/clipboard_data.h"
+#include "ui/base/models/image_model.h"
 
 namespace ash {
 
@@ -54,6 +55,12 @@
   const base::Time time_copied() const { return time_copied_; }
   ui::ClipboardInternalFormat main_format() const { return main_format_; }
   DisplayFormat display_format() const { return display_format_; }
+  void set_html_preview(const ui::ImageModel& html_preview) {
+    html_preview_ = html_preview;
+  }
+  const absl::optional<ui::ImageModel>& html_preview() const {
+    return html_preview_;
+  }
   const std::u16string& display_text() const { return display_text_; }
 
  private:
@@ -74,6 +81,10 @@
   // to the user.
   const DisplayFormat display_format_;
 
+  // Cached display image. For HTML items, this will be a placeholder image
+  // until the preview is ready; for non-HTML items, there will be no value.
+  absl::optional<ui::ImageModel> html_preview_;
+
   // The text that should be displayed on this item's menu entry.
   const std::u16string display_text_;
 };
diff --git a/ash/clipboard/clipboard_history_menu_model_adapter.h b/ash/clipboard/clipboard_history_menu_model_adapter.h
index 83cb07f3..d7f086f4 100644
--- a/ash/clipboard/clipboard_history_menu_model_adapter.h
+++ b/ash/clipboard/clipboard_history_menu_model_adapter.h
@@ -35,7 +35,8 @@
 
 // Used to show the clipboard history menu, which holds the last few things
 // copied.
-class ASH_EXPORT ClipboardHistoryMenuModelAdapter : views::MenuModelAdapter {
+class ASH_EXPORT ClipboardHistoryMenuModelAdapter
+    : public views::MenuModelAdapter {
  public:
   static std::unique_ptr<ClipboardHistoryMenuModelAdapter> Create(
       ui::SimpleMenuModel::Delegate* delegate,
diff --git a/ash/clipboard/clipboard_history_resource_manager.cc b/ash/clipboard/clipboard_history_resource_manager.cc
index 9a7f7b0..def002f 100644
--- a/ash/clipboard/clipboard_history_resource_manager.cc
+++ b/ash/clipboard/clipboard_history_resource_manager.cc
@@ -7,81 +7,25 @@
 #include <string>
 
 #include "ash/clipboard/clipboard_history_item.h"
-#include "ash/clipboard/clipboard_history_util.h"
 #include "ash/display/display_util.h"
 #include "ash/public/cpp/clipboard_image_model_factory.h"
 #include "ash/public/cpp/window_tree_host_lookup.h"
-#include "ash/resources/vector_icons/vector_icons.h"
 #include "base/containers/contains.h"
-#include "base/containers/cxx20_erase.h"
 #include "base/functional/bind.h"
 #include "base/ranges/algorithm.h"
-#include "base/stl_util.h"
 #include "ui/aura/window_tree_host.h"
 #include "ui/base/clipboard/clipboard_data.h"
 #include "ui/base/ime/input_method.h"
 #include "ui/base/ime/text_input_client.h"
 #include "ui/display/screen.h"
-#include "ui/gfx/canvas.h"
-#include "ui/gfx/color_palette.h"
-#include "ui/gfx/image/canvas_image_source.h"
-#include "ui/gfx/paint_vector_icon.h"
 
 namespace ash {
 
-namespace {
-
-constexpr int kPlaceholderImageWidth = 234;
-constexpr int kPlaceholderImageHeight = 74;
-constexpr int kPlaceholderImageOutlineCornerRadius = 8;
-constexpr int kPlaceholderImageSVGSize = 32;
-
-// Used to draw the UnrenderedHTMLPlaceholderImage, which is shown while HTML is
-// rendering. Drawn in order to turn the square and single colored SVG into a
-// multicolored rectangle image.
-class UnrenderedHTMLPlaceholderImage : public gfx::CanvasImageSource {
- public:
-  UnrenderedHTMLPlaceholderImage()
-      : gfx::CanvasImageSource(
-            gfx::Size(kPlaceholderImageWidth, kPlaceholderImageHeight)) {}
-  UnrenderedHTMLPlaceholderImage(const UnrenderedHTMLPlaceholderImage&) =
-      delete;
-  UnrenderedHTMLPlaceholderImage& operator=(
-      const UnrenderedHTMLPlaceholderImage&) = delete;
-  ~UnrenderedHTMLPlaceholderImage() override = default;
-
-  // gfx::CanvasImageSource:
-  void Draw(gfx::Canvas* canvas) override {
-    cc::PaintFlags flags;
-    flags.setStyle(cc::PaintFlags::kFill_Style);
-    flags.setAntiAlias(true);
-    flags.setColor(gfx::kGoogleGrey100);
-    canvas->DrawRoundRect(
-        /*rect=*/{kPlaceholderImageWidth, kPlaceholderImageHeight},
-        kPlaceholderImageOutlineCornerRadius, flags);
-
-    flags = cc::PaintFlags();
-    flags.setStyle(cc::PaintFlags::kFill_Style);
-    flags.setAntiAlias(true);
-    const gfx::ImageSkia center_image =
-        gfx::CreateVectorIcon(kUnrenderedHtmlPlaceholderIcon,
-                              kPlaceholderImageSVGSize, gfx::kGoogleGrey600);
-    canvas->DrawImageInt(
-        center_image, (size().width() - center_image.size().width()) / 2,
-        (size().height() - center_image.size().height()) / 2, flags);
-  }
-};
-
-}  // namespace
-
 // ClipboardHistoryResourceManager ---------------------------------------------
 
 ClipboardHistoryResourceManager::ClipboardHistoryResourceManager(
-    const ClipboardHistory* clipboard_history)
-    : clipboard_history_(clipboard_history),
-      placeholder_image_model_(
-          ui::ImageModel::FromImageSkia(gfx::CanvasImageSource::MakeImageSkia<
-                                        UnrenderedHTMLPlaceholderImage>())) {
+    ClipboardHistory* clipboard_history)
+    : clipboard_history_(clipboard_history) {
   clipboard_history_->AddObserver(this);
 }
 
@@ -91,17 +35,6 @@
     ClipboardImageModelFactory::Get()->OnShutdown();
 }
 
-ui::ImageModel ClipboardHistoryResourceManager::GetImageModel(
-    const ClipboardHistoryItem& item) const {
-  // Use a cached image model when possible.
-  auto cached_image_model = FindCachedImageModelForItem(item);
-  if (cached_image_model == cached_image_models_.end() ||
-      cached_image_model->image_model.IsEmpty()) {
-    return placeholder_image_model_;
-  }
-  return cached_image_model->image_model;
-}
-
 void ClipboardHistoryResourceManager::AddObserver(Observer* observer) const {
   observers_.AddObserver(observer);
 }
@@ -110,59 +43,66 @@
   observers_.RemoveObserver(observer);
 }
 
-ClipboardHistoryResourceManager::CachedImageModel::CachedImageModel() = default;
-
-ClipboardHistoryResourceManager::CachedImageModel::CachedImageModel(
-    const CachedImageModel& other) = default;
-
-ClipboardHistoryResourceManager::CachedImageModel&
-ClipboardHistoryResourceManager::CachedImageModel::operator=(
-    const CachedImageModel&) = default;
-
-ClipboardHistoryResourceManager::CachedImageModel::~CachedImageModel() =
+ClipboardHistoryResourceManager::ImageModelRequest::ImageModelRequest() =
     default;
 
-void ClipboardHistoryResourceManager::CacheImageModel(
+ClipboardHistoryResourceManager::ImageModelRequest::ImageModelRequest(
+    const ImageModelRequest& other) = default;
+
+ClipboardHistoryResourceManager::ImageModelRequest&
+ClipboardHistoryResourceManager::ImageModelRequest::operator=(
+    const ImageModelRequest&) = default;
+
+ClipboardHistoryResourceManager::ImageModelRequest::~ImageModelRequest() =
+    default;
+
+void ClipboardHistoryResourceManager::OnImageModelRendered(
     const base::UnguessableToken& id,
     ui::ImageModel image_model) {
-  auto cached_image_model = base::ConstCastIterator(
-      cached_image_models_, FindCachedImageModelForId(id));
-  if (cached_image_model == cached_image_models_.end())
+  auto image_model_request = base::ranges::find(
+      image_model_requests_, id,
+      &ClipboardHistoryResourceManager::ImageModelRequest::id);
+  if (image_model_request == image_model_requests_.end()) {
     return;
+  }
 
-  cached_image_model->image_model = std::move(image_model);
+  // Set the HTML preview for each item attached to `id`'s request.
+  for (auto& item : clipboard_history_->GetItems()) {
+    if (!base::Contains(image_model_request->clipboard_history_item_ids,
+                        item.id())) {
+      continue;
+    }
+
+    DCHECK(item.html_preview().has_value());
+    if (item.html_preview().value() != image_model) {
+      item.set_html_preview(image_model);
+    }
+  }
 
   for (auto& observer : observers_) {
     observer.OnCachedImageModelUpdated(
-        cached_image_model->clipboard_history_item_ids);
+        image_model_request->clipboard_history_item_ids);
   }
-}
 
-std::vector<ClipboardHistoryResourceManager::CachedImageModel>::const_iterator
-ClipboardHistoryResourceManager::FindCachedImageModelForId(
-    const base::UnguessableToken& id) const {
-  return base::ranges::find(
-      cached_image_models_, id,
-      &ClipboardHistoryResourceManager::CachedImageModel::id);
-}
-
-std::vector<ClipboardHistoryResourceManager::CachedImageModel>::const_iterator
-ClipboardHistoryResourceManager::FindCachedImageModelForItem(
-    const ClipboardHistoryItem& item) const {
-  return base::ranges::find_if(
-      cached_image_models_, [&](const auto& cached_image_model) {
-        return base::Contains(cached_image_model.clipboard_history_item_ids,
-                              item.id());
-      });
+  image_model_requests_.erase(image_model_request);
 }
 
 void ClipboardHistoryResourceManager::CancelUnfinishedRequests() {
-  for (const auto& cached_image_model : cached_image_models_) {
-    if (cached_image_model.image_model.IsEmpty())
-      ClipboardImageModelFactory::Get()->CancelRequest(cached_image_model.id);
+  for (const auto& image_model_request : image_model_requests_) {
+    ClipboardImageModelFactory::Get()->CancelRequest(image_model_request.id);
   }
 }
 
+std::vector<ClipboardHistoryResourceManager::ImageModelRequest>::iterator
+ClipboardHistoryResourceManager::GetImageModelRequestForItem(
+    const ClipboardHistoryItem& item) {
+  return base::ranges::find_if(
+      image_model_requests_, [&](const auto& image_model_request) {
+        return base::Contains(image_model_request.clipboard_history_item_ids,
+                              item.id());
+      });
+}
+
 void ClipboardHistoryResourceManager::OnClipboardHistoryItemAdded(
     const ClipboardHistoryItem& item,
     bool is_duplicate) {
@@ -176,25 +116,31 @@
     return;
   }
 
-  const auto& items = clipboard_history_->GetItems();
+  auto& items = clipboard_history_->GetItems();
 
-  // See if we have an |existing| item that will render the same as |item|.
+  // See if we have an `existing` item that will render the same as `item`.
   auto it = base::ranges::find_if(items, [&](const auto& existing) {
     return &existing != &item &&
-           !(existing.data().format() &
-             static_cast<int>(ui::ClipboardInternalFormat::kPng)) &&
+           existing.display_format() ==
+               ClipboardHistoryItem::DisplayFormat::kHtml &&
            existing.data().markup_data() == item.data().markup_data();
   });
 
-  // If we don't have an existing image model in the cache, create one and
-  // instruct ClipboardImageModelFactory to render it. Note that the factory may
+  // If no existing item will render the same as `item`, create a new request to
+  // render an HTML preview for `item`. Note that the image model factory may
   // or may not start rendering immediately depending on its activation status.
   if (it == items.end()) {
     base::UnguessableToken id = base::UnguessableToken::Create();
-    CachedImageModel cached_image_model;
-    cached_image_model.id = id;
-    cached_image_model.clipboard_history_item_ids.push_back(item.id());
-    cached_image_models_.push_back(std::move(cached_image_model));
+    ImageModelRequest image_model_request;
+    image_model_request.id = id;
+    image_model_request.clipboard_history_item_ids.push_back(item.id());
+    image_model_requests_.push_back(std::move(image_model_request));
+
+    // `image_model_factory` can be nullptr in tests.
+    auto* image_model_factory = ClipboardImageModelFactory::Get();
+    if (!image_model_factory) {
+      return;
+    }
 
     // `text_input_client` can be nullptr in tests.
     const auto* text_input_client =
@@ -202,23 +148,37 @@
             display::Screen::GetScreen()->GetPrimaryDisplay().id())
             ->GetInputMethod()
             ->GetTextInputClient();
-
     const gfx::Rect bounding_box =
         text_input_client ? text_input_client->GetSelectionBoundingBox()
                           : gfx::Rect();
-    ClipboardImageModelFactory::Get()->Render(
+
+    image_model_factory->Render(
         id, item.data().markup_data(),
         IsRectContainedByAnyDisplay(bounding_box) ? bounding_box.size()
                                                   : gfx::Size(),
-        base::BindOnce(&ClipboardHistoryResourceManager::CacheImageModel,
+        base::BindOnce(&ClipboardHistoryResourceManager::OnImageModelRendered,
                        weak_factory_.GetWeakPtr(), id));
     return;
   }
-  // If we do have an existing model, we need only to update its usages.
-  auto cached_image_model = base::ConstCastIterator(
-      cached_image_models_, FindCachedImageModelForItem(*it));
-  DCHECK(cached_image_model != cached_image_models_.end());
-  cached_image_model->clipboard_history_item_ids.push_back(item.id());
+
+  // If there is an existing item that will render the same as `item`, check
+  // whether the existing item's preview has rendered.
+  auto image_model_request = GetImageModelRequestForItem(*it);
+  if (image_model_request != image_model_requests_.end()) {
+    // If rendering is still in progress, just note that `item` will need to
+    // hear about the result as well.
+    image_model_request->clipboard_history_item_ids.push_back(item.id());
+  } else {
+    // If rendering has finished, set `item` to have the same preview.
+    auto mutable_item =
+        base::ranges::find(items, item.id(), &ClipboardHistoryItem::id);
+    DCHECK(mutable_item != items.end());
+
+    const auto& existing_preview = it->html_preview();
+    DCHECK(existing_preview.has_value());
+
+    mutable_item->set_html_preview(existing_preview.value());
+  }
 }
 
 void ClipboardHistoryResourceManager::OnClipboardHistoryItemRemoved(
@@ -228,30 +188,27 @@
     return;
   }
 
-  // We should have an image model in the cache.
-  auto cached_image_model = base::ConstCastIterator(
-      cached_image_models_, FindCachedImageModelForItem(item));
-
-  DCHECK(cached_image_model != cached_image_models_.end());
-  if (cached_image_model == cached_image_models_.end())
+  // If the item's image model request has already finished, there is nothing
+  // more to do.
+  auto image_model_request = GetImageModelRequestForItem(item);
+  if (image_model_request == image_model_requests_.end()) {
     return;
+  }
 
-  // Update usages.
-  base::Erase(cached_image_model->clipboard_history_item_ids, item.id());
-  if (!cached_image_model->clipboard_history_item_ids.empty())
-    return;
+  // If `item` was attached to a pending request, make sure it is not updated
+  // when rendering finishes.
+  base::Erase(image_model_request->clipboard_history_item_ids, item.id());
 
-  // If the ImageModel was never rendered, cancel the request.
-  if (cached_image_model->image_model.IsEmpty())
-    ClipboardImageModelFactory::Get()->CancelRequest(cached_image_model->id);
-
-  // If the cached image model is no longer in use, it can be erased.
-  cached_image_models_.erase(cached_image_model);
+  if (image_model_request->clipboard_history_item_ids.empty()) {
+    // If no more items are waiting on the image model, cancel the request.
+    ClipboardImageModelFactory::Get()->CancelRequest(image_model_request->id);
+    image_model_requests_.erase(image_model_request);
+  }
 }
 
 void ClipboardHistoryResourceManager::OnClipboardHistoryCleared() {
   CancelUnfinishedRequests();
-  cached_image_models_ = std::vector<CachedImageModel>();
+  image_model_requests_.clear();
 }
 
 }  // namespace ash
diff --git a/ash/clipboard/clipboard_history_resource_manager.h b/ash/clipboard/clipboard_history_resource_manager.h
index f5183f2..9de15e1 100644
--- a/ash/clipboard/clipboard_history_resource_manager.h
+++ b/ash/clipboard/clipboard_history_resource_manager.h
@@ -23,51 +23,44 @@
  public:
   class Observer : public base::CheckedObserver {
    public:
-    // Called when the CachedImageModel that corresponds with 'menu_item_ids'
-    // has been updated.
+    // Called when a rendered image model is set on the clipboard history items
+    // specified by `menu_item_ids`.
     virtual void OnCachedImageModelUpdated(
         const std::vector<base::UnguessableToken>& menu_item_ids) = 0;
   };
 
-  explicit ClipboardHistoryResourceManager(
-      const ClipboardHistory* clipboard_history);
+  explicit ClipboardHistoryResourceManager(ClipboardHistory* clipboard_history);
   ClipboardHistoryResourceManager(const ClipboardHistoryResourceManager&) =
       delete;
   ClipboardHistoryResourceManager& operator=(
       const ClipboardHistoryResourceManager&) = delete;
   ~ClipboardHistoryResourceManager() override;
 
-  // Returns the image to display for the specified clipboard history |item|.
-  ui::ImageModel GetImageModel(const ClipboardHistoryItem& item) const;
-
   void AddObserver(Observer* observer) const;
   void RemoveObserver(Observer* observer) const;
 
  private:
-  struct CachedImageModel {
-    CachedImageModel();
-    CachedImageModel(const CachedImageModel&);
-    CachedImageModel& operator=(const CachedImageModel&);
-    ~CachedImageModel();
+  struct ImageModelRequest {
+    ImageModelRequest();
+    ImageModelRequest(const ImageModelRequest&);
+    ImageModelRequest& operator=(const ImageModelRequest&);
+    ~ImageModelRequest();
+
     // Unique identifier.
     base::UnguessableToken id;
-    // ImageModel that was created by ClipboardImageModelFactory.
-    ui::ImageModel image_model;
-    // ClipboardHistoryItem id's which utilize this CachedImageModel.
+
+    // IDs of items whose image model will be set to this request's result.
     std::vector<base::UnguessableToken> clipboard_history_item_ids;
   };
 
-  // Caches the specified |image_model| with the specified |id|.
-  void CacheImageModel(const base::UnguessableToken& id,
-                       ui::ImageModel image_model);
+  // Sets the result `image_model` on each `ClipboardHistoryItem` waiting on the
+  // `ImageModelRequest` specified by `id`.
+  void OnImageModelRendered(const base::UnguessableToken& id,
+                            ui::ImageModel image_model);
 
-  // Finds the cached image model associated with the specified |id|.
-  std::vector<ClipboardHistoryResourceManager::CachedImageModel>::const_iterator
-  FindCachedImageModelForId(const base::UnguessableToken& id) const;
-
-  // Finds the cached image model associated with the specified |item|.
-  std::vector<ClipboardHistoryResourceManager::CachedImageModel>::const_iterator
-  FindCachedImageModelForItem(const ClipboardHistoryItem& item) const;
+  // Finds the pending image model request that `item` is waiting on.
+  std::vector<ImageModelRequest>::iterator GetImageModelRequestForItem(
+      const ClipboardHistoryItem& item);
 
   // Cancels all unfinished requests.
   void CancelUnfinishedRequests();
@@ -78,16 +71,16 @@
   void OnClipboardHistoryItemRemoved(const ClipboardHistoryItem& item) override;
   void OnClipboardHistoryCleared() override;
 
-  // Owned by ClipboardHistoryController.
-  const ClipboardHistory* const clipboard_history_;
+  // Owned by `ClipboardHistoryController`.
+  ClipboardHistory* const clipboard_history_;
 
-  std::vector<CachedImageModel> cached_image_models_;
+  // Pending requests for image models to be rendered. Once a request finishes,
+  // all of the clipboard history items waiting on that image model will be
+  // updated, and the request will be removed from this list.
+  std::vector<ImageModelRequest> image_model_requests_;
 
-  // Image used when the cached ImageModel has not yet been generated.
-  ui::ImageModel placeholder_image_model_;
-
-  // Mutable to allow adding/removing from |observers_| through a const
-  // ClipboardHistoryResourceManager.
+  // Mutable to allow adding/removing from `observers_` through a const
+  // `ClipboardHistoryResourceManager`.
   mutable base::ObserverList<Observer> observers_;
 
   base::WeakPtrFactory<ClipboardHistoryResourceManager> weak_factory_{this};
diff --git a/ash/clipboard/clipboard_history_resource_manager_unittest.cc b/ash/clipboard/clipboard_history_resource_manager_unittest.cc
index 1623b51..9978b0ebe 100644
--- a/ash/clipboard/clipboard_history_resource_manager_unittest.cc
+++ b/ash/clipboard/clipboard_history_resource_manager_unittest.cc
@@ -5,16 +5,19 @@
 #include "ash/clipboard/clipboard_history_resource_manager.h"
 
 #include <string>
-#include <unordered_map>
 
 #include "ash/clipboard/clipboard_history.h"
 #include "ash/clipboard/clipboard_history_controller_impl.h"
 #include "ash/clipboard/clipboard_history_item.h"
+#include "ash/clipboard/clipboard_history_util.h"
 #include "ash/public/cpp/clipboard_image_model_factory.h"
 #include "ash/shell.h"
 #include "ash/test/ash_test_base.h"
 #include "base/functional/callback.h"
+#include "base/location.h"
+#include "base/run_loop.h"
 #include "base/task/sequenced_task_runner.h"
+#include "base/test/repeating_test_future.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "third_party/skia/include/core/SkBitmap.h"
@@ -76,7 +79,8 @@
 
 class ClipboardHistoryResourceManagerTest : public AshTestBase {
  public:
-  ClipboardHistoryResourceManagerTest() = default;
+  ClipboardHistoryResourceManagerTest()
+      : AshTestBase(base::test::TaskEnvironment::TimeSource::MOCK_TIME) {}
   ClipboardHistoryResourceManagerTest(
       const ClipboardHistoryResourceManagerTest&) = delete;
   ClipboardHistoryResourceManagerTest& operator=(
@@ -111,8 +115,7 @@
   std::unique_ptr<MockClipboardImageModelFactory> mock_image_factory_;
 };
 
-// Tests that Render is called once when an eligible <img> is added
-// to ClipboardHistory.
+// Tests that an image model is rendered when HTML with an <img> tag is copied.
 TEST_F(ClipboardHistoryResourceManagerTest, BasicImgCachedImageModel) {
   ui::ImageModel expected_image_model = GetRandomImageModel();
   ON_CALL(*mock_image_factory(), Render)
@@ -123,21 +126,20 @@
   EXPECT_CALL(*mock_image_factory(), CancelRequest).Times(0);
   EXPECT_CALL(*mock_image_factory(), Render).Times(1);
 
-  // Write a basic ClipboardData which is eligible to render HTML.
   {
     ui::ScopedClipboardWriter scw(ui::ClipboardBuffer::kCopyPaste);
     scw.WriteHTML(u"<img test>", "source_url",
                   ui::ClipboardContentType::kSanitized);
   }
-
   FlushMessageLoop();
 
-  EXPECT_EQ(expected_image_model, resource_manager()->GetImageModel(
-                                      clipboard_history()->GetItems().front()));
+  ASSERT_EQ(clipboard_history()->GetItems().size(), 1u);
+  const auto& item = clipboard_history()->GetItems().front();
+  ASSERT_TRUE(item.html_preview().has_value());
+  EXPECT_EQ(item.html_preview().value(), expected_image_model);
 }
 
-// Tests that Render is called once when an eligible <table> is added
-// to ClipboardHistory.
+// Tests that an image model is rendered when HTML with a <table> tag is copied.
 TEST_F(ClipboardHistoryResourceManagerTest, BasicTableCachedImageModel) {
   ui::ImageModel expected_image_model = GetRandomImageModel();
   ON_CALL(*mock_image_factory(), Render)
@@ -148,21 +150,21 @@
   EXPECT_CALL(*mock_image_factory(), CancelRequest).Times(0);
   EXPECT_CALL(*mock_image_factory(), Render).Times(1);
 
-  // Write a basic ClipboardData which is eligible to render HTML.
   {
     ui::ScopedClipboardWriter scw(ui::ClipboardBuffer::kCopyPaste);
     scw.WriteHTML(u"<table test>", "source_url",
                   ui::ClipboardContentType::kSanitized);
   }
-
   FlushMessageLoop();
 
-  EXPECT_EQ(expected_image_model, resource_manager()->GetImageModel(
-                                      clipboard_history()->GetItems().front()));
+  ASSERT_EQ(clipboard_history()->GetItems().size(), 1u);
+  const auto& item = clipboard_history()->GetItems().front();
+  ASSERT_TRUE(item.html_preview().has_value());
+  EXPECT_EQ(item.html_preview().value(), expected_image_model);
 }
 
-// Tests that Render is not called when ineligble html is added to
-// ClipboarHistory
+// Tests that an image model is not rendered when HTML without render-eligible
+// tags is copied.
 TEST_F(ClipboardHistoryResourceManagerTest, BasicIneligibleCachedImageModel) {
   ui::ImageModel expected_image_model = GetRandomImageModel();
   ON_CALL(*mock_image_factory(), Render)
@@ -173,21 +175,21 @@
   EXPECT_CALL(*mock_image_factory(), CancelRequest).Times(0);
   EXPECT_CALL(*mock_image_factory(), Render).Times(0);
 
-  // Write a basic ClipboardData which is eligible to render HTML.
   {
     ui::ScopedClipboardWriter scw(ui::ClipboardBuffer::kCopyPaste);
-    scw.WriteHTML(u"html with no img or table tag", "source_url",
+    scw.WriteHTML(u"HTML with no img or table tag", "source_url",
                   ui::ClipboardContentType::kSanitized);
   }
-
   FlushMessageLoop();
+
+  ASSERT_EQ(clipboard_history()->GetItems().size(), 1u);
+  EXPECT_FALSE(
+      clipboard_history()->GetItems().front().html_preview().has_value());
 }
 
 // Tests that copying duplicate HTML to the buffer results in only one render
-// request, and that that request is canceled once when the item is forgotten.
+// request.
 TEST_F(ClipboardHistoryResourceManagerTest, DuplicateHTML) {
-  // Write two duplicate ClipboardDatas. Two things should be in clipboard
-  // history, but they should share a CachedImageModel.
   ui::ImageModel expected_image_model = GetRandomImageModel();
   ON_CALL(*mock_image_factory(), Render)
       .WillByDefault(testing::WithArg<3>(
@@ -197,14 +199,15 @@
   EXPECT_CALL(*mock_image_factory(), CancelRequest).Times(0);
   EXPECT_CALL(*mock_image_factory(), Render).Times(1);
 
-  // Use identical markup but differing source url so that both items are added
-  // to the clipboard history.
+  // Write identical markup from two different source URLs so that both items
+  // are added to the clipboard history.
   {
     ui::ScopedClipboardWriter scw(ui::ClipboardBuffer::kCopyPaste);
     scw.WriteHTML(u"<img test>", "source_url_1",
                   ui::ClipboardContentType::kSanitized);
   }
   FlushMessageLoop();
+
   {
     ui::ScopedClipboardWriter scw(ui::ClipboardBuffer::kCopyPaste);
     scw.WriteHTML(u"<img test>", "source_url_2",
@@ -212,16 +215,18 @@
   }
   FlushMessageLoop();
 
+  // Because the HTML for the two items renders to the same image, we should
+  // only try to render one time.
   auto items = clipboard_history()->GetItems();
-  EXPECT_EQ(2u, items.size());
-  for (const auto& item : items)
-    EXPECT_EQ(expected_image_model, resource_manager()->GetImageModel(item));
+  EXPECT_EQ(items.size(), 2u);
+  for (const auto& item : items) {
+    ASSERT_TRUE(item.html_preview().has_value());
+    EXPECT_EQ(item.html_preview().value(), expected_image_model);
+  }
 }
 
-// Tests that two different eligible ClipboardData copied results in two calls
-// to Render and Cancel.
+// Tests that copying different HTML items results in each one being rendered.
 TEST_F(ClipboardHistoryResourceManagerTest, DifferentHTML) {
-  // Write two ClipboardData with different HTML.
   ui::ImageModel first_expected_image_model = GetRandomImageModel();
   ui::ImageModel second_expected_image_model = GetRandomImageModel();
   std::deque<ui::ImageModel> expected_image_models{first_expected_image_model,
@@ -240,6 +245,7 @@
                   ui::ClipboardContentType::kSanitized);
   }
   FlushMessageLoop();
+
   {
     ui::ScopedClipboardWriter scw(ui::ClipboardBuffer::kCopyPaste);
     scw.WriteHTML(u"<img different>", "source_url",
@@ -248,43 +254,101 @@
   FlushMessageLoop();
 
   std::list<ClipboardHistoryItem> items = clipboard_history()->GetItems();
-  EXPECT_EQ(2u, items.size());
-  EXPECT_EQ(second_expected_image_model,
-            resource_manager()->GetImageModel(items.front()));
+  ASSERT_EQ(items.size(), 2u);
+  ASSERT_TRUE(items.front().html_preview().has_value());
+  EXPECT_EQ(items.front().html_preview().value(), second_expected_image_model);
+
   items.pop_front();
-  EXPECT_EQ(first_expected_image_model,
-            resource_manager()->GetImageModel(items.front()));
+  ASSERT_TRUE(items.front().html_preview().has_value());
+  EXPECT_EQ(items.front().html_preview().value(), first_expected_image_model);
 }
 
-// Tests that items that are ineligible for CachedImageModels (items with image
-// representations, or no markup) do not request Render.
-TEST_F(ClipboardHistoryResourceManagerTest, IneligibleItem) {
-  // Write a ClipboardData with an image, no CachedImageModel should be created.
+// Tests that copying content with non-HTML display formats does not result in
+// any render requests.
+TEST_F(ClipboardHistoryResourceManagerTest, IneligibleDisplayTypes) {
   EXPECT_CALL(*mock_image_factory(), Render).Times(0);
   EXPECT_CALL(*mock_image_factory(), CancelRequest).Times(0);
+
+  // Write clipboard data with what would otherwise be render-eligible markup,
+  // alongside an image. The image data format takes higher precedence, so no
+  // image model should be rendered.
   {
     ui::ScopedClipboardWriter scw(ui::ClipboardBuffer::kCopyPaste);
-    scw.WriteHTML(u"test", "source_url", ui::ClipboardContentType::kSanitized);
+    scw.WriteHTML(u"<img test>", "source_url",
+                  ui::ClipboardContentType::kSanitized);
     scw.WriteImage(GetRandomBitmap());
   }
   FlushMessageLoop();
 
-  EXPECT_EQ(1u, clipboard_history()->GetItems().size());
+  ASSERT_EQ(clipboard_history()->GetItems().size(), 1u);
+  EXPECT_FALSE(
+      clipboard_history()->GetItems().front().html_preview().has_value());
 
-  // Write a ClipboardData with no markup and no image. No CachedImageModel
-  // should be created.
+  // Write clipboard data without an HTML format. No image model should be
+  // rendered.
   {
     ui::ScopedClipboardWriter scw(ui::ClipboardBuffer::kCopyPaste);
-
     scw.WriteText(u"test");
-
     scw.WriteRTF("rtf");
-
     scw.WriteBookmark(u"bookmark_title", "test_url");
   }
   FlushMessageLoop();
 
-  EXPECT_EQ(2u, clipboard_history()->GetItems().size());
+  ASSERT_EQ(clipboard_history()->GetItems().size(), 2u);
+  EXPECT_FALSE(
+      clipboard_history()->GetItems().front().html_preview().has_value());
+}
+
+// Tests that a placeholder image model is cached while rendering is ongoing.
+TEST_F(ClipboardHistoryResourceManagerTest, PlaceholderDuringRender) {
+  constexpr const auto kRenderDelay = base::Seconds(1);
+  ui::ImageModel expected_image_model = GetRandomImageModel();
+  ON_CALL(*mock_image_factory(), Render)
+      .WillByDefault(testing::WithArg<3>(
+          [&](ClipboardImageModelFactory::ImageModelCallback callback) {
+            // Delay the processing of the rendered image until after the
+            // clipboard history item has been created.
+            base::SequencedTaskRunner::GetCurrentDefault()->PostDelayedTask(
+                FROM_HERE,
+                base::BindOnce(std::move(callback), expected_image_model),
+                kRenderDelay);
+          }));
+  EXPECT_CALL(*mock_image_factory(), CancelRequest).Times(0);
+  EXPECT_CALL(*mock_image_factory(), Render).Times(1);
+
+  base::test::RepeatingTestFuture<bool> operation_confirmed_future_;
+  Shell::Get()
+      ->clipboard_history_controller()
+      ->set_confirmed_operation_callback_for_test(
+          operation_confirmed_future_.GetCallback());
+
+  {
+    ui::ScopedClipboardWriter scw(ui::ClipboardBuffer::kCopyPaste);
+    scw.WriteHTML(u"<img test>", "source_url",
+                  ui::ClipboardContentType::kSanitized);
+  }
+
+  // Wait for the clipboard history item to be created. This allows us to check
+  // for the item's intermediate placeholder image model.
+  EXPECT_TRUE(operation_confirmed_future_.Take());
+
+  // Between the time a clipboard history item is first created and the time its
+  // image model finishes rendering, it should have a placeholder HTML preview.
+  ASSERT_EQ(clipboard_history()->GetItems().size(), 1u);
+  const auto& item = clipboard_history()->GetItems().front();
+  ASSERT_TRUE(item.html_preview().has_value());
+  EXPECT_NE(item.html_preview().value(), expected_image_model);
+  EXPECT_EQ(item.html_preview().value(),
+            clipboard_history_util::GetHtmlPreviewPlaceholder());
+
+  // Allow the resource manager to process the rendered image model.
+  task_environment()->FastForwardBy(kRenderDelay);
+  FlushMessageLoop();
+
+  // After the resource manager processes the rendered image, it should be
+  // cached in the clipboard history item.
+  ASSERT_TRUE(item.html_preview().has_value());
+  EXPECT_EQ(item.html_preview().value(), expected_image_model);
 }
 
 }  // namespace ash
diff --git a/ash/clipboard/clipboard_history_util.cc b/ash/clipboard/clipboard_history_util.cc
index fc478c5..3e9b612 100644
--- a/ash/clipboard/clipboard_history_util.cc
+++ b/ash/clipboard/clipboard_history_util.cc
@@ -13,13 +13,18 @@
 #include "ash/shell.h"
 #include "ash/style/dark_light_mode_controller_impl.h"
 #include "base/files/file_path.h"
+#include "base/no_destructor.h"
 #include "base/strings/string_split.h"
 #include "base/strings/utf_string_conversions.h"
+#include "cc/paint/paint_flags.h"
 #include "chromeos/ui/base/file_icon_util.h"
 #include "ui/base/clipboard/clipboard_data.h"
 #include "ui/base/clipboard/custom_data_helper.h"
 #include "ui/base/models/image_model.h"
 #include "ui/chromeos/styles/cros_tokens_color_mappings.h"
+#include "ui/gfx/canvas.h"
+#include "ui/gfx/image/canvas_image_source.h"
+#include "ui/gfx/paint_vector_icon.h"
 
 namespace ash::clipboard_history_util {
 
@@ -27,6 +32,11 @@
 
 constexpr char16_t kFileSystemSourcesType[] = u"fs/sources";
 
+constexpr int kPlaceholderImageWidth = 234;
+constexpr int kPlaceholderImageHeight = 74;
+constexpr int kPlaceholderImageOutlineCornerRadius = 8;
+constexpr int kPlaceholderImageSVGSize = 32;
+
 // The array of formats in order of decreasing priority.
 constexpr ui::ClipboardInternalFormat kPrioritizedFormats[] = {
     ui::ClipboardInternalFormat::kPng,
@@ -38,6 +48,43 @@
     ui::ClipboardInternalFormat::kWeb,
     ui::ClipboardInternalFormat::kCustom};
 
+// Used to draw a placeholder HTML preview to be shown while the real HTML is
+// rendering.
+class UnrenderedHtmlPlaceholderImage : public gfx::CanvasImageSource {
+ public:
+  UnrenderedHtmlPlaceholderImage()
+      : gfx::CanvasImageSource(
+            gfx::Size(kPlaceholderImageWidth, kPlaceholderImageHeight)) {}
+  UnrenderedHtmlPlaceholderImage(const UnrenderedHtmlPlaceholderImage&) =
+      delete;
+  UnrenderedHtmlPlaceholderImage& operator=(
+      const UnrenderedHtmlPlaceholderImage&) = delete;
+  ~UnrenderedHtmlPlaceholderImage() override = default;
+
+  // gfx::CanvasImageSource:
+  void Draw(gfx::Canvas* canvas) override {
+    cc::PaintFlags flags;
+    flags.setStyle(cc::PaintFlags::kFill_Style);
+    flags.setAntiAlias(true);
+    // TODO(b/269680517): Update to use a semantic color token.
+    flags.setColor(gfx::kGoogleGrey100);
+    canvas->DrawRoundRect(
+        /*rect=*/{kPlaceholderImageWidth, kPlaceholderImageHeight},
+        kPlaceholderImageOutlineCornerRadius, flags);
+
+    flags = cc::PaintFlags();
+    flags.setStyle(cc::PaintFlags::kFill_Style);
+    flags.setAntiAlias(true);
+    // TODO(b/269680517): Update to use a semantic color token.
+    const gfx::ImageSkia center_image =
+        gfx::CreateVectorIcon(kUnrenderedHtmlPlaceholderIcon,
+                              kPlaceholderImageSVGSize, gfx::kGoogleGrey600);
+    canvas->DrawImageInt(
+        center_image, (size().width() - center_image.size().width()) / 2,
+        (size().height() - center_image.size().height()) / 2, flags);
+  }
+};
+
 }  // namespace
 
 absl::optional<ui::ClipboardInternalFormat> CalculateMainFormat(
@@ -185,4 +232,10 @@
                                         cros_tokens::kColorPrimary);
 }
 
+ui::ImageModel GetHtmlPreviewPlaceholder() {
+  static base::NoDestructor<ui::ImageModel> model(ui::ImageModel::FromImageSkia(
+      gfx::CanvasImageSource::MakeImageSkia<UnrenderedHtmlPlaceholderImage>()));
+  return *model;
+}
+
 }  // namespace ash::clipboard_history_util
diff --git a/ash/clipboard/clipboard_history_util.h b/ash/clipboard/clipboard_history_util.h
index 9ac17a9..1fef00cb 100644
--- a/ash/clipboard/clipboard_history_util.h
+++ b/ash/clipboard/clipboard_history_util.h
@@ -151,6 +151,10 @@
     const ClipboardHistoryItem* item,
     const std::string& file_name);
 
+// Returns a placeholder image to display for HTML items while their previews
+// render.
+ASH_EXPORT ui::ImageModel GetHtmlPreviewPlaceholder();
+
 }  // namespace clipboard_history_util
 }  // namespace ash
 
diff --git a/ash/clipboard/test_support/clipboard_history_item_builder.cc b/ash/clipboard/test_support/clipboard_history_item_builder.cc
index f065c58..8d8febc 100644
--- a/ash/clipboard/test_support/clipboard_history_item_builder.cc
+++ b/ash/clipboard/test_support/clipboard_history_item_builder.cc
@@ -9,11 +9,9 @@
 #include "base/notreached.h"
 #include "base/pickle.h"
 #include "base/strings/string_util.h"
-#include "base/strings/utf_string_conversions.h"
 #include "ui/base/clipboard/clipboard_data.h"
 #include "ui/base/clipboard/clipboard_format_type.h"
 #include "ui/base/clipboard/custom_data_helper.h"
-#include "ui/gfx/codec/png_codec.h"
 #include "ui/gfx/image/image_unittest_util.h"
 
 namespace ash {
diff --git a/ash/clipboard/views/clipboard_history_bitmap_item_view.cc b/ash/clipboard/views/clipboard_history_bitmap_item_view.cc
index 9a5c4469..dcd6745d 100644
--- a/ash/clipboard/views/clipboard_history_bitmap_item_view.cc
+++ b/ash/clipboard/views/clipboard_history_bitmap_item_view.cc
@@ -16,7 +16,6 @@
 #include "ui/base/l10n/l10n_util.h"
 #include "ui/base/metadata/metadata_impl_macros.h"
 #include "ui/base/models/image_model.h"
-#include "ui/chromeos/styles/cros_tokens_color_mappings.h"
 #include "ui/compositor/layer.h"
 #include "ui/compositor/layer_animation_observer.h"
 #include "ui/compositor/scoped_layer_animation_settings.h"
@@ -116,12 +115,9 @@
   }
 
   void SetImageFromModel() {
-    // TODO(b/267677307): Make cached image an item field, set and updated
-    // directly by the resource manager.
     if (const auto* item = item_resolver_.Run()) {
-      const gfx::ImageSkia& image =
-          *(resource_manager_->GetImageModel(*item).GetImage().ToImageSkia());
-      SetImage(image);
+      DCHECK(item->html_preview().has_value());
+      SetImage(item->html_preview().value());
     }
 
     // When fading in a new image, the ImageView's image has likely changed
diff --git a/ash/components/arc/memory/arc_memory_bridge.cc b/ash/components/arc/memory/arc_memory_bridge.cc
index 1e1c33c9..8e5086cf6 100644
--- a/ash/components/arc/memory/arc_memory_bridge.cc
+++ b/ash/components/arc/memory/arc_memory_bridge.cc
@@ -67,4 +67,14 @@
   memory_instance->DropCaches(std::move(callback));
 }
 
+void ArcMemoryBridge::ReclaimAll(ReclaimAllCallback callback) {
+  auto* const memory_instance =
+      ARC_GET_INSTANCE_FOR_METHOD(arc_bridge_service_->memory(), ReclaimAll);
+  if (!memory_instance) {
+    std::move(callback).Run(0, 0);
+    return;
+  }
+  memory_instance->ReclaimAll(std::move(callback));
+}
+
 }  // namespace arc
diff --git a/ash/components/arc/memory/arc_memory_bridge.h b/ash/components/arc/memory/arc_memory_bridge.h
index 15a08fc2..5b7c387 100644
--- a/ash/components/arc/memory/arc_memory_bridge.h
+++ b/ash/components/arc/memory/arc_memory_bridge.h
@@ -37,6 +37,10 @@
   using DropCachesCallback = base::OnceCallback<void(bool)>;
   void DropCaches(DropCachesCallback callback);
 
+  // Reclaims all pages from all processes.
+  using ReclaimAllCallback = base::OnceCallback<void(uint32_t, uint32_t)>;
+  void ReclaimAll(ReclaimAllCallback);
+
  private:
   THREAD_CHECKER(thread_checker_);
 
diff --git a/ash/components/arc/memory/arc_memory_bridge_unittest.cc b/ash/components/arc/memory/arc_memory_bridge_unittest.cc
index 8cc5526..811da3b 100644
--- a/ash/components/arc/memory/arc_memory_bridge_unittest.cc
+++ b/ash/components/arc/memory/arc_memory_bridge_unittest.cc
@@ -82,5 +82,63 @@
   EXPECT_FALSE(*opt_result);
 }
 
+// Tests that ReclaimAll runs the callback with memory reclaimed from all
+// processes successfully.
+TEST_F(ArcMemoryBridgeTest, ReclaimAll_All_Success) {
+  memory_instance()->set_reclaim_all_result(100, 0);
+
+  absl::optional<uint32_t> reclaimed_result;
+  absl::optional<uint32_t> unreclaimed_result;
+  bridge()->ReclaimAll(
+      base::BindLambdaForTesting([&](uint32_t reclaimed, uint32_t unreclaimed) {
+        reclaimed_result = reclaimed;
+        unreclaimed_result = unreclaimed;
+      }));
+
+  ASSERT_TRUE(reclaimed_result);
+  EXPECT_EQ(*reclaimed_result, 100u);
+  ASSERT_TRUE(unreclaimed_result);
+  EXPECT_EQ(*unreclaimed_result, 0u);
+}
+
+// Tests that ReclaimAll runs the callback with memory reclaimed from some
+// processes successfully.
+TEST_F(ArcMemoryBridgeTest, ReclaimAll_Partial_Success) {
+  memory_instance()->set_reclaim_all_result(50, 50);
+
+  absl::optional<uint32_t> reclaimed_result;
+  absl::optional<uint32_t> unreclaimed_result;
+  bridge()->ReclaimAll(
+      base::BindLambdaForTesting([&](uint32_t reclaimed, uint32_t unreclaimed) {
+        reclaimed_result = reclaimed;
+        unreclaimed_result = unreclaimed;
+      }));
+
+  ASSERT_TRUE(reclaimed_result);
+  EXPECT_EQ(*reclaimed_result, 50u);
+  ASSERT_TRUE(unreclaimed_result);
+  EXPECT_EQ(*unreclaimed_result, 50u);
+}
+
+// Tests that ReclaimAll runs the callback with the instance not available.
+TEST_F(ArcMemoryBridgeTest, ReclaimAll_NoInstance) {
+  // Inject failure.
+  ArcServiceManager::Get()->arc_bridge_service()->memory()->CloseInstance(
+      memory_instance());
+
+  absl::optional<uint32_t> reclaimed_result;
+  absl::optional<uint32_t> unreclaimed_result;
+  bridge()->ReclaimAll(
+      base::BindLambdaForTesting([&](uint32_t reclaimed, uint32_t unreclaimed) {
+        reclaimed_result = reclaimed;
+        unreclaimed_result = unreclaimed;
+      }));
+
+  ASSERT_TRUE(reclaimed_result);
+  EXPECT_EQ(*reclaimed_result, 0u);
+  ASSERT_TRUE(unreclaimed_result);
+  EXPECT_EQ(*unreclaimed_result, 0u);
+}
+
 }  // namespace
 }  // namespace arc
diff --git a/ash/components/arc/mojom/memory.mojom b/ash/components/arc/mojom/memory.mojom
index eacd8dd..9c589f2a 100644
--- a/ash/components/arc/mojom/memory.mojom
+++ b/ash/components/arc/mojom/memory.mojom
@@ -1,12 +1,15 @@
 // 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.
-// Next MinVersion: 1
+// Next MinVersion: 2
 
 module arc.mojom;
 
-// Next method ID: 1
+// Next method ID: 2
 interface MemoryInstance {
   // Drops the guest kernel's page caches. Returns true on success.
   DropCaches@0() => (bool result);
+  // Reclaims all pages from all guest processes. Returns the numbers of
+  // processes that were reclaimed successfully and unsuccessfully.
+  ReclaimAll@1() => (uint32 reclaimed, uint32 unreclaimed);
 };
diff --git a/ash/components/arc/session/arc_session_runner.h b/ash/components/arc/session/arc_session_runner.h
index 8e53cdc..7ff11e5 100644
--- a/ash/components/arc/session/arc_session_runner.h
+++ b/ash/components/arc/session/arc_session_runner.h
@@ -123,6 +123,7 @@
     default_device_scale_factor_ = scale_factor;
   }
 
+  bool use_virtio_blk_data() { return use_virtio_blk_data_; }
   void set_use_virtio_blk_data(bool use_virtio_blk_data) {
     use_virtio_blk_data_ = use_virtio_blk_data;
   }
diff --git a/ash/components/arc/session/arc_vm_client_adapter.h b/ash/components/arc/session/arc_vm_client_adapter.h
index 4ac62d5..8fc132f 100644
--- a/ash/components/arc/session/arc_vm_client_adapter.h
+++ b/ash/components/arc/session/arc_vm_client_adapter.h
@@ -51,6 +51,7 @@
 // The "_2d" in job names below corresponds to "-". Upstart escapes characters
 // that aren't valid in D-Bus object paths with underscore followed by its
 // ascii code in hex. So "arc_2dcreate_2ddata" becomes "arc-create-data".
+constexpr char kArcVmDataMigratorJobName[] = "arcvm_2ddata_2dmigrator";
 constexpr char kArcVmMediaSharingServicesJobName[] =
     "arcvm_2dmedia_2dsharing_2dservices";
 constexpr const char kArcVmPerBoardFeaturesJobName[] =
@@ -65,10 +66,9 @@
 // List of Upstart jobs that can outlive ARC sessions (e.g. after Chrome crash,
 // Chrome restart on a feature flag change) and thus should be stopped at the
 // beginning of the ARCVM boot sequence.
-constexpr std::array<const char*, 4> kArcVmUpstartJobsToBeStoppedOnRestart = {
-    kArcVmPreLoginServicesJobName,
-    kArcVmPostLoginServicesJobName,
-    kArcVmPostVmStartServicesJobName,
+constexpr std::array<const char*, 5> kArcVmUpstartJobsToBeStoppedOnRestart = {
+    kArcVmDataMigratorJobName,         kArcVmPreLoginServicesJobName,
+    kArcVmPostLoginServicesJobName,    kArcVmPostVmStartServicesJobName,
     kArcVmMediaSharingServicesJobName,
 };
 
diff --git a/ash/components/arc/session/arc_vm_client_adapter_unittest.cc b/ash/components/arc/session/arc_vm_client_adapter_unittest.cc
index e1a9daf3..336d2432 100644
--- a/ash/components/arc/session/arc_vm_client_adapter_unittest.cc
+++ b/ash/components/arc/session/arc_vm_client_adapter_unittest.cc
@@ -753,6 +753,18 @@
   StopArcInstance();
 }
 
+// Tests that StartMiniArc() still succeeds even when Upstart fails to stop
+// arcvm-data-migrator.
+TEST_F(ArcVmClientAdapterTest, StartMiniArc_StopArcVmDataMigratorJobFail) {
+  // Inject failure to FakeUpstartClient.
+  InjectUpstartStopJobFailure(kArcVmDataMigratorJobName);
+
+  StartMiniArc();
+  EXPECT_GE(GetTestConciergeClient()->start_arc_vm_call_count(), 1);
+
+  StopArcInstance();
+}
+
 // Tests that StartMiniArc() fails when Upstart fails to start the job.
 TEST_F(ArcVmClientAdapterTest, StartMiniArc_StartArcVmPerBoardFeaturesJobFail) {
   // Inject failure to FakeUpstartClient.
diff --git a/ash/components/arc/test/fake_memory_instance.cc b/ash/components/arc/test/fake_memory_instance.cc
index 241d64b..dfd8b53 100644
--- a/ash/components/arc/test/fake_memory_instance.cc
+++ b/ash/components/arc/test/fake_memory_instance.cc
@@ -15,4 +15,7 @@
   std::move(callback).Run(drop_caches_result_);
 }
 
+void FakeMemoryInstance::ReclaimAll(ReclaimAllCallback callback) {
+  std::move(callback).Run(reclaimed_process_count_, unreclaimed_process_count_);
+}
 }  // namespace arc
diff --git a/ash/components/arc/test/fake_memory_instance.h b/ash/components/arc/test/fake_memory_instance.h
index 0266641..76a298b7 100644
--- a/ash/components/arc/test/fake_memory_instance.h
+++ b/ash/components/arc/test/fake_memory_instance.h
@@ -19,11 +19,21 @@
 
   void set_drop_caches_result(bool result) { drop_caches_result_ = result; }
 
+  void set_reclaim_all_result(uint32_t reclaimed, uint32_t unreclaimed) {
+    reclaimed_process_count_ = reclaimed;
+    unreclaimed_process_count_ = unreclaimed;
+  }
+
   // mojom::MemoryInstance:
   void DropCaches(DropCachesCallback callback) override;
 
+  // mojom::MemoryInstance:
+  void ReclaimAll(ReclaimAllCallback callback) override;
+
  private:
   bool drop_caches_result_ = true;
+  uint32_t reclaimed_process_count_ = 0;
+  uint32_t unreclaimed_process_count_ = 0;
 };
 
 }  // namespace arc
diff --git a/ash/quick_pair/pairing/fast_pair/fast_pair_pairer_impl.cc b/ash/quick_pair/pairing/fast_pair/fast_pair_pairer_impl.cc
index 9b54cf3..3fbba7a5 100644
--- a/ash/quick_pair/pairing/fast_pair/fast_pair_pairer_impl.cc
+++ b/ash/quick_pair/pairing/fast_pair/fast_pair_pairer_impl.cc
@@ -29,6 +29,7 @@
 #include "chromeos/ash/services/quick_pair/public/cpp/fast_pair_message_type.h"
 #include "device/bluetooth/bluetooth_adapter.h"
 #include "device/bluetooth/bluetooth_device.h"
+#include "device/bluetooth/floss/floss_features.h"
 #include "device/bluetooth/public/cpp/bluetooth_address.h"
 #include "third_party/boringssl/src/include/openssl/rand.h"
 
@@ -263,6 +264,18 @@
 
 void FastPairPairerImpl::OnConnectDevice(device::BluetoothDevice* device) {
   QP_LOG(VERBOSE) << __func__;
+
+  if (floss::features::IsFlossEnabled()) {
+    // On Floss, ConnectDevice behaves like CreateDevice. It only creates
+    // a new device object so we have to follow up with actually Pair()-ing
+    // to it.
+    QP_LOG(INFO) << __func__ << " on Floss";
+    device->Pair(/*pairing_delegate=*/this,
+                 base::BindOnce(&FastPairPairerImpl::OnPairConnected,
+                                weak_ptr_factory_.GetWeakPtr()));
+    return;
+  }
+
   RecordProtocolPairingStep(FastPairProtocolPairingSteps::kDeviceConnected,
                             *device_);
   RecordConnectDeviceResult(/*success=*/true);
@@ -775,6 +788,14 @@
     return;
   }
 
+  if (floss::features::IsFlossEnabled()) {
+    // On Floss, Pair is exactly the same as Connect. Therefore we skip calling
+    // Connect().
+    QP_LOG(VERBOSE) << __func__ << ": Skipping Connect on Floss";
+    OnConnected(absl::nullopt);
+    return;
+  }
+
   // We must follow `Pair` with `Connect`. Not all Fast Pair devices initiate
   // a connection following pairing. For device that do initiate connecting
   // following pairing, this may result in `OnConnected` to return a failure,
diff --git a/ash/quick_pair/pairing/fast_pair/fast_pair_pairer_impl_unittest.cc b/ash/quick_pair/pairing/fast_pair/fast_pair_pairer_impl_unittest.cc
index b72bd4a2..8aa6f1c 100644
--- a/ash/quick_pair/pairing/fast_pair/fast_pair_pairer_impl_unittest.cc
+++ b/ash/quick_pair/pairing/fast_pair/fast_pair_pairer_impl_unittest.cc
@@ -42,6 +42,7 @@
 #include "chromeos/ash/services/quick_pair/public/cpp/decrypted_passkey.h"
 #include "chromeos/ash/services/quick_pair/public/cpp/decrypted_response.h"
 #include "chromeos/ash/services/quick_pair/public/cpp/fast_pair_message_type.h"
+#include "device/bluetooth/floss/floss_features.h"
 #include "device/bluetooth/test/mock_bluetooth_adapter.h"
 #include "device/bluetooth/test/mock_bluetooth_device.h"
 #include "testing/gtest/include/gtest/gtest.h"
@@ -632,6 +633,28 @@
   histogram_tester().ExpectTotalCount(kCreateBondTime, 1);
 }
 
+TEST_F(FastPairPairerImplTest, PairByDeviceSuccess_Initial_Floss) {
+  base::test::ScopedFeatureList feature_list;
+  feature_list.InitWithFeatures(
+      /*enabled_features=*/
+      {floss::features::kFlossEnabled},
+      /*disabled_features=*/{});
+
+  Login(user_manager::UserType::USER_TYPE_REGULAR);
+
+  CreateMockDevice(DeviceFastPairVersion::kHigherThanV1,
+                   /*protocol=*/Protocol::kFastPairInitial);
+  AddConnectedHandshake();
+  fake_fast_pair_handshake_->InvokeCallback();
+  CreatePairer();
+  fake_bluetooth_device_ptr_->TriggerPairCallback();
+  EXPECT_EQ(GetPairFailure(), absl::nullopt);
+  ExpectStepMetrics(kProtocolPairingStepInitial,
+                    {FastPairProtocolPairingSteps::kPairingStarted,
+                     FastPairProtocolPairingSteps::kDeviceConnected});
+  histogram_tester().ExpectTotalCount(kCreateBondTime, 1);
+}
+
 TEST_F(FastPairPairerImplTest,
        PairByDeviceSuccess_Initial_AlreadyClassicPaired) {
   Login(user_manager::UserType::USER_TYPE_REGULAR);
@@ -1227,6 +1250,46 @@
                      FastPairProtocolPairingSteps::kDeviceConnected});
 }
 
+TEST_F(FastPairPairerImplTest, PairSuccess_Initial_Floss) {
+  base::test::ScopedFeatureList feature_list;
+  feature_list.InitWithFeatures(
+      /*enabled_features=*/
+      {floss::features::kFlossEnabled},
+      /*disabled_features=*/{});
+
+  Login(user_manager::UserType::USER_TYPE_REGULAR);
+
+  CreateMockDevice(DeviceFastPairVersion::kHigherThanV1,
+                   /*protocol=*/Protocol::kFastPairInitial);
+
+  // When pairing starts, if the classic address can't be resolved to
+  // a device then we pair via address. 'SetGetDeviceNullptr' tells the adapter
+  // to return null when queried for the device to mock this behavior.
+  SetGetDeviceNullptr();
+  AddConnectedHandshake();
+  fake_fast_pair_handshake_->InvokeCallback();
+  CreatePairer();
+  EXPECT_EQ(GetPairFailure(), absl::nullopt);
+  EXPECT_CALL(paired_callback_, Run);
+  SetDecryptPasskeyForSuccess();
+  NotifyConfirmPasskey();
+  RunWritePasskeyCallback(kResponseBytes);
+  // Floss calls Pair instead of finishing after ConnectDevice.
+  fake_bluetooth_device_ptr_->TriggerPairCallback();
+  EXPECT_EQ(GetPairFailure(), absl::nullopt);
+  EXPECT_TRUE(IsDevicePaired());
+  EXPECT_EQ(DeviceFastPairVersion::kHigherThanV1, device_->version().value());
+  adapter_->NotifyDevicePairedChanged(fake_bluetooth_device_ptr_, true);
+  ExpectStepMetrics(kProtocolPairingStepInitial,
+                    {FastPairProtocolPairingSteps::kPairingStarted,
+                     FastPairProtocolPairingSteps::kPairingComplete,
+                     FastPairProtocolPairingSteps::kPasskeyNegotiated,
+                     FastPairProtocolPairingSteps::kRecievedPasskeyResponse,
+                     FastPairProtocolPairingSteps::kPasskeyValidated,
+                     FastPairProtocolPairingSteps::kPasskeyConfirmed,
+                     FastPairProtocolPairingSteps::kDeviceConnected});
+}
+
 TEST_F(FastPairPairerImplTest, BleDeviceLostMidPair) {
   Login(user_manager::UserType::USER_TYPE_REGULAR);
 
diff --git a/ash/system/privacy/privacy_indicators_controller.cc b/ash/system/privacy/privacy_indicators_controller.cc
index 9645290..b2f2ff2 100644
--- a/ash/system/privacy/privacy_indicators_controller.cc
+++ b/ash/system/privacy/privacy_indicators_controller.cc
@@ -79,6 +79,17 @@
   }
 }
 
+void ASH_EXPORT UpdatePrivacyIndicators(
+    const std::string& app_id,
+    absl::optional<std::u16string> app_name,
+    bool is_camera_used,
+    bool is_microphone_used,
+    scoped_refptr<PrivacyIndicatorsNotificationDelegate> delegate) {
+  ModifyPrivacyIndicatorsNotification(app_id, app_name, is_camera_used,
+                                      is_microphone_used, delegate);
+  UpdatePrivacyIndicatorsView(app_id, is_camera_used, is_microphone_used);
+}
+
 std::string GetPrivacyIndicatorsNotificationId(const std::string& app_id) {
   return kPrivacyIndicatorsNotificationIdPrefix + app_id;
 }
diff --git a/ash/system/privacy/privacy_indicators_controller.h b/ash/system/privacy/privacy_indicators_controller.h
index 242673d..28324cf 100644
--- a/ash/system/privacy/privacy_indicators_controller.h
+++ b/ash/system/privacy/privacy_indicators_controller.h
@@ -19,7 +19,11 @@
 namespace ash {
 
 // An interface for the delegate of the privacy indicators notification,
-// handling launching the app and its settings.
+// handling launching the app and its settings. Clients that use privacy
+// indicators should provide this delegate when calling the privacy indicators
+// controller API so that the API can add correct buttons to the notification
+// based on the callbacks provided and appropriate actions are performed when
+// clicking the buttons.
 class ASH_EXPORT PrivacyIndicatorsNotificationDelegate
     : public message_center::NotificationDelegate {
  public:
@@ -69,6 +73,15 @@
   absl::optional<int> launch_settings_button_index_;
 };
 
+// Updates privacy indicators, including the privacy indicators view and the
+// privacy indicator notification(s).
+void ASH_EXPORT UpdatePrivacyIndicators(
+    const std::string& app_id,
+    absl::optional<std::u16string> app_name,
+    bool is_camera_used,
+    bool is_microphone_used,
+    scoped_refptr<PrivacyIndicatorsNotificationDelegate> delegate);
+
 // Get the id of the privacy indicators notification associated with `app_id`.
 std::string ASH_EXPORT
 GetPrivacyIndicatorsNotificationId(const std::string& app_id);
diff --git a/ash/system/privacy/privacy_indicators_tray_item_view.h b/ash/system/privacy/privacy_indicators_tray_item_view.h
index 36ce04c..25f28f7 100644
--- a/ash/system/privacy/privacy_indicators_tray_item_view.h
+++ b/ash/system/privacy/privacy_indicators_tray_item_view.h
@@ -74,6 +74,9 @@
 
   ~PrivacyIndicatorsTrayItemView() override;
 
+  views::ImageView* camera_icon() { return camera_icon_; }
+  views::ImageView* microphone_icon() { return microphone_icon_; }
+
   // Update the view according to the state of camara/microphone access.
   void Update(const std::string& app_id,
               bool is_camera_used,
diff --git a/ash/webui/personalization_app/resources/css/common.css b/ash/webui/personalization_app/resources/css/common.css
index 3453957..79a3b88 100644
--- a/ash/webui/personalization_app/resources/css/common.css
+++ b/ash/webui/personalization_app/resources/css/common.css
@@ -82,12 +82,6 @@
   animation: 2210ms linear var(--animation-delay, 1s) infinite ripple;
 }
 
-cr-button[aria-pressed=true],
-cr-button[aria-selected=true] {
-  background-color: var(--cros-highlight-color);
-  border: 0;
-}
-
 .preview-container {
   border: 1px solid var(--cros-separator-color);
   border-radius: 16px;
diff --git a/ash/webui/personalization_app/resources/css/cros_button_style.css b/ash/webui/personalization_app/resources/css/cros_button_style.css
index 3bf541e..dc0b8914 100644
--- a/ash/webui/personalization_app/resources/css/cros_button_style.css
+++ b/ash/webui/personalization_app/resources/css/cros_button_style.css
@@ -12,43 +12,51 @@
 }
 
 cr-button.primary {
-  background-color: var(--cros-button-background-color-primary);
+  background-color: var(--cros-sys-primary, var(--cros-button-background-color-primary));
   border: 0;
-  --text-color: var(--cros-button-label-color-primary);
-  --ink-color: var(--cros-button-ripple-color-primary);
-  --hover-bg-color: var(--cros-button-background-color-primary-hover-preblended);
-  --disabled-bg: var(--cros-button-background-color-primary-disabled);
-  --disabled-text-color: var(--cros-button-label-color-primary-disabled);
+  --text-color: var(--cros-sys-on_primary, var(--cros-button-label-color-primary));
+  --ink-color: var(--cros-sys-ripple_primary, var(--cros-button-ripple-color-primary));
+  --hover-bg-color: var(--cros-sys-hover_on_prominent, var(--cros-button-background-color-primary-hover-preblended));
+  --disabled-bg: var(--cros-sys-disabled_container, var(--cros-button-background-color-primary-disabled));
+  --disabled-text-color: var(--cros-sys-disabled, var(--cros-button-label-color-primary-disabled));
 }
 
 cr-button.primary:active {
   box-shadow: 0 1px 2px rgba(66, 133, 244, 0.3), 0 1px 3px rgba(66, 133, 244, 0.15);
 }
 
+:host-context(body.jelly-enabled) cr-button.primary:active {
+  box-shadow: none;
+}
+
 cr-button.primary:hover {
-  background-color: var(--cros-button-background-color-primary-hover-preblended);
+  background-color: var(--cros-sys-hover_on_prominent, var(--cros-button-background-color-primary-hover-preblended));
 }
 
 cr-button.secondary {
+  background-color: var(--cros-sys-primary_container, var(--cros-button-background-color-secondary));
   border: 1px solid var(--cros-button-stroke-color-secondary);
-  --text-color: var(--cros-button-label-color-secondary);
+  --text-color: var(--cros-sys-on_primary_container, var(--cros-button-label-color-secondary));
   --border-color: var(--cros-button-stroke-color-secondary);
-  --ink-color: var(--cros-button-ripple-color-secondary);
+  --ink-color: var(--cros-sys-ripple_primary, var(--cros-button-ripple-color-secondary));
   --hover-border-color: var(--cros-button-stroke-color-secondary-hover);
-  --hover-bg-color: var(--cros-button-background-color-secondary-hover);
-  --disabled-text-color: var(--cros-button-label-color-secondary-disabled);
+  --hover-bg-color: var(--cros-sys-hover_on_subtle, var(--cros-button-background-color-secondary-hover));
+  --disabled-text-color: var(--cros-sys-disabled, var(--cros-button-label-color-secondary-disabled));
   --disabled-border-color: var(--cros-button-stroke-color-secondary-disabled);
 }
 
+:host-context(body.jelly-enabled) cr-button.secondary {
+  border: none;
+}
+
 cr-button.secondary:hover {
-  background-color: var(--cros-button-background-color-secondary-hover);
-  border-color: var(--cros-button-stroke-color-secondary-hover);
+  background-color: var(--cros-sys-hover_on_subtle, var(--cros-button-background-color-secondary-hover));
 }
 
 cr-icon-button:focus-visible,
 cr-button:focus-visible {
   box-shadow: none;
-  outline: 2px solid rgba(var(--cros-focus-ring-color-rgb), 0.8);
+  outline: 2px solid var(--cros-sys-focus_ring, rgba(var(--cros-focus-ring-color-rgb), 0.8));
 }
 
 cr-icon-button:hover,
@@ -59,17 +67,8 @@
 
 cr-button[aria-pressed=true],
 cr-button[aria-selected=true] {
-  background-color: var(--cros-button-background-color-primary) !important;
-}
-
-cr-button[aria-pressed=true] .text,
-cr-button[aria-selected=true] .text {
-  color: var(--cros-button-label-color-primary) !important;
-}
-
-cr-button[aria-pressed=true] iron-icon,
-cr-button[aria-selected=true] iron-icon {
-  --iron-icon-fill-color: var(--cros-button-label-color-primary) !important;
+  background-color: var(--cros-sys-highlight_shape, var(--cros-highlight-color));
+  border: none;
 }
 
 iron-icon {
diff --git a/ash/webui/personalization_app/resources/js/theme/personalization_theme_element.html b/ash/webui/personalization_app/resources/js/theme/personalization_theme_element.html
index 1500082..521b835 100644
--- a/ash/webui/personalization_app/resources/js/theme/personalization_theme_element.html
+++ b/ash/webui/personalization_app/resources/js/theme/personalization_theme_element.html
@@ -18,6 +18,16 @@
 
   /* These cr-button styles are added here instead of cros-button-style because
   the specs for these buttons are different from others in the app. */
+  cr-button[aria-pressed=true] .text,
+  cr-button[aria-selected=true] .text {
+    color: var(--cros-button-label-color-primary) !important;
+  }
+
+  cr-button[aria-pressed=true] iron-icon,
+  cr-button[aria-selected=true] iron-icon {
+    --iron-icon-fill-color: var(--cros-button-label-color-primary) !important;
+  }
+
   cr-button .text,
   cr-button:hover .text  {
     color: var(--cros-text-color-secondary);
@@ -35,6 +45,11 @@
         var(--cros-button-primary-ripple-opacity));
   }
 
+  cr-button[aria-pressed=true],
+  cr-button[aria-selected=true] {
+    background-color: var(--cros-button-background-color-primary) !important;
+  }
+
   #selector {
     display: grid;
     gap: 8px;
diff --git a/ash/webui/personalization_app/resources/js/wallpaper/wallpaper_selected_element.html b/ash/webui/personalization_app/resources/js/wallpaper/wallpaper_selected_element.html
index 3838cd3..711efa2 100644
--- a/ash/webui/personalization_app/resources/js/wallpaper/wallpaper_selected_element.html
+++ b/ash/webui/personalization_app/resources/js/wallpaper/wallpaper_selected_element.html
@@ -60,11 +60,6 @@
     --iron-icon-width: 20px;
   }
 
-  cr-button {
-    border-color: var(--cros-button-stroke-color-secondary);
-    border-radius: 16px;
-  }
-
   cr-button + cr-button {
     margin-inline-start: 8px;
   }
@@ -131,12 +126,14 @@
       <template is="dom-if" if="[[shouldShowLayoutOptions_]]">
         <div id="wallpaperOptions">
           <cr-button id="fill" data-layout="FILL" on-click="onClickLayoutIcon_"
+              class="secondary"
               aria-pressed$="[[getFillAriaPressed_(image_)]]">
             <iron-icon icon="[[fillIcon_]]"></iron-icon>
             <div class="text">$i18n{fill}</div>
           </cr-button>
           <cr-button id="center" data-layout="CENTER"
               on-click="onClickLayoutIcon_"
+              class="secondary"
               aria-pressed$="[[getCenterAriaPressed_(image_)]]">
             <iron-icon icon="[[centerIcon_]]"></iron-icon>
             <div class="text">$i18n{center}</div>
@@ -145,7 +142,7 @@
       </template>
       <template is="dom-if" if="[[showCollectionOptions_]]">
         <div id="collectionOptions">
-          <cr-button id="dailyRefresh"
+          <cr-button id="dailyRefresh" class="secondary"
               aria-label="$i18n{ariaLabelChangeDaily}"
               aria-pressed$="[[ariaPressed_]]"
               on-click="onClickDailyRefreshToggle_"
@@ -153,7 +150,7 @@
             <iron-icon icon="[[dailyRefreshIcon_]]"></iron-icon>
             <div class="text">$i18n{changeDaily}</div>
           </cr-button>
-          <cr-button id="refreshWallpaper"
+          <cr-button id="refreshWallpaper" class="secondary"
               aria-label="$i18n{ariaLabelRefresh}"
               on-click="onClickUpdateDailyRefreshWallpaper_"
               hidden$="[[!showRefreshButton_]]">
diff --git a/ash/wm/collision_detection/collision_detection_utils.cc b/ash/wm/collision_detection/collision_detection_utils.cc
index 2f06378..3171538 100644
--- a/ash/wm/collision_detection/collision_detection_utils.cc
+++ b/ash/wm/collision_detection/collision_detection_utils.cc
@@ -5,15 +5,11 @@
 #include "ash/wm/collision_detection/collision_detection_utils.h"
 
 #include "ash/app_list/app_list_controller_impl.h"
-#include "ash/capture_mode/capture_mode_camera_controller.h"
 #include "ash/capture_mode/capture_mode_controller.h"
-#include "ash/capture_mode/capture_mode_session.h"
-#include "ash/constants/ash_features.h"
 #include "ash/keyboard/ui/keyboard_ui_controller.h"
 #include "ash/public/cpp/shelf_types.h"
 #include "ash/public/cpp/shell_window_ids.h"
 #include "ash/shelf/shelf.h"
-#include "ash/shelf/shelf_layout_manager.h"
 #include "ash/shell.h"
 #include "ash/system/message_center/ash_message_popup_collection.h"
 #include "ash/wm/work_area_insets.h"
@@ -178,26 +174,10 @@
         /*parent=*/root_window));
   }
 
-  // Check the capture bar if capture mode is active.
-  auto* capture_mode_controller = CaptureModeController::Get();
-  if (capture_mode_controller->IsActive()) {
-    aura::Window* capture_bar_window =
-        capture_mode_controller->capture_mode_session()
-            ->capture_mode_bar_widget()
-            ->GetNativeWindow();
-    rects.push_back(ComputeCollisionRectFromBounds(
-        capture_bar_window->GetTargetBounds(), capture_bar_window->parent()));
-  }
-
-  // Check the camera preview if it exists.
-  auto* camera_preview_widget =
-      capture_mode_controller->camera_controller()->camera_preview_widget();
-  if (camera_preview_widget && camera_preview_widget->IsVisible()) {
-    aura::Window* camera_preview_window =
-        camera_preview_widget->GetNativeWindow();
-    rects.push_back(
-        ComputeCollisionRectFromBounds(camera_preview_window->GetTargetBounds(),
-                                       camera_preview_window->parent()));
+  for (auto* window :
+       CaptureModeController::Get()->GetWindowsForCollisionAvoidance()) {
+    rects.push_back(ComputeCollisionRectFromBounds(window->GetTargetBounds(),
+                                                   window->parent()));
   }
 
   // Avoid clamshell-mode launcher bubble.
diff --git a/ash/wm/desks/desks_bar_view.cc b/ash/wm/desks/desks_bar_view.cc
index 12317644..3258b44 100644
--- a/ash/wm/desks/desks_bar_view.cc
+++ b/ash/wm/desks/desks_bar_view.cc
@@ -649,8 +649,8 @@
           std::make_unique<CrOSNextDeskIconButton>(
               this, &kDesksTemplatesIcon,
               l10n_util::GetStringUTF16(button_text_id),
-              cros_tokens::kCrosSysOnPrimaryContainer,
-              cros_tokens::kCrosSysSystemPrimaryContainer,
+              cros_tokens::kCrosSysOnSecondaryContainer,
+              cros_tokens::kCrosSysInversePrimary,
               /*initially_enabled=*/true,
               base::BindRepeating(&DesksBarView::OnLibraryButtonPressed,
                                   base::Unretained(this))));
diff --git a/ash/wm/multitask_menu_nudge_controller_unittest.cc b/ash/wm/multitask_menu_nudge_controller_unittest.cc
index 8343523..9019962e 100644
--- a/ash/wm/multitask_menu_nudge_controller_unittest.cc
+++ b/ash/wm/multitask_menu_nudge_controller_unittest.cc
@@ -26,6 +26,7 @@
 #include "chromeos/ui/frame/multitask_menu/multitask_button.h"
 #include "chromeos/ui/frame/multitask_menu/multitask_menu.h"
 #include "chromeos/ui/wm/features.h"
+#include "ui/display/screen.h"
 #include "ui/gfx/geometry/size.h"
 #include "ui/views/widget/any_widget_observer.h"
 #include "ui/wm/core/window_util.h"
@@ -192,6 +193,37 @@
   EXPECT_FALSE(GetNudgeWidgetForWindow(window.get()));
 }
 
+// Tests that the nudge bounds is within display bounds when the associated
+// window is maximized.
+TEST_F(MultitaskMenuNudgeControllerTest, ClamshellNudgeBounds) {
+  auto window = CreateAppWindow(gfx::Rect(300, 300));
+  ASSERT_TRUE(GetNudgeWidgetForWindow(window.get()));
+
+  WindowState::Get(window.get())->Maximize();
+  auto* nudge_widget = GetNudgeWidgetForWindow(window.get());
+  ASSERT_TRUE(nudge_widget);
+  EXPECT_TRUE(display::Screen::GetScreen()
+                  ->GetDisplayNearestView(window.get())
+                  .work_area()
+                  .Contains(nudge_widget->GetWindowBoundsInScreen()));
+
+  // Cleanup some state for the next test.
+  FireDismissNudgeTimer(window.get());
+  window.reset();
+  test_clock_.Advance(base::Hours(26));
+
+  // Test the same thing in RTL.
+  base::i18n::SetRTLForTesting(true);
+  window = CreateAppWindow(gfx::Rect(300, 300));
+  WindowState::Get(window.get())->Maximize();
+  nudge_widget = GetNudgeWidgetForWindow(window.get());
+  ASSERT_TRUE(nudge_widget);
+  EXPECT_TRUE(display::Screen::GetScreen()
+                  ->GetDisplayNearestView(window.get())
+                  .work_area()
+                  .Contains(nudge_widget->GetWindowBoundsInScreen()));
+}
+
 TEST_F(MultitaskMenuNudgeControllerTest, NudgeMultiDisplay) {
   UpdateDisplay("800x700,801+0-800x700");
   ASSERT_EQ(2u, Shell::GetAllRootWindows().size());
@@ -199,27 +231,21 @@
   auto window = CreateAppWindow(gfx::Rect(300, 300));
   ASSERT_TRUE(GetNudgeWidgetForWindow(window.get()));
 
+  // Move the window using the shortcut. Test that the nudge is on the correct
+  // display.
+  display_move_window_util::HandleMoveActiveWindowBetweenDisplays();
+  EXPECT_EQ(Shell::GetAllRootWindows()[1], GetNudgeWidgetForWindow(window.get())
+                                               ->GetNativeWindow()
+                                               ->GetRootWindow());
+
   // Drag from the caption the window to the other display. The nudge should be
-  // on the other display, even though the window is not (the window stays
-  // offscreen and a mirrored version called the drag window is the one on the
-  // secondary display).
+  // gone, but there is no crash.
+  display_move_window_util::HandleMoveActiveWindowBetweenDisplays();
   auto* event_generator = GetEventGenerator();
   event_generator->set_current_screen_location(gfx::Point(150, 10));
   event_generator->PressLeftButton();
-  event_generator->MoveMouseTo(gfx::Point(900, 0));
-  EXPECT_EQ(Shell::GetAllRootWindows()[1], GetNudgeWidgetForWindow(window.get())
-                                               ->GetNativeWindow()
-                                               ->GetRootWindow());
-
-  event_generator->ReleaseLeftButton();
-  EXPECT_EQ(Shell::GetAllRootWindows()[1], GetNudgeWidgetForWindow(window.get())
-                                               ->GetNativeWindow()
-                                               ->GetRootWindow());
-
-  display_move_window_util::HandleMoveActiveWindowBetweenDisplays();
-  EXPECT_EQ(Shell::GetAllRootWindows()[0], GetNudgeWidgetForWindow(window.get())
-                                               ->GetNativeWindow()
-                                               ->GetRootWindow());
+  event_generator->MoveMouseTo(gfx::Point(1200, 0));
+  EXPECT_FALSE(GetNudgeWidgetForWindow(window.get()));
 }
 
 // Tests that based on preferences (shown count, and last shown time), the nudge
diff --git a/base/allocator/partition_alloc_support.cc b/base/allocator/partition_alloc_support.cc
index d31d072..38b675d 100644
--- a/base/allocator/partition_alloc_support.cc
+++ b/base/allocator/partition_alloc_support.cc
@@ -466,17 +466,30 @@
 // are all the dangling raw_ptr occurrences in a table.
 std::string ExtractDanglingPtrSignature(std::string stacktrace) {
   std::vector<StringPiece> lines = SplitStringPiece(
-      stacktrace, "\r\n", TRIM_WHITESPACE, SPLIT_WANT_NONEMPTY);
+      stacktrace, "\r\n", KEEP_WHITESPACE, SPLIT_WANT_NONEMPTY);
 
   // We are looking for the callers of the function releasing the raw_ptr and
   // freeing memory:
   const StringPiece callees[] = {
+      // Common signatures
+      "internal::PartitionFree",
+      "base::(anonymous namespace)::FreeFn",
+
+      // Linux signatures
       "internal::RawPtrBackupRefImpl<>::ReleaseInternal()",
-      "internal::PartitionFree()",
       "base::RefCountedThreadSafe<>::Release()",
-      "base::(anonymous namespace)::FreeFn()",
+
+      // Windows signatures
+      "internal::RawPtrBackupRefImpl<0>::ReleaseInternal",
+      "_free_base",
+      // Windows stack traces are prefixed with "Backtrace:"
+      "Backtrace:",
+
+      // Mac signatures
+      "internal::RawPtrBackupRefImpl<false>::ReleaseInternal",
+
       // Task traces are prefixed with "Task trace:" in
-      // https://crsrc.org/c/base/debug/task_trace.cc;drc=82fbec846172f4e7ea576ad4f2f7f3d082dcb13b;l=77
+      // |TaskTrace::OutputToStream|
       "Task trace:",
   };
   size_t caller_index = 0;
@@ -492,20 +505,56 @@
   }
   StringPiece caller = lines[caller_index];
 
-  // |callers| follows the following format:
-  //
-  //    #4 0x56051fe3404b content::GeneratedCodeCache::DidCreateBackend()
-  //    -- -------------- -----------------------------------------------
-  // Depth Address        Function
-
-  size_t address_start = caller.find(' ');
-  size_t function_start = caller.find(' ', address_start + 1);
-
-  if (address_start == caller.npos || function_start == caller.npos) {
+  if (caller.empty()) {
     return "invalid_format";
   }
 
-  return std::string(caller.substr(function_start + 1));
+  // On Posix platforms |callers| follows the following format:
+  //
+  // #<index> <address> <symbol>
+  //
+  // See https://crsrc.org/c/base/debug/stack_trace_posix.cc
+  if (caller[0] == '#') {
+    const size_t address_start = caller.find(' ');
+    const size_t function_start = caller.find(' ', address_start + 1);
+
+    if (address_start == caller.npos || function_start == caller.npos) {
+      return "invalid_format";
+    }
+
+    return std::string(caller.substr(function_start + 1));
+  }
+
+  // On Windows platforms |callers| follows the following format:
+  //
+  // \t<symbol> [0x<address>]+<displacement>(<filename>:<line>)
+  //
+  // See https://crsrc.org/c/base/debug/stack_trace_win.cc
+  if (caller[0] == '\t') {
+    const size_t symbol_start = 1;
+    const size_t symbol_end = caller.find(' ');
+    if (symbol_end == caller.npos) {
+      return "invalid_format";
+    }
+    return std::string(caller.substr(symbol_start, symbol_end - symbol_start));
+  }
+
+  // On Mac platforms |callers| follows the following format:
+  //
+  // <index> <library> 0x<address> <symbol> + <line>
+  //
+  // See https://crsrc.org/c/base/debug/stack_trace_posix.cc
+  if (caller[0] >= '0' && caller[0] <= '9') {
+    const size_t address_start = caller.find("0x");
+    const size_t symbol_start = caller.find(' ', address_start + 1) + 1;
+    const size_t symbol_end = caller.find(' ', symbol_start);
+    if (symbol_start == caller.npos || symbol_end == caller.npos) {
+      return "invalid_format";
+    }
+    return std::string(caller.substr(symbol_start, symbol_end - symbol_start));
+  }
+
+  return "invalid_format";
 }
 
 std::string ExtractDanglingPtrSignature(debug::TaskTrace task_trace) {
@@ -1190,4 +1239,11 @@
         // BUILDFLAG(USE_PARTITION_ALLOC_AS_MALLOC)
 }
 
+#if BUILDFLAG(ENABLE_DANGLING_RAW_PTR_CHECKS)
+std::string PartitionAllocSupport::ExtractDanglingPtrSignatureForTests(
+    std::string stacktrace) {
+  return ExtractDanglingPtrSignature(stacktrace);
+}
+#endif
+
 }  // namespace base::allocator
diff --git a/base/allocator/partition_alloc_support.h b/base/allocator/partition_alloc_support.h
index ead3d47d..cd4ca93c 100644
--- a/base/allocator/partition_alloc_support.h
+++ b/base/allocator/partition_alloc_support.h
@@ -75,6 +75,11 @@
   void OnForegrounded(bool has_main_frame);
   void OnBackgrounded();
 
+#if BUILDFLAG(ENABLE_DANGLING_RAW_PTR_CHECKS)
+  static std::string ExtractDanglingPtrSignatureForTests(
+      std::string stacktrace);
+#endif
+
   static PartitionAllocSupport* Get() {
     static auto* singleton = new PartitionAllocSupport();
     return singleton;
diff --git a/base/allocator/partition_alloc_support_unittest.cc b/base/allocator/partition_alloc_support_unittest.cc
index 34b7b8bc..c1aa35b 100644
--- a/base/allocator/partition_alloc_support_unittest.cc
+++ b/base/allocator/partition_alloc_support_unittest.cc
@@ -327,6 +327,57 @@
   partition_alloc::GetDanglingRawPtrReleasedFn()(42);
 }
 
+TEST(PartitionAllocDanglingPtrChecks,
+     ExtractDanglingPtrSignatureMacStackTrace) {
+  const std::string stack_trace_output =
+      "0   lib_1  0x0000000115fdfa12 base::F1(**) + 18\r\n"
+      "1   lib_1  0x0000000115ec0043 base::F2() + 19\r\n"
+      "2   lib_1  0x000000011601fb01 "
+      "allocator_shim::internal::PartitionFree(foo) + 13265\r\n"
+      "3   lib_1  0x0000000114831027 base::F3(bar) + 42\r\n"
+      "4   lib_2  0x00000001148eae35 base::F4() + 437\r\n";
+  EXPECT_EQ("base::F3(bar)",
+            PartitionAllocSupport::ExtractDanglingPtrSignatureForTests(
+                stack_trace_output));
+}
+
+TEST(PartitionAllocDanglingPtrChecks, ExtractDanglingPtrSignatureMacTaskTrace) {
+  const std::string task_trace_output =
+      "Task trace:\r\n"
+      "0   lib_1  0x00000001161fd431 base::F1() + 257\r\n"
+      "1   lib_1  0x0000000115a49404 base::F2() + 68\r\n";
+  EXPECT_EQ("base::F1()",
+            PartitionAllocSupport::ExtractDanglingPtrSignatureForTests(
+                task_trace_output));
+}
+
+TEST(PartitionAllocDanglingPtrChecks,
+     ExtractDanglingPtrSignatureWindowsStackTrace) {
+  const std::string stack_trace_output =
+      "Backtrace:\r\n"
+      "\tbase::F1 [0x055643C3+19] (o:\\base\\F1.cc:329)\r\n"
+      "\tallocator_shim::internal::PartitionFree [0x0648F87B+5243] "
+      "(o:\\path.cc:441)\r\n"
+      "\t_free_base [0x0558475D+29] (o:\\file_path.cc:142)\r\n"
+      "\tbase::F2 [0x04E5B317+23] (o:\\base\\F2.cc:91)\r\n"
+      "\tbase::F3 [0x04897800+544] (o:\\base\\F3.cc:638)\r\n";
+  EXPECT_EQ("base::F2",
+            PartitionAllocSupport::ExtractDanglingPtrSignatureForTests(
+                stack_trace_output));
+}
+
+TEST(PartitionAllocDanglingPtrChecks,
+     ExtractDanglingPtrSignatureWindowsTaskTrace) {
+  const std::string task_trace_output =
+      "Task trace:\r\n"
+      "Backtrace:\r\n"
+      "\tbase::F1 [0x049068A3+813] (o:\\base\\F1.cc:207)\r\n"
+      "\tbase::F2 [0x0490614C+192] (o:\\base\\F2.cc:116)\r\n";
+  EXPECT_EQ("base::F1",
+            PartitionAllocSupport::ExtractDanglingPtrSignatureForTests(
+                task_trace_output));
+}
+
 #endif
 
 }  // namespace allocator
diff --git a/base/feature_list.cc b/base/feature_list.cc
index a924a1e..468b1974 100644
--- a/base/feature_list.cc
+++ b/base/feature_list.cc
@@ -668,7 +668,12 @@
     const Feature& feature) const {
   DCHECK(initialized_);
   DCHECK(IsValidFeatureOrFieldTrialName(feature.name)) << feature.name;
-  DCHECK(CheckFeatureIdentity(feature)) << feature.name;
+  DCHECK(CheckFeatureIdentity(feature))
+      << feature.name
+      << " has multiple definitions. Either it is defined more than once in "
+         "code or (for component builds) the code is built into multiple "
+         "components (shared libraries) without a corresponding export "
+         "statement";
 
   // If caching is disabled, always perform the full lookup.
   if (!g_cache_override_state)
diff --git a/base/test/data/activity_analyzer_fuzzer/9288a7d7d26f1cc5a3d0a62632827b3b6ade5206 b/base/test/data/activity_analyzer_fuzzer/9288a7d7d26f1cc5a3d0a62632827b3b6ade5206
deleted file mode 100644
index a87ef6b..0000000
--- a/base/test/data/activity_analyzer_fuzzer/9288a7d7d26f1cc5a3d0a62632827b3b6ade5206
+++ /dev/null
Binary files differ
diff --git a/base/test/data/activity_analyzer_fuzzer/b115be9accdbf58ce8b1c951ac8a6376fdfc2b32 b/base/test/data/activity_analyzer_fuzzer/b115be9accdbf58ce8b1c951ac8a6376fdfc2b32
deleted file mode 100644
index 5aa8553..0000000
--- a/base/test/data/activity_analyzer_fuzzer/b115be9accdbf58ce8b1c951ac8a6376fdfc2b32
+++ /dev/null
Binary files differ
diff --git a/base/test/data/activity_analyzer_fuzzer/cd42dd22f7cce7f6ca59be23d77c5eb749585641 b/base/test/data/activity_analyzer_fuzzer/cd42dd22f7cce7f6ca59be23d77c5eb749585641
deleted file mode 100644
index eb810b3..0000000
--- a/base/test/data/activity_analyzer_fuzzer/cd42dd22f7cce7f6ca59be23d77c5eb749585641
+++ /dev/null
Binary files differ
diff --git a/base/test/data/activity_analyzer_fuzzer/ce037ea9196e360bf9be0a160c57f2c705d739c5 b/base/test/data/activity_analyzer_fuzzer/ce037ea9196e360bf9be0a160c57f2c705d739c5
deleted file mode 100644
index b6bf749..0000000
--- a/base/test/data/activity_analyzer_fuzzer/ce037ea9196e360bf9be0a160c57f2c705d739c5
+++ /dev/null
Binary files differ
diff --git a/base/test/data/activity_analyzer_fuzzer/e3d08b4e17e3558cacddfe5f39565e5ed13a0159 b/base/test/data/activity_analyzer_fuzzer/e3d08b4e17e3558cacddfe5f39565e5ed13a0159
deleted file mode 100644
index 60db68e..0000000
--- a/base/test/data/activity_analyzer_fuzzer/e3d08b4e17e3558cacddfe5f39565e5ed13a0159
+++ /dev/null
Binary files differ
diff --git a/build/fuchsia/gcs_download_test.py b/build/fuchsia/gcs_download_test.py
index 3c5adaa..7aebe7d8 100755
--- a/build/fuchsia/gcs_download_test.py
+++ b/build/fuchsia/gcs_download_test.py
@@ -3,6 +3,7 @@
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
+import os
 import subprocess
 import sys
 import tarfile
@@ -27,18 +28,23 @@
 @mock.patch('tempfile.TemporaryDirectory')
 @mock.patch('subprocess.run')
 @mock.patch('tarfile.open')
+@unittest.skipIf(os.name == 'nt', 'Fuchsia tests not supported on Windows')
 class TestDownloadAndUnpackFromCloudStorage(unittest.TestCase):
   def testHappyPath(self, mock_tarfile, mock_run, mock_tmp_dir):
     mock_run.return_value = _mock_task()
-    mock_tmp_dir.return_value.__enter__.return_value = '/some/tmp/dir'
+
+    tmp_dir = os.path.join('some', 'tmp', 'dir')
+    mock_tmp_dir.return_value.__enter__.return_value = tmp_dir
 
     mock_seq = mock.Mock()
     mock_seq.attach_mock(mock_run, 'Run')
     mock_seq.attach_mock(mock_tarfile, 'Untar')
     mock_seq.attach_mock(mock_tmp_dir, 'MkTmpD')
 
-    DownloadAndUnpackFromCloudStorage('gs://some/url', 'output/dir')
+    output_dir = os.path.join('output', 'dir')
+    DownloadAndUnpackFromCloudStorage('gs://some/url', output_dir)
 
+    image_tgz_path = os.path.join(tmp_dir, 'image.tgz')
     mock_seq.assert_has_calls([
         mock.call.MkTmpD(),
         mock.call.MkTmpD().__enter__(),
@@ -47,8 +53,8 @@
                       stdout=subprocess.PIPE,
                       check=True,
                       encoding='utf-8'),
-        mock.call.Untar(name='/some/tmp/dir/image.tgz', mode='r|gz'),
-        mock.call.Untar().extractall(path='output/dir'),
+        mock.call.Untar(name=image_tgz_path, mode='r|gz'),
+        mock.call.Untar().extractall(path=output_dir),
         mock.call.MkTmpD().__exit__(None, None, None)
     ],
                               any_order=False)
@@ -56,8 +62,7 @@
     # Verify cmd.
     cmd = ' '.join(mock_run.call_args[0][0])
     self.assertRegex(
-        cmd,
-        r'.*python\s.*gsutil.py\s+cp\s+gs://some/url\s+/some/tmp/dir/image.tgz')
+        cmd, r'.*python\s.*gsutil.py\s+cp\s+gs://some/url\s+' + image_tgz_path)
 
   def testFailedTarOpen(self, mock_tarfile, mock_run, mock_tmp_dir):
     mock_run.return_value = _mock_task(stderr='some error')
diff --git a/cc/base/container_util.h b/cc/base/container_util.h
index a7d8bdd..b53f2b6 100644
--- a/cc/base/container_util.h
+++ b/cc/base/container_util.h
@@ -5,6 +5,7 @@
 #ifndef CC_BASE_CONTAINER_UTIL_H_
 #define CC_BASE_CONTAINER_UTIL_H_
 
+#include <utility>
 
 namespace cc {
 
diff --git a/cc/base/features.cc b/cc/base/features.cc
index 865158f..59fe321 100644
--- a/cc/base/features.cc
+++ b/cc/base/features.cc
@@ -44,9 +44,15 @@
              "RemoveMobileViewportDoubleTap",
              base::FEATURE_ENABLED_BY_DEFAULT);
 
+// Design doc: bit.ly/scrollunification
+// Disabled on Windows due to crbug.com/1378021.
 BASE_FEATURE(kScrollUnification,
              "ScrollUnification",
+#if BUILDFLAG(IS_WIN)
              base::FEATURE_DISABLED_BY_DEFAULT);
+#else
+             base::FEATURE_ENABLED_BY_DEFAULT);
+#endif
 
 BASE_FEATURE(kSchedulerSmoothnessForAnimatedScrolls,
              "SmoothnessModeForAnimatedScrolls",
diff --git a/cc/base/rolling_time_delta_history.cc b/cc/base/rolling_time_delta_history.cc
index d967f94..429a125 100644
--- a/cc/base/rolling_time_delta_history.cc
+++ b/cc/base/rolling_time_delta_history.cc
@@ -5,6 +5,7 @@
 #include <stddef.h>
 
 #include <cmath>
+#include <utility>
 
 #include "cc/base/rolling_time_delta_history.h"
 
diff --git a/cc/base/unique_notifier.cc b/cc/base/unique_notifier.cc
index d714237..2717ab3 100644
--- a/cc/base/unique_notifier.cc
+++ b/cc/base/unique_notifier.cc
@@ -4,6 +4,8 @@
 
 #include "cc/base/unique_notifier.h"
 
+#include <utility>
+
 #include "base/functional/bind.h"
 #include "base/functional/callback_helpers.h"
 #include "base/location.h"
diff --git a/cc/benchmarks/micro_benchmark_controller_impl.cc b/cc/benchmarks/micro_benchmark_controller_impl.cc
index fcaaeef..2250c2ac 100644
--- a/cc/benchmarks/micro_benchmark_controller_impl.cc
+++ b/cc/benchmarks/micro_benchmark_controller_impl.cc
@@ -5,6 +5,7 @@
 #include "cc/benchmarks/micro_benchmark_controller_impl.h"
 
 #include <string>
+#include <utility>
 
 #include "base/containers/cxx20_erase.h"
 #include "base/functional/callback.h"
diff --git a/cc/benchmarks/micro_benchmark_controller_impl.h b/cc/benchmarks/micro_benchmark_controller_impl.h
index 5cb033f..8a8f860 100644
--- a/cc/benchmarks/micro_benchmark_controller_impl.h
+++ b/cc/benchmarks/micro_benchmark_controller_impl.h
@@ -5,6 +5,7 @@
 #ifndef CC_BENCHMARKS_MICRO_BENCHMARK_CONTROLLER_IMPL_H_
 #define CC_BENCHMARKS_MICRO_BENCHMARK_CONTROLLER_IMPL_H_
 
+#include <memory>
 #include <string>
 #include <vector>
 
diff --git a/cc/debug/rendering_stats.cc b/cc/debug/rendering_stats.cc
index 4202758..fd36dc85 100644
--- a/cc/debug/rendering_stats.cc
+++ b/cc/debug/rendering_stats.cc
@@ -4,6 +4,8 @@
 
 #include "cc/debug/rendering_stats.h"
 
+#include <utility>
+
 namespace cc {
 
 RenderingStats::TimeDeltaList::TimeDeltaList() = default;
diff --git a/cc/input/layer_selection_bound.h b/cc/input/layer_selection_bound.h
index 35a1301..87e04a6 100644
--- a/cc/input/layer_selection_bound.h
+++ b/cc/input/layer_selection_bound.h
@@ -5,6 +5,8 @@
 #ifndef CC_INPUT_LAYER_SELECTION_BOUND_H_
 #define CC_INPUT_LAYER_SELECTION_BOUND_H_
 
+#include <string>
+
 #include "cc/cc_export.h"
 #include "components/viz/common/quads/selection.h"
 #include "ui/gfx/geometry/point.h"
diff --git a/cc/layers/layer_list_iterator_unittest.cc b/cc/layers/layer_list_iterator_unittest.cc
index acbba594..218e9bd 100644
--- a/cc/layers/layer_list_iterator_unittest.cc
+++ b/cc/layers/layer_list_iterator_unittest.cc
@@ -5,6 +5,8 @@
 #include "cc/layers/layer_list_iterator.h"
 
 #include <memory>
+#include <unordered_map>
+#include <utility>
 
 #include "base/containers/adapters.h"
 #include "cc/animation/animation_host.h"
diff --git a/cc/paint/decode_stashing_image_provider.cc b/cc/paint/decode_stashing_image_provider.cc
index c1b58af..03e2c4f 100644
--- a/cc/paint/decode_stashing_image_provider.cc
+++ b/cc/paint/decode_stashing_image_provider.cc
@@ -4,6 +4,8 @@
 
 #include "cc/paint/decode_stashing_image_provider.h"
 
+#include <utility>
+
 namespace cc {
 DecodeStashingImageProvider::DecodeStashingImageProvider(
     ImageProvider* source_provider)
diff --git a/cc/paint/paint_image_generator.cc b/cc/paint/paint_image_generator.cc
index 0ecaf26..890055c 100644
--- a/cc/paint/paint_image_generator.cc
+++ b/cc/paint/paint_image_generator.cc
@@ -2,6 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+#include <utility>
 #include <vector>
 
 #include "cc/paint/paint_image_generator.h"
diff --git a/cc/paint/transfer_cache_serialize_helper.h b/cc/paint/transfer_cache_serialize_helper.h
index a4330ee..49c85792 100644
--- a/cc/paint/transfer_cache_serialize_helper.h
+++ b/cc/paint/transfer_cache_serialize_helper.h
@@ -6,6 +6,7 @@
 #define CC_PAINT_TRANSFER_CACHE_SERIALIZE_HELPER_H_
 
 #include <set>
+#include <utility>
 #include <vector>
 
 #include "cc/paint/paint_export.h"
diff --git a/cc/raster/tile_task.cc b/cc/raster/tile_task.cc
index d5313ce..9312d7b9 100644
--- a/cc/raster/tile_task.cc
+++ b/cc/raster/tile_task.cc
@@ -4,6 +4,8 @@
 
 #include "cc/raster/tile_task.h"
 
+#include <utility>
+
 #include "base/check.h"
 
 namespace cc {
diff --git a/cc/resources/cross_thread_shared_bitmap.cc b/cc/resources/cross_thread_shared_bitmap.cc
index ff957302..84a08f7 100644
--- a/cc/resources/cross_thread_shared_bitmap.cc
+++ b/cc/resources/cross_thread_shared_bitmap.cc
@@ -4,6 +4,7 @@
 
 #include "cc/resources/cross_thread_shared_bitmap.h"
 
+#include <utility>
 namespace cc {
 
 CrossThreadSharedBitmap::CrossThreadSharedBitmap(const viz::SharedBitmapId& id,
diff --git a/cc/resources/shared_bitmap_id_registrar.cc b/cc/resources/shared_bitmap_id_registrar.cc
index 839ee84..e6f43d2 100644
--- a/cc/resources/shared_bitmap_id_registrar.cc
+++ b/cc/resources/shared_bitmap_id_registrar.cc
@@ -4,6 +4,8 @@
 
 #include "cc/resources/shared_bitmap_id_registrar.h"
 
+#include <utility>
+
 #include "cc/layers/texture_layer.h"
 
 namespace cc {
diff --git a/cc/test/fake_layer_tree_frame_sink_client.h b/cc/test/fake_layer_tree_frame_sink_client.h
index 2ea2dc86..0f16a23 100644
--- a/cc/test/fake_layer_tree_frame_sink_client.h
+++ b/cc/test/fake_layer_tree_frame_sink_client.h
@@ -5,9 +5,10 @@
 #ifndef CC_TEST_FAKE_LAYER_TREE_FRAME_SINK_CLIENT_H_
 #define CC_TEST_FAKE_LAYER_TREE_FRAME_SINK_CLIENT_H_
 
+#include <vector>
+
 #include "base/memory/raw_ptr.h"
 #include "cc/trees/layer_tree_frame_sink_client.h"
-
 #include "cc/trees/managed_memory_policy.h"
 #include "components/viz/common/hit_test/hit_test_region_list.h"
 
diff --git a/cc/test/fake_mask_layer_impl.h b/cc/test/fake_mask_layer_impl.h
index b564a1c1..b4c8984 100644
--- a/cc/test/fake_mask_layer_impl.h
+++ b/cc/test/fake_mask_layer_impl.h
@@ -5,6 +5,8 @@
 #ifndef CC_TEST_FAKE_MASK_LAYER_IMPL_H_
 #define CC_TEST_FAKE_MASK_LAYER_IMPL_H_
 
+#include <memory>
+
 #include "cc/layers/picture_layer_impl.h"
 #include "cc/raster/raster_source.h"
 
diff --git a/cc/test/fake_picture_layer_tiling_client.h b/cc/test/fake_picture_layer_tiling_client.h
index f292d8d0..bb86cc6 100644
--- a/cc/test/fake_picture_layer_tiling_client.h
+++ b/cc/test/fake_picture_layer_tiling_client.h
@@ -5,6 +5,8 @@
 #ifndef CC_TEST_FAKE_PICTURE_LAYER_TILING_CLIENT_H_
 #define CC_TEST_FAKE_PICTURE_LAYER_TILING_CLIENT_H_
 
+#include <memory>
+
 #include "base/memory/raw_ptr.h"
 #include "cc/raster/raster_source.h"
 #include "cc/test/fake_tile_manager_client.h"
diff --git a/cc/test/fake_scoped_ui_resource.h b/cc/test/fake_scoped_ui_resource.h
index e55b86b2..4d5cf2cc 100644
--- a/cc/test/fake_scoped_ui_resource.h
+++ b/cc/test/fake_scoped_ui_resource.h
@@ -5,6 +5,8 @@
 #ifndef CC_TEST_FAKE_SCOPED_UI_RESOURCE_H_
 #define CC_TEST_FAKE_SCOPED_UI_RESOURCE_H_
 
+#include <memory>
+
 #include "cc/resources/scoped_ui_resource.h"
 
 namespace cc {
diff --git a/cc/test/fake_video_frame_provider.h b/cc/test/fake_video_frame_provider.h
index ce2544a..a7e8453 100644
--- a/cc/test/fake_video_frame_provider.h
+++ b/cc/test/fake_video_frame_provider.h
@@ -5,6 +5,8 @@
 #ifndef CC_TEST_FAKE_VIDEO_FRAME_PROVIDER_H_
 #define CC_TEST_FAKE_VIDEO_FRAME_PROVIDER_H_
 
+#include <utility>
+
 #include "base/memory/raw_ptr.h"
 #include "cc/layers/video_frame_provider.h"
 #include "media/base/video_frame.h"
diff --git a/cc/test/test_layer_tree_host_base.h b/cc/test/test_layer_tree_host_base.h
index 12478798..ae72f96 100644
--- a/cc/test/test_layer_tree_host_base.h
+++ b/cc/test/test_layer_tree_host_base.h
@@ -6,6 +6,7 @@
 #define CC_TEST_TEST_LAYER_TREE_HOST_BASE_H_
 
 #include <memory>
+#include <utility>
 
 #include "base/memory/raw_ptr.h"
 #include "cc/test/fake_impl_task_runner_provider.h"
diff --git a/cc/test/test_paint_worklet_layer_painter.h b/cc/test/test_paint_worklet_layer_painter.h
index 387f7f9..8084fb19 100644
--- a/cc/test/test_paint_worklet_layer_painter.h
+++ b/cc/test/test_paint_worklet_layer_painter.h
@@ -5,6 +5,8 @@
 #ifndef CC_TEST_TEST_PAINT_WORKLET_LAYER_PAINTER_H_
 #define CC_TEST_TEST_PAINT_WORKLET_LAYER_PAINTER_H_
 
+#include <utility>
+
 #include "cc/paint/paint_worklet_layer_painter.h"
 #include "testing/gmock/include/gmock/gmock.h"
 
diff --git a/cc/test/test_ukm_recorder_factory.h b/cc/test/test_ukm_recorder_factory.h
index b1a76ad..0704485 100644
--- a/cc/test/test_ukm_recorder_factory.h
+++ b/cc/test/test_ukm_recorder_factory.h
@@ -5,6 +5,8 @@
 #ifndef CC_TEST_TEST_UKM_RECORDER_FACTORY_H_
 #define CC_TEST_TEST_UKM_RECORDER_FACTORY_H_
 
+#include <memory>
+
 #include "cc/trees/ukm_manager.h"
 
 namespace cc {
diff --git a/cc/tiles/eviction_tile_priority_queue.h b/cc/tiles/eviction_tile_priority_queue.h
index b26ff4e2..a8c4749 100644
--- a/cc/tiles/eviction_tile_priority_queue.h
+++ b/cc/tiles/eviction_tile_priority_queue.h
@@ -5,6 +5,7 @@
 #ifndef CC_TILES_EVICTION_TILE_PRIORITY_QUEUE_H_
 #define CC_TILES_EVICTION_TILE_PRIORITY_QUEUE_H_
 
+#include <memory>
 #include <set>
 #include <utility>
 #include <vector>
diff --git a/cc/tiles/frame_viewer_instrumentation.cc b/cc/tiles/frame_viewer_instrumentation.cc
index 1f01c6e..e4a3d70 100644
--- a/cc/tiles/frame_viewer_instrumentation.cc
+++ b/cc/tiles/frame_viewer_instrumentation.cc
@@ -4,6 +4,9 @@
 
 #include "cc/tiles/frame_viewer_instrumentation.h"
 
+#include <memory>
+#include <utility>
+
 #include "components/viz/common/traced_value.h"
 
 namespace cc {
diff --git a/cc/tiles/mipmap_util.cc b/cc/tiles/mipmap_util.cc
index 47fd8c8..37779072 100644
--- a/cc/tiles/mipmap_util.cc
+++ b/cc/tiles/mipmap_util.cc
@@ -4,6 +4,9 @@
 
 #include "cc/tiles/mipmap_util.h"
 
+#include <algorithm>
+#include <limits>
+
 #include "base/numerics/safe_math.h"
 
 namespace cc {
diff --git a/cc/tiles/raster_tile_priority_queue.cc b/cc/tiles/raster_tile_priority_queue.cc
index 35b1580b..d51d9cf 100644
--- a/cc/tiles/raster_tile_priority_queue.cc
+++ b/cc/tiles/raster_tile_priority_queue.cc
@@ -4,6 +4,8 @@
 
 #include "cc/tiles/raster_tile_priority_queue.h"
 
+#include <utility>
+
 #include "base/notreached.h"
 #include "cc/tiles/raster_tile_priority_queue_all.h"
 #include "cc/tiles/raster_tile_priority_queue_required.h"
diff --git a/cc/tiles/raster_tile_priority_queue.h b/cc/tiles/raster_tile_priority_queue.h
index e61a082..cca10301 100644
--- a/cc/tiles/raster_tile_priority_queue.h
+++ b/cc/tiles/raster_tile_priority_queue.h
@@ -5,6 +5,7 @@
 #ifndef CC_TILES_RASTER_TILE_PRIORITY_QUEUE_H_
 #define CC_TILES_RASTER_TILE_PRIORITY_QUEUE_H_
 
+#include <memory>
 #include <vector>
 
 #include "cc/cc_export.h"
diff --git a/cc/tiles/raster_tile_priority_queue_required.cc b/cc/tiles/raster_tile_priority_queue_required.cc
index f40e95a..a459eec 100644
--- a/cc/tiles/raster_tile_priority_queue_required.cc
+++ b/cc/tiles/raster_tile_priority_queue_required.cc
@@ -4,6 +4,8 @@
 
 #include "cc/tiles/raster_tile_priority_queue_required.h"
 
+#include <utility>
+
 #include "cc/tiles/tiling_set_raster_queue_required.h"
 
 namespace cc {
diff --git a/cc/tiles/raster_tile_priority_queue_required.h b/cc/tiles/raster_tile_priority_queue_required.h
index b54a2ca..914a22a1 100644
--- a/cc/tiles/raster_tile_priority_queue_required.h
+++ b/cc/tiles/raster_tile_priority_queue_required.h
@@ -5,6 +5,7 @@
 #ifndef CC_TILES_RASTER_TILE_PRIORITY_QUEUE_REQUIRED_H_
 #define CC_TILES_RASTER_TILE_PRIORITY_QUEUE_REQUIRED_H_
 
+#include <memory>
 #include <vector>
 
 #include "cc/layers/picture_layer_impl.h"
diff --git a/cc/tiles/tile_task_manager.h b/cc/tiles/tile_task_manager.h
index 26487cb..441314f 100644
--- a/cc/tiles/tile_task_manager.h
+++ b/cc/tiles/tile_task_manager.h
@@ -6,6 +6,7 @@
 #define CC_TILES_TILE_TASK_MANAGER_H_
 
 #include <stddef.h>
+#include <memory>
 
 #include "base/memory/raw_ptr.h"
 #include "cc/raster/raster_buffer_provider.h"
diff --git a/cc/trees/frame_rate_estimator_unittest.cc b/cc/trees/frame_rate_estimator_unittest.cc
index 93d0e0a6..ee4c755 100644
--- a/cc/trees/frame_rate_estimator_unittest.cc
+++ b/cc/trees/frame_rate_estimator_unittest.cc
@@ -4,6 +4,8 @@
 
 #include "cc/trees/frame_rate_estimator.h"
 
+#include <memory>
+
 #include "base/test/test_simple_task_runner.h"
 #include "base/time/time.h"
 #include "components/viz/common/frame_sinks/begin_frame_args.h"
diff --git a/chrome/app/os_settings_strings.grdp b/chrome/app/os_settings_strings.grdp
index ca4bd30a..ce4bd906 100644
--- a/chrome/app/os_settings_strings.grdp
+++ b/chrome/app/os_settings_strings.grdp
@@ -2478,6 +2478,15 @@
   <message name="IDS_SETTINGS_AUDIO_INPUT_GAIN_TITLE" desc="In Device Settings, the title of the slider under the input section in audio settings subpage. This slider controls the microphone's gain setting." translateable="false">
     Gain
   </message>
+  <message name="IDS_SETTINGS_AUDIO_INPUT_MUTE_BUTTON_ARIA_LABEL_NOT_MUTED" desc="In Device Settings, the aria label for the input mute button when not muted." translateable="false">
+    Toggle mic. Mic is on, toggling will mute input.
+  </message>
+  <message name="IDS_SETTINGS_AUDIO_INPUT_MUTE_BUTTON_ARIA_LABEL_MUTED" desc="In Device Settings, the aria label for the input mute button when muted." translateable="false">
+    Toggle mic. Mic is muted.
+  </message>
+  <message name="IDS_SETTINGS_AUDIO_INPUT_MUTE_BUTTON_ARIA_LABEL_MUTED_BY_HARDWARE_SWITCH" desc="In Device Settings, the aria label for the input mute button when microphone mute switch is turned on." translateable="false">
+    Device's microphone button is turned off.
+  </message>
   <message name="IDS_SETTINGS_AUDIO_INPUT_NOISE_CANCELLATION_TITLE" desc="In Device Settings, the title of the noise cancellation toggle under the input section in audio settings subpage." translateable="false">
     Noise Cancellation
   </message>
@@ -2520,6 +2529,12 @@
   <message name="IDS_SETTINGS_AUDIO_MUTED_EXTERNALLY_TOOLTIP" desc="In Device Settings, the tooltip used for the input and output mute buttons shown when the device is muted by hardware/switch." translateable="false">
     Muted by hardware/switch
   </message>
+  <message name="IDS_SETTINGS_AUDIO_OUTPUT_MUTE_BUTTON_ARIA_LABEL_NOT_MUTED" desc="In Device Settings, the aria label for the output mute button when not muted." translateable="false">
+    Toggle volume. Volume is on, toggling will mute audio.
+  </message>
+  <message name="IDS_SETTINGS_AUDIO_OUTPUT_MUTE_BUTTON_ARIA_LABEL_MUTED" desc="In Device Settings, the aria label for the output mute button when not muted." translateable="false">
+    Toggle volume. Volume is muted.
+  </message>
 
   <!-- Device pointer page (OS settings) -->
   <message name="IDS_SETTINGS_MOUSE_TITLE" desc="In Device Settings, the title of the mouse settings subpage.">
diff --git a/chrome/browser/about_flags.cc b/chrome/browser/about_flags.cc
index 842dc9d..716224e4 100644
--- a/chrome/browser/about_flags.cc
+++ b/chrome/browser/about_flags.cc
@@ -7569,12 +7569,6 @@
      FEATURE_VALUE_TYPE(printing::features::kEnableOopPrintDrivers)},
 #endif
 
-    {"enable-browsing-data-lifetime-manager",
-     flag_descriptions::kEnableBrowsingDataLifetimeManagerName,
-     flag_descriptions::kEnableBrowsingDataLifetimeManagerDescription, kOsAll,
-     FEATURE_VALUE_TYPE(
-         browsing_data::features::kEnableBrowsingDataLifetimeManager)},
-
     {"privacy-sandbox-ads-apis",
      flag_descriptions::kPrivacySandboxAdsAPIsOverrideName,
      flag_descriptions::kPrivacySandboxAdsAPIsOverrideDescription, kOsAll,
diff --git a/chrome/browser/ash/BUILD.gn b/chrome/browser/ash/BUILD.gn
index c5afc286..e3343199 100644
--- a/chrome/browser/ash/BUILD.gn
+++ b/chrome/browser/ash/BUILD.gn
@@ -552,6 +552,8 @@
     "arc/session/arc_session_manager.cc",
     "arc/session/arc_session_manager.h",
     "arc/session/arc_session_manager_observer.h",
+    "arc/session/arc_vm_data_migration_necessity_checker.cc",
+    "arc/session/arc_vm_data_migration_necessity_checker.h",
     "arc/sharesheet/arc_sharesheet_bridge.cc",
     "arc/sharesheet/arc_sharesheet_bridge.h",
     "arc/survey/arc_survey_service.cc",
@@ -3797,6 +3799,7 @@
     "//chromeos/ash/components/dbus:vm_permission_service_proto",
     "//chromeos/ash/components/dbus:vm_sk_forwarding_proto",
     "//chromeos/ash/components/dbus/arc",
+    "//chromeos/ash/components/dbus/arc:arcvm_data_migrator_proto_lib",
     "//chromeos/ash/components/dbus/audio",
     "//chromeos/ash/components/dbus/biod",
     "//chromeos/ash/components/dbus/biod:biod_proto",
@@ -4942,6 +4945,7 @@
     "arc/session/arc_play_store_enabled_preference_handler_unittest.cc",
     "arc/session/arc_provisioning_result_unittest.cc",
     "arc/session/arc_session_manager_unittest.cc",
+    "arc/session/arc_vm_data_migration_necessity_checker_unittest.cc",
     "arc/sharesheet/arc_sharesheet_bridge_unittest.cc",
     "arc/survey/arc_survey_service_unittest.cc",
     "arc/tracing/arc_app_performance_tracing_unittest.cc",
@@ -5790,6 +5794,7 @@
     "//chromeos/ash/components/dbus",
     "//chromeos/ash/components/dbus:vm_applications_apps_proto",
     "//chromeos/ash/components/dbus/anomaly_detector",
+    "//chromeos/ash/components/dbus/arc",
     "//chromeos/ash/components/dbus/attestation",
     "//chromeos/ash/components/dbus/attestation:attestation_proto",
     "//chromeos/ash/components/dbus/audio",
diff --git a/chrome/browser/ash/arc/arc_util.cc b/chrome/browser/ash/arc/arc_util.cc
index acd7f0ce..a9a220a 100644
--- a/chrome/browser/ash/arc/arc_util.cc
+++ b/chrome/browser/ash/arc/arc_util.cc
@@ -146,6 +146,7 @@
         break;
       case ArcSessionManager::State::CHECKING_REQUIREMENTS:
       case ArcSessionManager::State::REMOVING_DATA_DIR:
+      case ArcSessionManager::State::CHECKING_DATA_MIGRATION_NECESSITY:
       case ArcSessionManager::State::READY:
       case ArcSessionManager::State::ACTIVE:
       case ArcSessionManager::State::STOPPING:
diff --git a/chrome/browser/ash/arc/input_overlay/ui/edit_finish_view.cc b/chrome/browser/ash/arc/input_overlay/ui/edit_finish_view.cc
index ea11b5b..4df2324 100644
--- a/chrome/browser/ash/arc/input_overlay/ui/edit_finish_view.cc
+++ b/chrome/browser/ash/arc/input_overlay/ui/edit_finish_view.cc
@@ -16,7 +16,6 @@
 #include "third_party/skia/include/core/SkColor.h"
 #include "ui/base/l10n/l10n_util.h"
 #include "ui/color/color_id.h"
-#include "ui/compositor/layer.h"
 #include "ui/events/event.h"
 #include "ui/gfx/geometry/size.h"
 #include "ui/views/animation/flood_fill_ink_drop_ripple.h"
@@ -36,7 +35,6 @@
 constexpr int kViewCornerRadius = 16;
 constexpr int kViewBackgroundColor = SkColorSetA(SK_ColorBLACK, 0xCC /*80%*/);
 constexpr int kParentPadding = 16;
-constexpr int kBackgroundBlur = 10;
 // Space between children.
 constexpr int kSpaceRow = 4;
 // Alpha view features.
@@ -231,13 +229,6 @@
                                                          kViewCornerRadius)
                     : views::CreateSolidBackground(SK_ColorTRANSPARENT));
 
-  if (AllowReposition()) {
-    SetPaintToLayer();
-    layer()->SetFillsBoundsOpaquely(false);
-    layer()->SetBackgroundBlur(kBackgroundBlur);
-    layer()->SetRoundedCornerRadius(gfx::RoundedCornersF(kViewCornerRadius));
-  }
-
   auto on_mouse_pressed_callback = base::BindRepeating(
       &EditFinishView::OnMousePressed, base::Unretained(this));
   auto on_mouse_dragged_callback = base::BindRepeating(
diff --git a/chrome/browser/ash/arc/input_overlay/ui/menu_entry_view.cc b/chrome/browser/ash/arc/input_overlay/ui/menu_entry_view.cc
index 26b658f..b55fbc7 100644
--- a/chrome/browser/ash/arc/input_overlay/ui/menu_entry_view.cc
+++ b/chrome/browser/ash/arc/input_overlay/ui/menu_entry_view.cc
@@ -13,7 +13,6 @@
 #include "chrome/browser/ash/arc/input_overlay/util.h"
 #include "components/vector_icons/vector_icons.h"
 #include "ui/color/color_id.h"
-#include "ui/compositor/layer.h"
 #include "ui/events/event.h"
 #include "ui/events/types/event_type.h"
 #include "ui/gfx/color_utils.h"
@@ -34,7 +33,6 @@
 constexpr SkColor kHoverColor = SkColorSetA(gfx::kGoogleBlue600, 0x66 /*40%*/);
 
 constexpr int kParentPadding = 16;
-constexpr int kBackgroundBlur = 20;
 constexpr int kMenuEntrySize = 48;
 constexpr int kMenuEntryIconSize = 24;
 constexpr int kMenuEntryCornerRadius = 8;
@@ -67,10 +65,6 @@
   SetImageModel(views::Button::STATE_NORMAL, game_icon);
   SetBackground(views::CreateRoundedRectBackground(kDefaultColor,
                                                    kMenuEntryCornerRadius));
-  SetPaintToLayer();
-  layer()->SetFillsBoundsOpaquely(false);
-  layer()->SetBackgroundBlur(kBackgroundBlur);
-  layer()->SetRoundedCornerRadius(gfx::RoundedCornersF(kMenuEntryCornerRadius));
 
   SetSize(allow_reposition_
               ? gfx::Size(kMenuEntrySize, kMenuEntrySize)
diff --git a/chrome/browser/ash/arc/session/arc_session_manager.cc b/chrome/browser/ash/arc/session/arc_session_manager.cc
index 3946995..0b3548f2 100644
--- a/chrome/browser/ash/arc/session/arc_session_manager.cc
+++ b/chrome/browser/ash/arc/session/arc_session_manager.cc
@@ -22,6 +22,7 @@
 #include "ash/components/arc/session/serial_number_util.h"
 #include "ash/constants/ash_switches.h"
 #include "base/command_line.h"
+#include "base/feature_list.h"
 #include "base/functional/bind.h"
 #include "base/functional/callback_helpers.h"
 #include "base/logging.h"
@@ -769,8 +770,6 @@
       multi_user_util::GetAccountIdFromProfile(profile_));
   data_remover_ = std::make_unique<ArcDataRemover>(prefs, cryptohome_id);
 
-  arc_session_runner_->set_use_virtio_blk_data(ShouldUseVirtioBlkData(prefs));
-
   // Chrome may be shut down before completing ARC data removal.
   // For such a case, start removing the data now, if necessary.
   MaybeStartArcDataRemoval();
@@ -810,6 +809,9 @@
     case State::REMOVING_DATA_DIR:
       // When data removing is done, |state_| will be set to STOPPED.
       // Do nothing here.
+    case State::CHECKING_DATA_MIGRATION_NECESSITY:
+      // Checking whether /data migration is necessary. |state_| will be set to
+      // STOPPED when the check is done. Do nothing.
     case State::STOPPING:
       // Now ARC is stopping. Do nothing here.
       VLOG(1) << "Skipping session shutdown because state is: " << state_;
@@ -973,7 +975,8 @@
   DCHECK(profile_);
   DCHECK(enable_requested_);
   DCHECK(state_ == State::STOPPED || state_ == State::STOPPING ||
-         state_ == State::REMOVING_DATA_DIR)
+         state_ == State::REMOVING_DATA_DIR ||
+         state_ == State::CHECKING_DATA_MIGRATION_NECESSITY)
       << state_;
 
   if (state_ != State::STOPPED) {
@@ -1455,12 +1458,75 @@
     // We may have to avoid it.
   }
 
-  MaybeReenableArc();
+  if (!base::FeatureList::IsEnabled(kEnableArcVmDataMigration) ||
+      GetArcVmDataMigrationStatus(profile_->GetPrefs()) ==
+          ArcVmDataMigrationStatus::kFinished) {
+    // No need to check the necessity of ARCVM /data migration.
+    MaybeReenableArc();
+    return;
+  }
+
+  if (GetArcVmDataMigrationStatus(profile_->GetPrefs()) ==
+      ArcVmDataMigrationStatus::kStarted) {
+    VLOG(1) << "ARCVM /data migration is in progress. Restarting Chrome session"
+            << " to resume the migration";
+    chrome::AttemptRestart();
+    return;
+  }
+
+  CheckArcVmDataMigrationNecessity(base::BindOnce(
+      &ArcSessionManager::MaybeReenableArc, weak_ptr_factory_.GetWeakPtr()));
+}
+
+void ArcSessionManager::CheckArcVmDataMigrationNecessity(
+    base::OnceClosure callback) {
+  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+
+  DCHECK_EQ(state_, State::STOPPED);
+  state_ = State::CHECKING_DATA_MIGRATION_NECESSITY;
+
+  DCHECK(profile_);
+  DCHECK(!arc_vm_data_migration_necessity_checker_);
+  arc_vm_data_migration_necessity_checker_ =
+      std::make_unique<ArcVmDataMigrationNecessityChecker>(profile_);
+  arc_vm_data_migration_necessity_checker_->Check(
+      base::BindOnce(&ArcSessionManager::OnArcVmDataMigrationNecessityChecked,
+                     weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
+}
+
+void ArcSessionManager::OnArcVmDataMigrationNecessityChecked(
+    base::OnceClosure callback,
+    absl::optional<bool> result) {
+  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+
+  DCHECK_EQ(state_, State::CHECKING_DATA_MIGRATION_NECESSITY);
+  state_ = State::STOPPED;
+
+  DCHECK(profile_);
+  DCHECK(arc_vm_data_migration_necessity_checker_);
+  arc_vm_data_migration_necessity_checker_.reset();
+
+  // We assume that the migration is needed when |result| has no value, i.e.,
+  // when ArcVmDataMigrationNecessityChecker could not determine the necessity.
+  if (!result.value_or(true)) {
+    VLOG(1) << "No need to perform ARCVM /data migration. Marking the migration"
+            << " as finished";
+    SetArcVmDataMigrationStatus(profile_->GetPrefs(),
+                                ArcVmDataMigrationStatus::kFinished);
+  }
+  std::move(callback).Run();
 }
 
 void ArcSessionManager::MaybeReenableArc() {
   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
   DCHECK_EQ(state_, State::STOPPED);
+  DCHECK(profile_);
+
+  // Whether to use virtio-blk for /data depends on the status of ARCVM /data
+  // migration, which can be updated between Initialize() and MaybeReenableArc()
+  // by CheckArcVmDataMigrationNecessity(). Hence it should be set here.
+  arc_session_runner_->set_use_virtio_blk_data(
+      ShouldUseVirtioBlkData(profile_->GetPrefs()));
 
   if (!reenable_arc_) {
     // Re-enabling is not triggered. Do nothing.
@@ -1706,6 +1772,7 @@
     MAP_STATE(NOT_INITIALIZED);
     MAP_STATE(STOPPED);
     MAP_STATE(CHECKING_REQUIREMENTS);
+    MAP_STATE(CHECKING_DATA_MIGRATION_NECESSITY);
     MAP_STATE(REMOVING_DATA_DIR);
     MAP_STATE(READY);
     MAP_STATE(ACTIVE);
diff --git a/chrome/browser/ash/arc/session/arc_session_manager.h b/chrome/browser/ash/arc/session/arc_session_manager.h
index 13027a5d..47d273dd 100644
--- a/chrome/browser/ash/arc/session/arc_session_manager.h
+++ b/chrome/browser/ash/arc/session/arc_session_manager.h
@@ -24,6 +24,7 @@
 #include "chrome/browser/ash/arc/session/arc_app_id_provider_impl.h"
 #include "chrome/browser/ash/arc/session/arc_requirement_checker.h"
 #include "chrome/browser/ash/arc/session/arc_session_manager_observer.h"
+#include "chrome/browser/ash/arc/session/arc_vm_data_migration_necessity_checker.h"
 #include "chrome/browser/ash/guest_os/public/guest_os_mount_provider_registry.h"
 #include "chrome/browser/ash/policy/arc/android_management_client.h"
 #include "chromeos/ash/components/dbus/concierge/concierge_client.h"
@@ -80,6 +81,10 @@
   //   State is ACTIVE, instead.
   // REMOVING_DATA_DIR: When ARC is disabled, the data directory is removed.
   //   While removing is processed, ARC cannot be started. This is the state.
+  // CHECKING_DATA_MIGRATION_NECESSITY: When ARC /data migration is enabled but
+  //   not started yet, we need to check whether the migration is necessary by
+  //   inspecting the content of /data. ARC cannot be started while the check is
+  //   being performed, which is indicated by this state.
   // READY: ARC is ready to run, but not running yet. This state is skipped on
   //   the first boot case.
   // ACTIVE: ARC is running.
@@ -109,6 +114,8 @@
   //     immediately.
   //   REMOVING_DATA_DIR: Eventually state will become STOPPED. Do nothing
   //     immediately.
+  //   CHECKING_DATA_MIGRATION_NECESSITY: Eventually state will become STOPPED.
+  //     Do nothing immediately.
   //
   // TODO(hidehiko): Fix the state machine, and update the comment including
   // relationship with |enable_requested_|.
@@ -117,6 +124,7 @@
     STOPPED,
     CHECKING_REQUIREMENTS,
     REMOVING_DATA_DIR,
+    CHECKING_DATA_MIGRATION_NECESSITY,
     READY,
     ACTIVE,
     STOPPING,
@@ -407,10 +415,18 @@
   // Starts to remove ARC data, if it is requested via RequestArcDataRemoval().
   // On completion, OnArcDataRemoved() is called.
   // If not requested, just skipping the data removal, and moves to
-  // MaybeReenableArc() directly.
+  // MaybeReenableArc() or CheckArcVmDataMigrationNecessity() directly.
   void MaybeStartArcDataRemoval();
   void OnArcDataRemoved(absl::optional<bool> success);
 
+  // Checks whether /data migration is needed for enabling virtio-blk /data.
+  // On completion, OnArcVmDataMigrationNecessityChecked() is called.
+  // ArcSessionRunner::set_use_virtio_blk_data() should be called after the
+  // check is finished but before ARC is enabled in MaybeReenableArc().
+  void CheckArcVmDataMigrationNecessity(base::OnceClosure callback);
+  void OnArcVmDataMigrationNecessityChecked(base::OnceClosure callback,
+                                            absl::optional<bool> result);
+
   // On ARC session stopped and/or data removal completion, this is called
   // so that, if necessary, ARC session is restarted.
   // TODO(hidehiko): This can be removed after the racy state machine
@@ -465,6 +481,9 @@
   std::unique_ptr<ArcSupportHost> support_host_;
   std::unique_ptr<ArcDataRemover> data_remover_;
 
+  std::unique_ptr<ArcVmDataMigrationNecessityChecker>
+      arc_vm_data_migration_necessity_checker_;
+
   ArcRequirementChecker::AndroidManagementCheckerFactory
       android_management_checker_factory_;
   std::unique_ptr<ArcRequirementChecker> requirement_checker_;
diff --git a/chrome/browser/ash/arc/session/arc_session_manager_unittest.cc b/chrome/browser/ash/arc/session/arc_session_manager_unittest.cc
index 4167df3..e9d2a77 100644
--- a/chrome/browser/ash/arc/session/arc_session_manager_unittest.cc
+++ b/chrome/browser/ash/arc/session/arc_session_manager_unittest.cc
@@ -57,6 +57,8 @@
 #include "chrome/browser/ui/webui/ash/login/arc_terms_of_service_screen_handler.h"
 #include "chrome/test/base/testing_browser_process.h"
 #include "chrome/test/base/testing_profile.h"
+#include "chromeos/ash/components/dbus/arc/arcvm_data_migrator_client.h"
+#include "chromeos/ash/components/dbus/arc/fake_arcvm_data_migrator_client.h"
 #include "chromeos/ash/components/dbus/concierge/concierge_client.h"
 #include "chromeos/ash/components/dbus/session_manager/session_manager_client.h"
 #include "chromeos/ash/components/dbus/upstart/upstart_client.h"
@@ -286,6 +288,7 @@
   }
 
   void SetUp() override {
+    ash::ArcVmDataMigratorClient::InitializeFake();
     ash::ConciergeClient::InitializeFake(/*fake_cicerone_client=*/nullptr);
     chromeos::PowerManagerClient::InitializeFake();
     ash::SessionManagerClient::InitializeFakeInMemory();
@@ -321,6 +324,7 @@
     ash::SessionManagerClient::Shutdown();
     chromeos::PowerManagerClient::Shutdown();
     ash::ConciergeClient::Shutdown();
+    ash::ArcVmDataMigratorClient::Shutdown();
   }
 
   ash::FakeChromeUserManager* GetFakeUserManager() const {
@@ -855,6 +859,70 @@
   arc_session_manager()->Shutdown();
 }
 
+TEST_F(ArcSessionManagerTest, ArcVmDataMigrationNecessityChecker_Necessary) {
+  base::test::ScopedFeatureList feature_list;
+  feature_list.InitAndEnableFeature(kEnableArcVmDataMigration);
+  SetArcVmDataMigrationStatus(profile()->GetPrefs(),
+                              ArcVmDataMigrationStatus::kUnnotified);
+  ash::FakeArcVmDataMigratorClient::Get()->set_has_data_to_migrate(true);
+
+  arc_session_manager()->SetProfile(profile());
+  arc_session_manager()->Initialize();
+  arc_session_manager()->RequestEnable();
+  base::RunLoop().RunUntilIdle();
+
+  EXPECT_FALSE(arc_session_manager()
+                   ->GetArcSessionRunnerForTesting()
+                   ->use_virtio_blk_data());
+  EXPECT_EQ(GetArcVmDataMigrationStatus(profile()->GetPrefs()),
+            ArcVmDataMigrationStatus::kUnnotified);
+
+  arc_session_manager()->Shutdown();
+}
+
+TEST_F(ArcSessionManagerTest, ArcVmDataMigrationNecessityChecker_Unnecessary) {
+  base::test::ScopedFeatureList feature_list;
+  feature_list.InitAndEnableFeature(kEnableArcVmDataMigration);
+  SetArcVmDataMigrationStatus(profile()->GetPrefs(),
+                              ArcVmDataMigrationStatus::kUnnotified);
+  ash::FakeArcVmDataMigratorClient::Get()->set_has_data_to_migrate(false);
+
+  arc_session_manager()->SetProfile(profile());
+  arc_session_manager()->Initialize();
+  arc_session_manager()->RequestEnable();
+  base::RunLoop().RunUntilIdle();
+
+  EXPECT_TRUE(arc_session_manager()
+                  ->GetArcSessionRunnerForTesting()
+                  ->use_virtio_blk_data());
+  EXPECT_EQ(GetArcVmDataMigrationStatus(profile()->GetPrefs()),
+            ArcVmDataMigrationStatus::kFinished);
+
+  arc_session_manager()->Shutdown();
+}
+
+TEST_F(ArcSessionManagerTest, ArcVmDataMigrationNecessityChecker_Undetermined) {
+  base::test::ScopedFeatureList feature_list;
+  feature_list.InitAndEnableFeature(kEnableArcVmDataMigration);
+  SetArcVmDataMigrationStatus(profile()->GetPrefs(),
+                              ArcVmDataMigrationStatus::kUnnotified);
+  ash::FakeArcVmDataMigratorClient::Get()->set_has_data_to_migrate(
+      absl::nullopt);
+
+  arc_session_manager()->SetProfile(profile());
+  arc_session_manager()->Initialize();
+  arc_session_manager()->RequestEnable();
+  base::RunLoop().RunUntilIdle();
+
+  EXPECT_FALSE(arc_session_manager()
+                   ->GetArcSessionRunnerForTesting()
+                   ->use_virtio_blk_data());
+  EXPECT_EQ(GetArcVmDataMigrationStatus(profile()->GetPrefs()),
+            ArcVmDataMigrationStatus::kUnnotified);
+
+  arc_session_manager()->Shutdown();
+}
+
 TEST_F(ArcSessionManagerTest, RegularToChildTransition) {
   // Emulate the situation where a regular user has transitioned to a child
   // account.
diff --git a/chrome/browser/ash/arc/session/arc_vm_data_migration_necessity_checker.cc b/chrome/browser/ash/arc/session/arc_vm_data_migration_necessity_checker.cc
new file mode 100644
index 0000000..28c0d91
--- /dev/null
+++ b/chrome/browser/ash/arc/session/arc_vm_data_migration_necessity_checker.cc
@@ -0,0 +1,100 @@
+// 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/arc/session/arc_vm_data_migration_necessity_checker.h"
+
+#include <string>
+#include <vector>
+
+#include "ash/components/arc/arc_features.h"
+#include "ash/components/arc/arc_prefs.h"
+#include "ash/components/arc/arc_util.h"
+#include "ash/components/arc/session/arc_vm_client_adapter.h"
+#include "base/feature_list.h"
+#include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/ui/ash/multi_user/multi_user_util.h"
+#include "chromeos/ash/components/cryptohome/cryptohome_parameters.h"
+#include "chromeos/ash/components/dbus/arc/arcvm_data_migrator_client.h"
+#include "chromeos/ash/components/dbus/arcvm_data_migrator/arcvm_data_migrator.pb.h"
+#include "components/account_id/account_id.h"
+
+namespace arc {
+
+namespace {
+
+std::string GetChromeOsUser(Profile* profile) {
+  const AccountId account(multi_user_util::GetAccountIdFromProfile(profile));
+  return cryptohome::CreateAccountIdentifierFromAccountId(account).account_id();
+}
+
+}  // namespace
+
+ArcVmDataMigrationNecessityChecker::ArcVmDataMigrationNecessityChecker(
+    Profile* profile)
+    : profile_(profile) {
+  DCHECK(profile_);
+}
+
+ArcVmDataMigrationNecessityChecker::~ArcVmDataMigrationNecessityChecker() =
+    default;
+
+void ArcVmDataMigrationNecessityChecker::Check(CheckCallback callback) {
+  DCHECK(base::FeatureList::IsEnabled(kEnableArcVmDataMigration));
+  if (base::FeatureList::IsEnabled(kEnableVirtioBlkForData)) {
+    // No migration needs to be performed if virtio-blk /data is forciblly
+    // enabled via a feature.
+    std::move(callback).Run(false);
+    return;
+  }
+
+  if (GetArcVmDataMigrationStatus(profile_->GetPrefs()) ==
+      ArcVmDataMigrationStatus::kFinished) {
+    // No migration needs to be performed if it's already finished.
+    std::move(callback).Run(false);
+    return;
+  }
+
+  std::vector<std::string> environment = {"CHROMEOS_USER=" +
+                                          GetChromeOsUser(profile_)};
+  std::deque<JobDesc> jobs{JobDesc{kArcVmDataMigratorJobName,
+                                   UpstartOperation::JOB_STOP_AND_START,
+                                   std::move(environment)}};
+  ConfigureUpstartJobs(
+      std::move(jobs),
+      base::BindOnce(
+          &ArcVmDataMigrationNecessityChecker::OnArcVmDataMigratorStarted,
+          weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
+}
+
+void ArcVmDataMigrationNecessityChecker::OnArcVmDataMigratorStarted(
+    CheckCallback callback,
+    bool result) {
+  if (!result) {
+    LOG(ERROR) << "Failed to start arcvm-data-migrator";
+    std::move(callback).Run(absl::nullopt);
+    return;
+  }
+
+  DCHECK(ash::ArcVmDataMigratorClient::Get());
+  data_migrator::HasDataToMigrateRequest request;
+  request.set_username(GetChromeOsUser(profile_));
+  ash::ArcVmDataMigratorClient::Get()->HasDataToMigrate(
+      request,
+      base::BindOnce(
+          &ArcVmDataMigrationNecessityChecker::OnHasDataToMigrateResponse,
+          weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
+}
+
+void ArcVmDataMigrationNecessityChecker::OnHasDataToMigrateResponse(
+    CheckCallback callback,
+    absl::optional<bool> response) {
+  if (!response.has_value()) {
+    LOG(ERROR) << "Failed to check whether /data has any content: "
+               << "No valid D-Bus response";
+  }
+
+  std::move(callback).Run(response);
+}
+
+}  // namespace arc
diff --git a/chrome/browser/ash/arc/session/arc_vm_data_migration_necessity_checker.h b/chrome/browser/ash/arc/session/arc_vm_data_migration_necessity_checker.h
new file mode 100644
index 0000000..1677447
--- /dev/null
+++ b/chrome/browser/ash/arc/session/arc_vm_data_migration_necessity_checker.h
@@ -0,0 +1,49 @@
+// 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_ARC_SESSION_ARC_VM_DATA_MIGRATION_NECESSITY_CHECKER_H_
+#define CHROME_BROWSER_ASH_ARC_SESSION_ARC_VM_DATA_MIGRATION_NECESSITY_CHECKER_H_
+
+#include "base/functional/callback.h"
+#include "base/memory/weak_ptr.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
+
+class Profile;
+
+namespace arc {
+
+// Class to check whether /data migration needs to be performed for enabling
+// virtio-blk /data on ARCVM.
+class ArcVmDataMigrationNecessityChecker {
+ public:
+  explicit ArcVmDataMigrationNecessityChecker(Profile* profile);
+  ArcVmDataMigrationNecessityChecker(
+      const ArcVmDataMigrationNecessityChecker&) = delete;
+  ArcVmDataMigrationNecessityChecker& operator=(
+      const ArcVmDataMigrationNecessityChecker&) = delete;
+  ~ArcVmDataMigrationNecessityChecker();
+
+  using CheckCallback = base::OnceCallback<void(absl::optional<bool> result)>;
+
+  // Checks whether /data migration needs to be performed. When the migration is
+  // necessary/unnecessary, |callback| is called with true/false, respectively.
+  // On error, |callback| is called with absl::nullopt.
+  // Should be called when ARCVM /data migration is enabled.
+  void Check(CheckCallback callback);
+
+ private:
+  void OnArcVmDataMigratorStarted(CheckCallback callback, bool result);
+
+  void OnHasDataToMigrateResponse(CheckCallback callback,
+                                  absl::optional<bool> response);
+
+  Profile* const profile_;
+
+  base::WeakPtrFactory<ArcVmDataMigrationNecessityChecker> weak_ptr_factory_{
+      this};
+};
+
+}  // namespace arc
+
+#endif  // CHROME_BROWSER_ASH_ARC_SESSION_ARC_VM_DATA_MIGRATION_NECESSITY_CHECKER_H_
diff --git a/chrome/browser/ash/arc/session/arc_vm_data_migration_necessity_checker_unittest.cc b/chrome/browser/ash/arc/session/arc_vm_data_migration_necessity_checker_unittest.cc
new file mode 100644
index 0000000..c08b3742
--- /dev/null
+++ b/chrome/browser/ash/arc/session/arc_vm_data_migration_necessity_checker_unittest.cc
@@ -0,0 +1,144 @@
+// 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/arc/session/arc_vm_data_migration_necessity_checker.h"
+
+#include "ash/components/arc/arc_features.h"
+#include "ash/components/arc/arc_prefs.h"
+#include "ash/components/arc/arc_util.h"
+#include "ash/components/arc/session/arc_vm_client_adapter.h"
+#include "base/test/bind.h"
+#include "base/test/scoped_feature_list.h"
+#include "chrome/test/base/testing_profile.h"
+#include "chromeos/ash/components/dbus/arc/arcvm_data_migrator_client.h"
+#include "chromeos/ash/components/dbus/arc/fake_arcvm_data_migrator_client.h"
+#include "chromeos/ash/components/dbus/upstart/fake_upstart_client.h"
+#include "chromeos/ash/components/dbus/upstart/upstart_client.h"
+#include "content/public/test/browser_task_environment.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
+
+namespace arc {
+namespace {
+
+class ArcVmDataMigrationNecessityCheckerTest : public testing::Test {
+ public:
+  ArcVmDataMigrationNecessityCheckerTest() = default;
+  ~ArcVmDataMigrationNecessityCheckerTest() override = default;
+
+  void SetUp() override {
+    ash::UpstartClient::InitializeFake();
+    ash::ArcVmDataMigratorClient::InitializeFake();
+
+    profile_ = std::make_unique<TestingProfile>();
+    checker_ =
+        std::make_unique<ArcVmDataMigrationNecessityChecker>(profile_.get());
+  }
+
+  void TearDown() override {
+    checker_.reset();
+    profile_.reset();
+
+    ash::ArcVmDataMigratorClient::Shutdown();
+    ash::UpstartClient::Shutdown();
+  }
+
+ protected:
+  content::BrowserTaskEnvironment task_environment_;
+  std::unique_ptr<TestingProfile> profile_;
+  std::unique_ptr<ArcVmDataMigrationNecessityChecker> checker_;
+};
+
+TEST_F(ArcVmDataMigrationNecessityCheckerTest, HasDataToMigrate) {
+  absl::optional<bool> migration_needed = false;
+  base::test::ScopedFeatureList feature_list;
+  feature_list.InitAndEnableFeature(kEnableArcVmDataMigration);
+  ash::FakeArcVmDataMigratorClient::Get()->set_has_data_to_migrate(true);
+  checker_->Check(base::BindLambdaForTesting(
+      [&migration_needed](absl::optional<bool> result) {
+        migration_needed = result;
+      }));
+  base::RunLoop().RunUntilIdle();
+  EXPECT_TRUE(migration_needed.has_value());
+  EXPECT_TRUE(migration_needed.value());
+}
+
+TEST_F(ArcVmDataMigrationNecessityCheckerTest, HasNoDataToMigrate) {
+  absl::optional<bool> migration_needed = true;
+  base::test::ScopedFeatureList feature_list;
+  feature_list.InitAndEnableFeature(kEnableArcVmDataMigration);
+  ash::FakeArcVmDataMigratorClient::Get()->set_has_data_to_migrate(false);
+  checker_->Check(base::BindLambdaForTesting(
+      [&migration_needed](absl::optional<bool> result) {
+        migration_needed = result;
+      }));
+  base::RunLoop().RunUntilIdle();
+  EXPECT_TRUE(migration_needed.has_value());
+  EXPECT_FALSE(migration_needed.value());
+}
+
+TEST_F(ArcVmDataMigrationNecessityCheckerTest, ForceVirtioBlkForData) {
+  absl::optional<bool> migration_needed = true;
+  base::test::ScopedFeatureList feature_list;
+  feature_list.InitWithFeatures(
+      {kEnableArcVmDataMigration, kEnableVirtioBlkForData}, {});
+  ash::FakeArcVmDataMigratorClient::Get()->set_has_data_to_migrate(true);
+  checker_->Check(base::BindLambdaForTesting(
+      [&migration_needed](absl::optional<bool> result) {
+        migration_needed = result;
+      }));
+  base::RunLoop().RunUntilIdle();
+  EXPECT_TRUE(migration_needed.has_value());
+  EXPECT_FALSE(migration_needed.value());
+}
+
+TEST_F(ArcVmDataMigrationNecessityCheckerTest, MigrationFinished) {
+  absl::optional<bool> migration_needed = true;
+  base::test::ScopedFeatureList feature_list;
+  feature_list.InitAndEnableFeature(kEnableArcVmDataMigration);
+  SetArcVmDataMigrationStatus(profile_->GetPrefs(),
+                              ArcVmDataMigrationStatus::kFinished);
+  ash::FakeArcVmDataMigratorClient::Get()->set_has_data_to_migrate(true);
+  checker_->Check(base::BindLambdaForTesting(
+      [&migration_needed](absl::optional<bool> result) {
+        migration_needed = result;
+      }));
+  base::RunLoop().RunUntilIdle();
+  EXPECT_TRUE(migration_needed.has_value());
+  EXPECT_FALSE(migration_needed.value());
+}
+
+TEST_F(ArcVmDataMigrationNecessityCheckerTest, StartJobFailed) {
+  absl::optional<bool> migration_needed = true;
+  base::test::ScopedFeatureList feature_list;
+  feature_list.InitAndEnableFeature(kEnableArcVmDataMigration);
+  ash::FakeArcVmDataMigratorClient::Get()->set_has_data_to_migrate(true);
+  ash::FakeUpstartClient::Get()->set_start_job_cb(base::BindLambdaForTesting(
+      [](const std::string& job_name, const std::vector<std::string>& env) {
+        return job_name != kArcVmDataMigratorJobName;
+      }));
+  checker_->Check(base::BindLambdaForTesting(
+      [&migration_needed](absl::optional<bool> result) {
+        migration_needed = result;
+      }));
+  base::RunLoop().RunUntilIdle();
+  EXPECT_FALSE(migration_needed.has_value());
+}
+
+TEST_F(ArcVmDataMigrationNecessityCheckerTest, HasDataToMigrateFailed) {
+  absl::optional<bool> migration_needed = true;
+  base::test::ScopedFeatureList feature_list;
+  feature_list.InitAndEnableFeature(kEnableArcVmDataMigration);
+  ash::FakeArcVmDataMigratorClient::Get()->set_has_data_to_migrate(
+      absl::nullopt);
+  checker_->Check(base::BindLambdaForTesting(
+      [&migration_needed](absl::optional<bool> result) {
+        migration_needed = result;
+      }));
+  base::RunLoop().RunUntilIdle();
+  EXPECT_FALSE(migration_needed.has_value());
+}
+
+}  // namespace
+}  // namespace arc
diff --git a/chrome/browser/ash/crosapi/browser_data_back_migrator.cc b/chrome/browser/ash/crosapi/browser_data_back_migrator.cc
index 93f8727b..60039240 100644
--- a/chrome/browser/ash/crosapi/browser_data_back_migrator.cc
+++ b/chrome/browser/ash/crosapi/browser_data_back_migrator.cc
@@ -11,6 +11,7 @@
 #include "ash/constants/ash_features.h"
 #include "ash/constants/ash_switches.h"
 #include "base/command_line.h"
+#include "base/containers/contains.h"
 #include "base/files/file_enumerator.h"
 #include "base/files/file_path.h"
 #include "base/files/file_util.h"
@@ -24,6 +25,7 @@
 #include "base/path_service.h"
 #include "base/task/task_traits.h"
 #include "base/task/thread_pool.h"
+#include "chrome/browser/ash/crosapi/browser_data_migrator_util.h"
 #include "chrome/browser/ash/profiles/profile_helper.h"
 #include "chrome/browser/lifetime/application_lifetime.h"
 #include "chrome/common/chrome_constants.h"
@@ -94,6 +96,7 @@
       crosapi::browser_util::PolicyInitState::kBeforeInit));
 
   running_ = true;
+  migration_start_time_ = base::TimeTicks::Now();
 
   const base::FilePath lacros_profile_dir =
       ash_profile_dir_.Append(browser_data_migrator_util::kLacrosDir);
@@ -787,14 +790,8 @@
     return false;
   }
 
-  // For preferences that were moved to Lacros, and deleted in Ash, copy them
-  // back to Ash.
-  for (const char* key :
-       browser_data_migrator_util::kLacrosOnlyPreferencesKeys) {
-    base::Value* lacros_value = lacros_root_dict->FindByDottedPath(key);
-    if (lacros_value)
-      ash_root_dict->SetByDottedPath(key, lacros_value->Clone());
-  }
+  std::string current_path;
+  MergeLacrosPreferences(*ash_root_dict, current_path, lacros_root.value(), 0u);
 
   // Preferences that were split between Ash and Lacros relate to extensions.
   // Here we need to take the preferences from Lacros that were removed from
@@ -856,6 +853,56 @@
 }
 
 // static
+bool BrowserDataBackMigrator::MergeLacrosPreferences(
+    base::Value::Dict& ash_root_dict,
+    std::string& current_dotted_path,
+    const base::Value& current_value,
+    unsigned int recursion_depth) {
+  if (recursion_depth >= kMaxRecursionDepth) {
+    LOG(WARNING) << "We have reached maximum recursion depth "
+                 << kMaxRecursionDepth
+                 << " and we are stopping MergeLacrosPreferences()";
+    return false;
+  }
+
+  // If the |current_dotted_path| was split or ash-only, then ignore it.
+  if (base::Contains(browser_data_migrator_util::kSplitPreferencesKeys,
+                     current_dotted_path) ||
+      base::Contains(browser_data_migrator_util::kAshOnlyPreferencesKeys,
+                     current_dotted_path)) {
+    return true;
+  }
+
+  // If current value is not a dictionary, then it is a final pref.
+  // Merge it into the |ash_root_dict|.
+  if (!current_value.is_dict()) {
+    ash_root_dict.SetByDottedPath(current_dotted_path, current_value.Clone());
+
+    return true;
+  }
+  // Otherwise, traverse all child elements of the current dictionary.
+  for (const auto child_entry : current_value.GetDict()) {
+    const std::string& child_entry_key = child_entry.first;
+    const base::Value* child_entry_value = &child_entry.second;
+
+    std::string child_dotted_path;
+    // Can be empty if it is a root dictionary.
+    if (current_dotted_path.empty()) {
+      child_dotted_path = child_entry_key;
+    } else {
+      child_dotted_path = current_dotted_path + "." + child_entry_key;
+    }
+
+    if (!MergeLacrosPreferences(ash_root_dict, child_dotted_path,
+                                *child_entry_value, recursion_depth + 1u)) {
+      return false;
+    }
+  }
+
+  return true;
+}
+
+// static
 bool BrowserDataBackMigrator::IsLacrosOnlyExtension(
     const base::StringPiece extension_id) {
   return !base::Contains(browser_data_migrator_util::kExtensionsAshOnly,
@@ -1274,6 +1321,7 @@
 void BrowserDataBackMigrator::InvokeCallback(TaskResult result) {
   RecordFinalStatus(result);
   RecordPosixErrnoIfAvailable(result);
+  RecordMigrationTimeIfSuccessful(result, migration_start_time_);
   std::move(finished_callback_).Run(ToResult(result));
 }
 
@@ -1299,6 +1347,18 @@
 }
 
 // static
+void BrowserDataBackMigrator::RecordMigrationTimeIfSuccessful(
+    TaskResult result,
+    base::TimeTicks migration_start_time) {
+  if (result.status != TaskStatus::kSucceeded) {
+    return;
+  }
+
+  base::UmaHistogramMediumTimes(kSuccessfulMigrationTimeUMA,
+                                base::TimeTicks::Now() - migration_start_time);
+}
+
+// static
 std::string BrowserDataBackMigrator::TaskStatusToString(
     TaskStatus task_status) {
   switch (task_status) {
diff --git a/chrome/browser/ash/crosapi/browser_data_back_migrator.h b/chrome/browser/ash/crosapi/browser_data_back_migrator.h
index a1631ef..cd7ad6c 100644
--- a/chrome/browser/ash/crosapi/browser_data_back_migrator.h
+++ b/chrome/browser/ash/crosapi/browser_data_back_migrator.h
@@ -25,6 +25,8 @@
 
 constexpr char kFinalStatusUMA[] = "Ash.BrowserDataBackMigrator.FinalStatus";
 constexpr char kPosixErrnoUMA[] = "Ash.BrowserDataBackMigrator.PosixErrno.";
+constexpr char kSuccessfulMigrationTimeUMA[] =
+    "Ash.BrowserDataBackMigrator.SuccessfulMigrationTime";
 
 // Injects the restart function called from
 // `BrowserDataBackMigrator::AttemptRestart()` in RAII manner.
@@ -103,7 +105,17 @@
   FRIEND_TEST_ALL_PREFIXES(BrowserDataBackMigratorUMATest, RecordFinalStatus);
   FRIEND_TEST_ALL_PREFIXES(BrowserDataBackMigratorUMATest,
                            RecordPosixErrnoIfAvailable);
+  FRIEND_TEST_ALL_PREFIXES(BrowserDataBackMigratorUMATest,
+                           RecordMigrationTimeIfSuccessful);
   FRIEND_TEST_ALL_PREFIXES(BrowserDataBackMigratorUMATest, TaskStatusToString);
+  FRIEND_TEST_ALL_PREFIXES(BrowserDataBackMigratorTest,
+                           MergesAshOnlyPreferencesCorrectly);
+  FRIEND_TEST_ALL_PREFIXES(BrowserDataBackMigratorTest,
+                           MergesDictSplitPreferencesCorrectly);
+  FRIEND_TEST_ALL_PREFIXES(BrowserDataBackMigratorTest,
+                           MergesListSplitPreferencesCorrectly);
+  FRIEND_TEST_ALL_PREFIXES(BrowserDataBackMigratorTest,
+                           MergesLacrosPreferencesCorrectly);
 
   // A list of all the possible results of migration, including success and all
   // failure types in each step of the migration.
@@ -252,6 +264,15 @@
                                const base::FilePath& lacros_pref_path,
                                const base::FilePath& tmp_pref_path);
 
+  // For Lacros preferences that were neither split nor ash-only,
+  // simply prefer them over the ones that are currently in Ash.
+  // Traverse all JSON dotted paths in Lacros preferences using
+  // depth-first search and merge them into |ash_root_dict|.
+  static bool MergeLacrosPreferences(base::Value::Dict& ash_root_dict,
+                                     std::string& current_path,
+                                     const base::Value& current_value,
+                                     unsigned int recursion_depth);
+
   // Decides whether preferences for the given `extension_id` should be migrated
   // back from Lacros to Ash.
   static bool IsLacrosOnlyExtension(const base::StringPiece extension_id);
@@ -309,11 +330,17 @@
   // Records the final status of the migration in `kFinalStatusUMA`.
   static void RecordFinalStatus(TaskResult result);
 
-  // Record Ash.BrowserDataBackMigrator.PosixErrno.{result.status} UMA with the
+  // Records Ash.BrowserDataBackMigrator.PosixErrno.{result.status} UMA with the
   // value of `result.posix_errno` if the migration failed.
   static void RecordPosixErrnoIfAvailable(TaskResult result);
 
-  // Convert `TaskStatus` to string.
+  // Records `kSuccessfulMigrationTimeUMA` UMA with the elapsed time since
+  // starting backward migration. Only recorded if migration was successful.
+  static void RecordMigrationTimeIfSuccessful(
+      TaskResult result,
+      base::TimeTicks migration_start_time);
+
+  // Converts `TaskStatus` to string.
   static std::string TaskStatusToString(TaskStatus task_status);
 
   // Path to the ash profile directory.
@@ -325,6 +352,9 @@
   // Local state prefs, not owned.
   PrefService* local_state_ = nullptr;
 
+  // Used to record how long the migration takes in UMA.
+  base::TimeTicks migration_start_time_;
+
   base::WeakPtrFactory<BrowserDataBackMigrator> weak_factory_{this};
 };
 
diff --git a/chrome/browser/ash/crosapi/browser_data_back_migrator_unittest.cc b/chrome/browser/ash/crosapi/browser_data_back_migrator_unittest.cc
index a9e14731..654556b 100644
--- a/chrome/browser/ash/crosapi/browser_data_back_migrator_unittest.cc
+++ b/chrome/browser/ash/crosapi/browser_data_back_migrator_unittest.cc
@@ -8,9 +8,12 @@
 
 #include "ash/constants/ash_features.h"
 #include "base/files/file_enumerator.h"
+#include "base/files/file_path.h"
 #include "base/files/file_util.h"
 #include "base/files/scoped_temp_dir.h"
 #include "base/test/metrics/histogram_tester.h"
+#include "base/json/json_reader.h"
+#include "base/json/json_writer.h"
 #include "base/test/scoped_feature_list.h"
 #include "chrome/browser/ash/crosapi/browser_data_migrator_util.h"
 #include "chrome/browser/ash/crosapi/browser_util.h"
@@ -51,6 +54,16 @@
 constexpr char kAshLevelDBMeta[] = "ash-meta";
 constexpr char kLacrosLevelDBMeta[] = "lacros-meta";
 
+const int kAshPrefValue = 0;
+const int kLacrosPrefValue = 1;
+// Dotted paths of preferences not found in:
+// - kAshOnlyPreferencesKeys
+// - kLacrosOnlyPreferencesKeys
+// - kSplitPreferencesKeys
+constexpr char kOtherLacrosPreference[] = "xxx.xxx.xxx";
+constexpr char kOtherAshPreference[] = "yyy.xxx.xxx";
+constexpr char kOtherBothChromesPreference[] = "zzz.xxx.xxx";
+
 enum class FilesSetup {
   kAshOnly = 0,
   kLacrosOnly = 1,
@@ -223,6 +236,27 @@
   return db_map;
 }
 
+bool WriteJSONDict(const base::Value::Dict& json_dict,
+                   const base::FilePath& dest) {
+  std::string serialized_dict;
+
+  if (!base::JSONWriter::Write(json_dict, &serialized_dict)) {
+    return false;
+  }
+  if (!base::WriteFile(dest, serialized_dict)) {
+    return false;
+  }
+  return true;
+}
+
+size_t CountStringInList(const base::Value::List& list,
+                         const std::string& value) {
+  return std::count_if(list.cbegin(), list.cend(),
+                       [&](const base::Value& item) {
+                         return item.is_string() && item.GetString() == value;
+                       });
+}
+
 class BrowserDataBackMigratorTest : public testing::Test {
  public:
   BrowserDataBackMigratorTest() {
@@ -269,10 +303,20 @@
 
     tmp_profile_dir_ =
         ash_profile_dir_.Append(browser_data_back_migrator::kTmpDir);
+
+    merged_prefs_path_ = tmp_profile_dir_.Append("Preferences");
+    lacros_prefs_path_ = lacros_profile_dir_.Append("Preferences");
+    ash_prefs_path_ = ash_profile_dir_.Append("Preferences");
   }
 
   void TearDown() override { EXPECT_TRUE(user_data_dir_.Delete()); }
 
+  void CreateDirectories() {
+    ASSERT_TRUE(base::CreateDirectory(ash_profile_dir_));
+    ASSERT_TRUE(base::CreateDirectory(lacros_profile_dir_));
+    CreateTemporaryDirectory();
+  }
+
   void CreateTemporaryDirectory() {
     // During backward migration, `tmp_profile_dir_` is created in
     // `MergeSplitItems`, but we don't want to call that in tests so we generate
@@ -324,6 +368,22 @@
     }
   }
 
+  void WritePrefs(const base::Value::Dict& ash_prefs,
+                  const base::Value::Dict& lacros_prefs) {
+    ASSERT_TRUE(WriteJSONDict(ash_prefs, ash_prefs_path_));
+    ASSERT_TRUE(WriteJSONDict(lacros_prefs, lacros_prefs_path_));
+  }
+
+  void ReadMergedPrefs(base::Value* prefs_out) {
+    std::string merged_contents;
+    ASSERT_TRUE(base::ReadFileToString(merged_prefs_path_, &merged_contents));
+    absl::optional<base::Value> merged_prefs =
+        base::JSONReader::Read(merged_contents);
+    ASSERT_TRUE(merged_prefs.has_value());
+
+    *prefs_out = std::move(merged_prefs.value());
+  }
+
   void SetupStateStoreLevelDBFiles(const base::FilePath& ash_profile_dir,
                                    const base::FilePath& lacros_profile_dir,
                                    FilesSetup setup) {
@@ -363,6 +423,10 @@
   base::FilePath lacros_profile_dir_;
   base::FilePath tmp_profile_dir_;
 
+  base::FilePath merged_prefs_path_;
+  base::FilePath ash_prefs_path_;
+  base::FilePath lacros_prefs_path_;
+
   std::string kAshOnlyMetaKey;
   std::string kAshOnlyValueKey;
   std::string kLacrosOnlyMetaKey;
@@ -609,6 +673,263 @@
   }
 }
 
+TEST_F(BrowserDataBackMigratorTest,
+       MergesAshOnlyPreferencesCorrectly) {
+  // AshPrefs
+  // {
+  //   kOtherAshPreference: kAshPrefValue,
+  //   browser_data_migrator_util::kAshOnlyPreferencesKeys[0]: kAshPrefValue,
+  // }
+  //
+  // LacrosPrefs
+  // {
+  //   browser_data_migrator_util::kAshOnlyPreferencesKeys[0]: kLacrosPrefValue,
+  // }
+  CreateDirectories();
+
+  base::Value::Dict ash_prefs;
+  ash_prefs.SetByDottedPath(
+      browser_data_migrator_util::kAshOnlyPreferencesKeys[0], kAshPrefValue);
+  ash_prefs.SetByDottedPath(kOtherAshPreference, kAshPrefValue);
+
+  base::Value::Dict lacros_prefs;
+  lacros_prefs.SetByDottedPath(
+      browser_data_migrator_util::kAshOnlyPreferencesKeys[0], kLacrosPrefValue);
+
+  WritePrefs(ash_prefs, lacros_prefs);
+
+  ASSERT_TRUE(BrowserDataBackMigrator::MergePreferences(
+      ash_prefs_path_, lacros_prefs_path_, merged_prefs_path_));
+
+  // Expected MergedPrefs
+  // {
+  //   kOtherAshPreference: kAshPrefValue,
+  //   browser_data_migrator_util::kAshOnlyPreferencesKeys[0]: kAshPrefValue,
+  // }
+  base::Value merged_prefs;
+  ReadMergedPrefs(&merged_prefs);
+
+  const base::Value* merged_ash_pref = merged_prefs.GetDict().FindByDottedPath(
+      browser_data_migrator_util::kAshOnlyPreferencesKeys[0]);
+  ASSERT_TRUE(merged_ash_pref);
+  ASSERT_EQ(merged_ash_pref->GetInt(), kAshPrefValue);
+  const base::Value* merged_other_ash_pref =
+      merged_prefs.GetDict().FindByDottedPath(kOtherAshPreference);
+  ASSERT_TRUE(merged_other_ash_pref);
+  ASSERT_EQ(merged_other_ash_pref->GetInt(), kAshPrefValue);
+}
+
+TEST_F(BrowserDataBackMigratorTest,
+       MergesDictSplitPreferencesCorrectly) {
+  // AshPrefs
+  // {
+  //   browser_data_migrator_util::kSplitPreferencesKeys[0]: {
+  //    browser_data_migrator_util::kExtensionsAshOnly[0]: kAshPrefValue,
+  //    browser_data_migrator_util::kExtensionsBothChromes[0]: kAshPrefValue,
+  //    kLacrosOnlyExtensionId: kAshPrefValue
+  //   }
+  // }
+  //
+  // LacrosPrefs
+  // {
+  //   browser_data_migrator_util::kSplitPreferencesKeys[0]: {
+  //    browser_data_migrator_util::kExtensionsAshOnly[0]: kLacrosPrefValue,
+  //    browser_data_migrator_util::kExtensionsBothChromes[0]: kLacrosPrefValue,
+  //    kLacrosOnlyExtensionId: kLacrosPrefValue
+  //   }
+  // }
+  CreateDirectories();
+
+  base::Value::Dict ash_prefs;
+  base::Value::Dict ash_split_pref_dict;
+  ash_split_pref_dict.SetByDottedPath(
+      browser_data_migrator_util::kExtensionsAshOnly[0], kAshPrefValue);
+  ash_split_pref_dict.SetByDottedPath(
+      browser_data_migrator_util::kExtensionsBothChromes[0], kAshPrefValue);
+  ash_split_pref_dict.SetByDottedPath(kLacrosOnlyExtensionId, kAshPrefValue);
+  ash_prefs.SetByDottedPath(
+      browser_data_migrator_util::kSplitPreferencesKeys[0],
+      base::Value(std::move(ash_split_pref_dict)));
+
+  base::Value::Dict lacros_prefs;
+  base::Value::Dict lacros_split_pref_dict;
+  lacros_split_pref_dict.SetByDottedPath(
+      browser_data_migrator_util::kExtensionsAshOnly[0], kLacrosPrefValue);
+  lacros_split_pref_dict.SetByDottedPath(
+      browser_data_migrator_util::kExtensionsBothChromes[0], kLacrosPrefValue);
+  lacros_split_pref_dict.SetByDottedPath(kLacrosOnlyExtensionId,
+                                         kLacrosPrefValue);
+  lacros_prefs.SetByDottedPath(
+      browser_data_migrator_util::kSplitPreferencesKeys[0],
+      base::Value(std::move(lacros_split_pref_dict)));
+
+  WritePrefs(ash_prefs, lacros_prefs);
+
+  ASSERT_TRUE(BrowserDataBackMigrator::MergePreferences(
+      ash_prefs_path_, lacros_prefs_path_, merged_prefs_path_));
+
+  // Expected MergedPrefs
+  // {
+  //   browser_data_migrator_util::kSplitPreferencesKeys[0]: {
+  //    browser_data_migrator_util::kExtensionsAshOnly[0]: kAshPrefValue,
+  //    browser_data_migrator_util::kExtensionsBothChromes[0]: kAshPrefValue,
+  //    kLacrosOnlyExtensionId: kLacrosPrefValue
+  //   }
+  // }
+  base::Value merged_prefs;
+  ReadMergedPrefs(&merged_prefs);
+
+  const base::Value* split_pref = merged_prefs.GetDict().FindByDottedPath(
+      browser_data_migrator_util::kSplitPreferencesKeys[0]);
+  ASSERT_TRUE(split_pref);
+  const base::Value::Dict* split_pref_dict = &split_pref->GetDict();
+  const base::Value* ash_extension_value = split_pref_dict->FindByDottedPath(
+      browser_data_migrator_util::kExtensionsAshOnly[0]);
+  ASSERT_TRUE(ash_extension_value);
+  ASSERT_EQ(ash_extension_value->GetInt(), kAshPrefValue);
+  const base::Value* common_extension_value = split_pref_dict->FindByDottedPath(
+      browser_data_migrator_util::kExtensionsBothChromes[0]);
+  ASSERT_TRUE(common_extension_value);
+  ASSERT_EQ(common_extension_value->GetInt(), kAshPrefValue);
+  const base::Value* lacros_extension_value =
+      split_pref_dict->FindByDottedPath(kLacrosOnlyExtensionId);
+  ASSERT_TRUE(lacros_extension_value);
+  ASSERT_EQ(lacros_extension_value->GetInt(), kLacrosPrefValue);
+}
+
+TEST_F(BrowserDataBackMigratorTest,
+       MergesListSplitPreferencesCorrectly) {
+  // AshPrefs
+  // {
+  //   browser_data_migrator_util::kSplitPreferencesKeys[0]: [
+  //    browser_data_migrator_util::kExtensionsAshOnly[0],
+  //    browser_data_migrator_util::kExtensionsBothChromes[0],
+  //    kLacrosOnlyExtensionId
+  //   ]
+  // }
+  //
+  // LacrosPrefs
+  // {
+  //   browser_data_migrator_util::kSplitPreferencesKeys[0]: [
+  //    browser_data_migrator_util::kExtensionsAshOnly[0],
+  //    browser_data_migrator_util::kExtensionsBothChromes[0],
+  //    kLacrosOnlyExtensionId
+  //   ]
+  // }
+  CreateDirectories();
+
+  base::Value::Dict ash_prefs;
+  base::Value::List ash_split_pref_list;
+  ash_split_pref_list.Append(browser_data_migrator_util::kExtensionsAshOnly[0]);
+  ash_split_pref_list.Append(
+      browser_data_migrator_util::kExtensionsBothChromes[0]);
+  ash_split_pref_list.Append(kLacrosOnlyExtensionId);
+  ash_prefs.SetByDottedPath(
+      browser_data_migrator_util::kSplitPreferencesKeys[0],
+      base::Value(std::move(ash_split_pref_list)));
+
+  base::Value::Dict lacros_prefs;
+  base::Value::List lacros_split_pref_list;
+  lacros_split_pref_list.Append(kLacrosOnlyExtensionId);
+  lacros_prefs.SetByDottedPath(
+      browser_data_migrator_util::kSplitPreferencesKeys[0],
+      base::Value(std::move(lacros_split_pref_list)));
+
+  WritePrefs(ash_prefs, lacros_prefs);
+
+  ASSERT_TRUE(BrowserDataBackMigrator::MergePreferences(
+      ash_prefs_path_, lacros_prefs_path_, merged_prefs_path_));
+
+  // Expected MergedPrefs
+  // {
+  //   browser_data_migrator_util::kSplitPreferencesKeys[0]: [
+  //    browser_data_migrator_util::kExtensionsAshOnly[0],
+  //    browser_data_migrator_util::kExtensionsBothChromes[0],
+  //    kLacrosOnlyExtensionId
+  //   ]
+  // }
+  base::Value merged_prefs;
+  ReadMergedPrefs(&merged_prefs);
+
+  const base::Value* split_pref = merged_prefs.GetDict().FindByDottedPath(
+      browser_data_migrator_util::kSplitPreferencesKeys[0]);
+  ASSERT_TRUE(split_pref);
+  const base::Value::List* split_pref_list = &split_pref->GetList();
+  ASSERT_EQ(
+      CountStringInList(*split_pref_list,
+                        browser_data_migrator_util::kExtensionsAshOnly[0]),
+      1u);
+  ASSERT_EQ(
+      CountStringInList(*split_pref_list,
+                        browser_data_migrator_util::kExtensionsBothChromes[0]),
+      1u);
+  ASSERT_EQ(CountStringInList(*split_pref_list, kLacrosOnlyExtensionId), 1u);
+}
+
+TEST_F(BrowserDataBackMigratorTest,
+       MergesLacrosPreferencesCorrectly) {
+  // AshPrefs
+  // {
+  //   kOtherAshPreference: kAshPrefValue,
+  //   browser_data_migrator_util::kLacrosOnlyPreferencesKeys[0]: kAshPrefValue,
+  //   kOtherBothChromesPreference: kAshPrefValue,
+  // }
+  //
+  // LacrosPrefs
+  // {
+  //   kOtherBothChromesPreference: kLacrosPrefValue,
+  //   browser_data_migrator_util::kAshOnlyPreferencesKeys[0]: kLacrosPrefValue,
+  //   kOtherLacrosPreference: kLacrosPrefValue,
+  // }
+  CreateDirectories();
+
+  base::Value::Dict ash_prefs;
+  ash_prefs.SetByDottedPath(
+      browser_data_migrator_util::kLacrosOnlyPreferencesKeys[0], kAshPrefValue);
+  ash_prefs.SetByDottedPath(kOtherAshPreference, kAshPrefValue);
+  ash_prefs.SetByDottedPath(kOtherBothChromesPreference, kAshPrefValue);
+
+  base::Value::Dict lacros_prefs;
+  lacros_prefs.SetByDottedPath(
+      browser_data_migrator_util::kLacrosOnlyPreferencesKeys[0],
+      kLacrosPrefValue);
+  lacros_prefs.SetByDottedPath(kOtherBothChromesPreference, kLacrosPrefValue);
+  lacros_prefs.SetByDottedPath(kOtherLacrosPreference, kLacrosPrefValue);
+
+  WritePrefs(ash_prefs, lacros_prefs);
+
+  ASSERT_TRUE(BrowserDataBackMigrator::MergePreferences(
+      ash_prefs_path_, lacros_prefs_path_, merged_prefs_path_));
+
+  // Expected MergedPrefs
+  // {
+  //   kOtherAshPreference: kAshPrefValue,
+  //   browser_data_migrator_util::kLacrosOnlyPreferencesKeys[0]: kLacrosPrefValue,
+  //   kOtherBothChromesPreference: kLacrosPrefValue,
+  //   kOtherLacrosPreference: kLacrosPrefValue,
+  // }
+  base::Value merged_prefs;
+  ReadMergedPrefs(&merged_prefs);
+
+  const base::Value* lacros_preference =
+      merged_prefs.GetDict().FindByDottedPath(
+          browser_data_migrator_util::kLacrosOnlyPreferencesKeys[0]);
+  ASSERT_TRUE(lacros_preference);
+  ASSERT_EQ(lacros_preference->GetInt(), kLacrosPrefValue);
+  const base::Value* other_ash_preference =
+      merged_prefs.GetDict().FindByDottedPath(kOtherAshPreference);
+  ASSERT_TRUE(other_ash_preference);
+  ASSERT_EQ(other_ash_preference->GetInt(), kAshPrefValue);
+  const base::Value* other_common_preference =
+      merged_prefs.GetDict().FindByDottedPath(kOtherBothChromesPreference);
+  ASSERT_TRUE(other_common_preference);
+  ASSERT_EQ(other_common_preference->GetInt(), kLacrosPrefValue);
+  const base::Value* other_lacros_preference =
+      merged_prefs.GetDict().FindByDottedPath(kOtherLacrosPreference);
+  ASSERT_TRUE(other_lacros_preference);
+  ASSERT_EQ(other_lacros_preference->GetInt(), kLacrosPrefValue);
+}
+
 TEST_P(BrowserDataBackMigratorFilesSetupTest,
        DeletesLacrosItemsFromAshDirCorrectly) {
   auto files_setup = GetParam();
@@ -805,4 +1126,22 @@
             "Succeeded");
 }
 
+TEST(BrowserDataBackMigratorUMATest, RecordMigrationTimeIfSuccessful) {
+  base::HistogramTester histogram_tester;
+
+  // No total time is recorded on failed migration.
+  BrowserDataBackMigrator::TaskResult failure = {
+      BrowserDataBackMigrator::TaskStatus::kDeleteTmpDirDeleteFailed, EPERM};
+  BrowserDataBackMigrator::RecordMigrationTimeIfSuccessful(
+      failure, base::TimeTicks::Now());
+  histogram_tester.ExpectTotalCount(kSuccessfulMigrationTimeUMA, 0);
+
+  // When migration succeeds, total time is recorded.
+  BrowserDataBackMigrator::TaskResult success = {
+      BrowserDataBackMigrator::TaskStatus::kSucceeded};
+  BrowserDataBackMigrator::RecordMigrationTimeIfSuccessful(
+      success, base::TimeTicks::Now());
+  histogram_tester.ExpectTotalCount(kSuccessfulMigrationTimeUMA, 1);
+}
+
 }  // namespace ash
diff --git a/chrome/browser/ash/login/gaia_reauth_token_fetcher.cc b/chrome/browser/ash/login/gaia_reauth_token_fetcher.cc
index 6e9d2e6..b7afa64 100644
--- a/chrome/browser/ash/login/gaia_reauth_token_fetcher.cc
+++ b/chrome/browser/ash/login/gaia_reauth_token_fetcher.cc
@@ -16,6 +16,7 @@
 #include "chrome/browser/browser_process.h"
 #include "chrome/browser/net/system_network_context_manager.h"
 #include "chromeos/ash/components/login/auth/recovery/service_constants.h"
+#include "google_apis/credentials_mode.h"
 #include "google_apis/google_api_keys.h"
 #include "net/base/load_flags.h"
 #include "net/base/url_util.h"
@@ -59,7 +60,8 @@
   resource_request->url = GetFetchReauthTokenUrl();
   resource_request->load_flags =
       net::LOAD_DISABLE_CACHE | net::LOAD_DO_NOT_SAVE_COOKIES;
-  resource_request->credentials_mode = network::mojom::CredentialsMode::kOmit;
+  resource_request->credentials_mode =
+      google_apis::GetOmitCredentialsModeForGaiaRequests();
   resource_request->method = "GET";
 
   // TODO(b/197615068): Update the "policy" field in the traffic
diff --git a/chrome/browser/ash/login/marketing_backend_connector.cc b/chrome/browser/ash/login/marketing_backend_connector.cc
index 5a05a5e..b97cd0b 100644
--- a/chrome/browser/ash/login/marketing_backend_connector.cc
+++ b/chrome/browser/ash/login/marketing_backend_connector.cc
@@ -22,6 +22,7 @@
 #include "components/signin/public/identity_manager/scope_set.h"
 #include "components/user_manager/user.h"
 #include "components/user_manager/user_manager.h"
+#include "google_apis/credentials_mode.h"
 #include "net/base/load_flags.h"
 #include "net/http/http_request_headers.h"
 #include "net/http/http_status_code.h"
@@ -65,7 +66,8 @@
   auto resource_request = std::make_unique<network::ResourceRequest>();
   resource_request->url = GetChromebookServiceEndpoint();
   resource_request->load_flags = net::LOAD_DISABLE_CACHE;
-  resource_request->credentials_mode = network::mojom::CredentialsMode::kOmit;
+  resource_request->credentials_mode =
+      google_apis::GetOmitCredentialsModeForGaiaRequests();
   resource_request->method = "POST";
   return resource_request;
 }
diff --git a/chrome/browser/ash/login/saml/password_sync_token_fetcher.cc b/chrome/browser/ash/login/saml/password_sync_token_fetcher.cc
index 70fd8ca..27c9874 100644
--- a/chrome/browser/ash/login/saml/password_sync_token_fetcher.cc
+++ b/chrome/browser/ash/login/saml/password_sync_token_fetcher.cc
@@ -24,6 +24,7 @@
 #include "components/user_manager/known_user.h"
 #include "content/public/browser/browser_context.h"
 #include "content/public/common/url_constants.h"
+#include "google_apis/credentials_mode.h"
 #include "google_apis/gaia/gaia_auth_fetcher.h"
 #include "google_apis/gaia/gaia_constants.h"
 #include "google_apis/gaia/google_service_auth_error.h"
@@ -241,7 +242,8 @@
   }
   resource_request->load_flags =
       net::LOAD_DISABLE_CACHE | net::LOAD_BYPASS_CACHE;
-  resource_request->credentials_mode = network::mojom::CredentialsMode::kOmit;
+  resource_request->credentials_mode =
+      google_apis::GetOmitCredentialsModeForGaiaRequests();
   if (request_type_ == RequestType::kCreateToken) {
     resource_request->method = net::HttpRequestHeaders::kPostMethod;
   } else {
diff --git a/chrome/browser/ash/login/users/avatar/user_image_loader.cc b/chrome/browser/ash/login/users/avatar/user_image_loader.cc
index 5c450b1b..4bb3eed1 100644
--- a/chrome/browser/ash/login/users/avatar/user_image_loader.cc
+++ b/chrome/browser/ash/login/users/avatar/user_image_loader.cc
@@ -21,6 +21,7 @@
 #include "chrome/browser/browser_process.h"
 #include "chrome/browser/ui/ash/image_downloader_impl.h"
 #include "components/user_manager/user_image/user_image.h"
+#include "google_apis/credentials_mode.h"
 #include "ipc/ipc_channel.h"
 #include "net/traffic_annotation/network_traffic_annotation.h"
 #include "services/data_decoder/public/cpp/data_decoder.h"
@@ -438,7 +439,8 @@
 
   auto request = std::make_unique<network::ResourceRequest>();
   request->url = default_image_url;
-  request->credentials_mode = network::mojom::CredentialsMode::kOmit;
+  request->credentials_mode =
+      google_apis::GetOmitCredentialsModeForGaiaRequests();
 
   auto loader = network::SimpleURLLoader::Create(std::move(request),
                                                  kNetworkTrafficAnnotationTag);
diff --git a/chrome/browser/ash/printing/zeroconf_printer_detector.cc b/chrome/browser/ash/printing/zeroconf_printer_detector.cc
index d7acd374..b457f6ad 100644
--- a/chrome/browser/ash/printing/zeroconf_printer_detector.cc
+++ b/chrome/browser/ash/printing/zeroconf_printer_detector.cc
@@ -27,6 +27,7 @@
 const char ZeroconfPrinterDetector::kIppsServiceName[] = "_ipps._tcp.local";
 const char ZeroconfPrinterDetector::kSocketServiceName[] =
     "_pdl-datastream._tcp.local";
+const char ZeroconfPrinterDetector::kLpdServiceName[] = "_printer._tcp.local";
 
 // IppEverywhere printers are also required to advertise these services.
 const char ZeroconfPrinterDetector::kIppEverywhereServiceName[] =
@@ -36,12 +37,13 @@
 
 // These service names are ordered in priority. In other words, earlier
 // service types in this list will be used preferentially over later ones.
-constexpr std::array<const char*, 5> kServiceNames = {
+constexpr std::array<const char*, 6> kServiceNames = {
     ZeroconfPrinterDetector::kIppsEverywhereServiceName,
     ZeroconfPrinterDetector::kIppEverywhereServiceName,
     ZeroconfPrinterDetector::kIppsServiceName,
     ZeroconfPrinterDetector::kIppServiceName,
     ZeroconfPrinterDetector::kSocketServiceName,
+    ZeroconfPrinterDetector::kLpdServiceName,
 };
 
 namespace {
@@ -186,6 +188,8 @@
     // If the "rp" key is present in a Socket TXT record, the key/value MUST
     // be ignored.
     rp.clear();
+  } else if (service_type == ZeroconfPrinterDetector::kLpdServiceName) {
+    uri.SetScheme("lpd");
   } else {
     // Since we only register for these services, we should never get back
     // a service other than the ones above.
diff --git a/chrome/browser/ash/printing/zeroconf_printer_detector.h b/chrome/browser/ash/printing/zeroconf_printer_detector.h
index 708c6e2..a5d0dfa 100644
--- a/chrome/browser/ash/printing/zeroconf_printer_detector.h
+++ b/chrome/browser/ash/printing/zeroconf_printer_detector.h
@@ -26,6 +26,7 @@
   static const char kIppEverywhereServiceName[];
   static const char kIppsEverywhereServiceName[];
   static const char kSocketServiceName[];
+  static const char kLpdServiceName[];
 
   ~ZeroconfPrinterDetector() override = default;
 
diff --git a/chrome/browser/ash/printing/zeroconf_printer_detector_unittest.cc b/chrome/browser/ash/printing/zeroconf_printer_detector_unittest.cc
index 456992b2..41a7438 100644
--- a/chrome/browser/ash/printing/zeroconf_printer_detector_unittest.cc
+++ b/chrome/browser/ash/printing/zeroconf_printer_detector_unittest.cc
@@ -64,6 +64,7 @@
   kIppE,    // IPP-Everywhere
   kIppsE,   // IPPS-Everywhere
   kSocket,  // Socket
+  kLpd,     // LPD
 };
 
 // This corresponds to MakeServiceDescription() below. Given the same name (and
@@ -97,6 +98,9 @@
       scheme = "socket";
       rp = "";
       break;
+    case ServiceType::kLpd:
+      scheme = "lpd";
+      break;
   }
   printer.SetUri(base::StringPrintf("%s://%s.local:%d/%s", scheme.c_str(),
                                     name.c_str(), port, rp.c_str()));
@@ -159,6 +163,9 @@
     auto socket_lister = std::make_unique<FakeServiceDiscoveryDeviceLister>(
         runner, ZeroconfPrinterDetector::kSocketServiceName);
     socket_lister_ = socket_lister.get();
+    auto lpd_lister = std::make_unique<FakeServiceDiscoveryDeviceLister>(
+        runner, ZeroconfPrinterDetector::kLpdServiceName);
+    lpd_lister_ = lpd_lister.get();
 
     listers_[ZeroconfPrinterDetector::kIppServiceName] = std::move(ipp_lister);
     listers_[ZeroconfPrinterDetector::kIppsServiceName] =
@@ -169,6 +176,7 @@
         std::move(ippse_lister);
     listers_[ZeroconfPrinterDetector::kSocketServiceName] =
         std::move(socket_lister);
+    listers_[ZeroconfPrinterDetector::kLpdServiceName] = std::move(lpd_lister);
   }
   ~ZeroconfPrinterDetectorTest() override = default;
 
@@ -187,6 +195,7 @@
     ippe_lister_->SetDelegate(detector_.get());
     ippse_lister_->SetDelegate(detector_.get());
     socket_lister_->SetDelegate(detector_.get());
+    lpd_lister_->SetDelegate(detector_.get());
   }
 
   // Expect that the most up-to-date results from the detector match those
@@ -278,6 +287,7 @@
   FakeServiceDiscoveryDeviceLister* ippe_lister_;
   FakeServiceDiscoveryDeviceLister* ippse_lister_;
   FakeServiceDiscoveryDeviceLister* socket_lister_;
+  FakeServiceDiscoveryDeviceLister* lpd_lister_;
 
   // Detector under test.
   std::unique_ptr<ZeroconfPrinterDetector> detector_;
@@ -334,6 +344,14 @@
   ExpectPrintersAre({MakeExpectedPrinter("Printer5", ServiceType::kSocket)});
 }
 
+TEST_F(ZeroconfPrinterDetectorTest, SingleLpdPrinter) {
+  lpd_lister_->Announce(MakeServiceDescription(
+      "Printer6", ZeroconfPrinterDetector::kLpdServiceName));
+  CreateDetector();
+  CompleteTasks();
+  ExpectPrintersAre({MakeExpectedPrinter("Printer6", ServiceType::kLpd)});
+}
+
 // Test that an announce after the detector creation shows up as a printer.
 TEST_F(ZeroconfPrinterDetectorTest, AnnounceAfterDetectorCreation) {
   CreateDetector();
@@ -401,6 +419,17 @@
   ASSERT_EQ(1U, printers_found_callbacks_.back().size());
   // Id should be the same.
   ASSERT_EQ(id, printers_found_callbacks_.back()[0].printer.id());
+
+  // Remove it as a socket printer, add it as an LPD printer.
+  socket_lister_->Remove("Printer1");
+  CompleteTasks();
+  ASSERT_TRUE(printers_found_callbacks_.back().empty());
+  lpd_lister_->Announce(MakeServiceDescription(
+      "Printer1", ZeroconfPrinterDetector::kLpdServiceName));
+  CompleteTasks();
+  ASSERT_EQ(1U, printers_found_callbacks_.back().size());
+  // Id should be the same.
+  ASSERT_EQ(id, printers_found_callbacks_.back()[0].printer.id());
 }
 
 // Test a basic removal.
@@ -432,9 +461,9 @@
 
 // Test that, when the same printer appears in multiple services, we
 // use the highest priority one.  Priorities, from highest to lowest
-// are IPPS-E, IPP-E, IPPS, IPP.
+// are IPPS-E, IPP-E, IPPS, IPP, Socket, LPD.
 TEST_F(ZeroconfPrinterDetectorTest, ServiceTypePriorities) {
-  // Advertise on all 4 services.
+  // Advertise on all services.
   ipp_lister_->Announce(MakeServiceDescription(
       "Printer5", ZeroconfPrinterDetector::kIppServiceName));
   ipps_lister_->Announce(MakeServiceDescription(
@@ -445,6 +474,8 @@
       "Printer5", ZeroconfPrinterDetector::kIppsEverywhereServiceName));
   socket_lister_->Announce(MakeServiceDescription(
       "Printer5", ZeroconfPrinterDetector::kSocketServiceName));
+  lpd_lister_->Announce(MakeServiceDescription(
+      "Printer5", ZeroconfPrinterDetector::kLpdServiceName));
   CreateDetector();
   CompleteTasks();
   // IPPS-E is highest priority.
@@ -466,11 +497,16 @@
 
   ipp_lister_->Remove("Printer5");
   CompleteTasks();
-  // Socket is only remaining entry.
+  // Socket is highest remaining entry.
   ExpectPrintersAre({MakeExpectedPrinter("Printer5", ServiceType::kSocket)});
 
   socket_lister_->Remove("Printer5");
   CompleteTasks();
+  // LPD is only remaining entry.
+  ExpectPrintersAre({MakeExpectedPrinter("Printer5", ServiceType::kLpd)});
+
+  lpd_lister_->Remove("Printer5");
+  CompleteTasks();
   // No entries left.
   ExpectPrintersEmpty();
 }
@@ -497,6 +533,10 @@
       "Printer10", ZeroconfPrinterDetector::kSocketServiceName));
   socket_lister_->Announce(MakeServiceDescription(
       "Printer11", ZeroconfPrinterDetector::kSocketServiceName));
+  lpd_lister_->Announce(MakeServiceDescription(
+      "Printer11", ZeroconfPrinterDetector::kLpdServiceName));
+  lpd_lister_->Announce(MakeServiceDescription(
+      "Printer12", ZeroconfPrinterDetector::kLpdServiceName));
 
   CreateDetector();
   CompleteTasks();
@@ -505,7 +545,8 @@
                      MakeExpectedPrinter("Printer8", ServiceType::kIppE),
                      MakeExpectedPrinter("Printer9", ServiceType::kIppsE),
                      MakeExpectedPrinter("Printer10", ServiceType::kIppsE),
-                     MakeExpectedPrinter("Printer11", ServiceType::kSocket)});
+                     MakeExpectedPrinter("Printer11", ServiceType::kSocket),
+                     MakeExpectedPrinter("Printer12", ServiceType::kLpd)});
 
   ipps_lister_->Clear();
 
@@ -518,15 +559,15 @@
 
   // Just for kicks, announce something new at this point.
   ipps_lister_->Announce(MakeServiceDescription(
-      "Printer12", ZeroconfPrinterDetector::kIppsServiceName));
+      "Printer13", ZeroconfPrinterDetector::kIppsServiceName));
   CompleteTasks();
-  ExpectPrintersAre({MakeExpectedPrinter("Printer12", ServiceType::kIpps)});
+  ExpectPrintersAre({MakeExpectedPrinter("Printer13", ServiceType::kIpps)});
 
   // Clear out the IPPS lister, which will clear all printers too.
   ipps_lister_->Clear();
   CompleteTasks();
 
-  // With the IPPS lister cleared, Printer12 should disappear.
+  // With the IPPS lister cleared, Printer13 should disappear.
   ExpectPrintersEmpty();
   EXPECT_TRUE(ippe_lister_->discovery_started());
 }
@@ -545,6 +586,8 @@
       "Printer15", ZeroconfPrinterDetector::kIppsServiceName));
   socket_lister_->Announce(MakeServiceDescription(
       "Printer16", ZeroconfPrinterDetector::kSocketServiceName));
+  lpd_lister_->Announce(MakeServiceDescription(
+      "Printer17", ZeroconfPrinterDetector::kLpdServiceName));
 
   CreateDetector();
   CompleteTasks();
@@ -552,19 +595,21 @@
                      MakeExpectedPrinter("Printer13", ServiceType::kIpps),
                      MakeExpectedPrinter("Printer14", ServiceType::kIppsE),
                      MakeExpectedPrinter("Printer15", ServiceType::kIpps),
-                     MakeExpectedPrinter("Printer16", ServiceType::kSocket)});
+                     MakeExpectedPrinter("Printer16", ServiceType::kSocket),
+                     MakeExpectedPrinter("Printer17", ServiceType::kLpd)});
 
   ippe_lister_->Announce(MakeServiceDescription(
       "Printer13", ZeroconfPrinterDetector::kIppEverywhereServiceName));
   ipp_lister_->Announce(MakeServiceDescription(
-      "Printer17", ZeroconfPrinterDetector::kIppServiceName));
+      "Printer18", ZeroconfPrinterDetector::kIppServiceName));
   CompleteTasks();
   ExpectPrintersAre({MakeExpectedPrinter("Printer12", ServiceType::kIpps),
                      MakeExpectedPrinter("Printer13", ServiceType::kIppE),
                      MakeExpectedPrinter("Printer14", ServiceType::kIppsE),
                      MakeExpectedPrinter("Printer15", ServiceType::kIpps),
                      MakeExpectedPrinter("Printer16", ServiceType::kSocket),
-                     MakeExpectedPrinter("Printer17", ServiceType::kIpp)});
+                     MakeExpectedPrinter("Printer17", ServiceType::kLpd),
+                     MakeExpectedPrinter("Printer18", ServiceType::kIpp)});
 
   ipp_lister_->Remove("NonexistantPrinter");
   ipps_lister_->Remove("Printer12");
diff --git a/chrome/browser/download/bubble/download_bubble_controller.cc b/chrome/browser/download/bubble/download_bubble_controller.cc
index aeadb8a..c8a6c38 100644
--- a/chrome/browser/download/bubble/download_bubble_controller.cc
+++ b/chrome/browser/download/bubble/download_bubble_controller.cc
@@ -36,6 +36,10 @@
 namespace {
 constexpr int kShowDownloadsInBubbleForNumDays = 1;
 constexpr int kMaxDownloadsToShow = 100;
+// Don't show the partial view more than once per 15 seconds, as this pops up
+// automatically and may be annoying to the user. The time is reset when the
+// user clicks on the button to open the main view.
+constexpr base::TimeDelta kShowPartialViewMinInterval = base::Seconds(15);
 
 bool FindOfflineItemByContentId(const ContentId& to_find,
                                 const OfflineItem& candidate) {
@@ -375,7 +379,12 @@
 }
 
 std::vector<DownloadUIModelPtr> DownloadBubbleUIController::GetPartialView() {
-  last_partial_view_shown_time_ = absl::make_optional(base::Time::Now());
+  base::Time now = base::Time::Now();
+  if (last_partial_view_shown_time_.has_value() &&
+      now - *last_partial_view_shown_time_ < kShowPartialViewMinInterval) {
+    return {};
+  }
+  last_partial_view_shown_time_ = absl::make_optional(now);
   std::vector<DownloadUIModelPtr> list =
       GetDownloadUIModels(/*is_main_view=*/false);
   base::UmaHistogramCounts100("Download.Bubble.PartialViewSize", list.size());
diff --git a/chrome/browser/download/bubble/download_bubble_controller_unittest.cc b/chrome/browser/download/bubble/download_bubble_controller_unittest.cc
index 05917ef..0cb357b 100644
--- a/chrome/browser/download/bubble/download_bubble_controller_unittest.cc
+++ b/chrome/browser/download/bubble/download_bubble_controller_unittest.cc
@@ -387,6 +387,45 @@
   EXPECT_EQ(second_controller().GetPartialView().size(), 0ul);
 }
 
+// Tests that no items are returned (i.e. no partial view will be shown) if it
+// is too soon since the last partial view has been shown.
+TEST_F(DownloadBubbleUIControllerTest, NoItemsReturnedForPartialViewTooSoon) {
+  std::vector<std::string> ids = {"Download 1", "Download 2", "Download 3",
+                                  "Download 4"};
+
+  // First time showing the partial view should work.
+  EXPECT_CALL(display_controller(), OnNewItem(true, true)).Times(1);
+  InitDownloadItem(FILE_PATH_LITERAL("/foo/bar1.pdf"),
+                   download::DownloadItem::IN_PROGRESS, ids[0]);
+  EXPECT_EQ(controller().GetPartialView().size(), 1u);
+
+  // No items are returned for a partial view because it is too soon.
+  task_environment_.FastForwardBy(base::Seconds(14));
+  EXPECT_CALL(display_controller(), OnNewItem(false, true)).Times(1);
+  InitDownloadItem(FILE_PATH_LITERAL("/foo/bar2.pdf"),
+                   download::DownloadItem::COMPLETE, ids[1]);
+  EXPECT_EQ(controller().GetPartialView().size(), 0u);
+
+  // Partial view can now be shown, and contains all the items.
+  task_environment_.FastForwardBy(base::Seconds(1));
+  EXPECT_CALL(display_controller(), OnNewItem(false, true)).Times(1);
+  InitDownloadItem(FILE_PATH_LITERAL("/foo/bar3.pdf"),
+                   download::DownloadItem::COMPLETE, ids[1]);
+  EXPECT_EQ(controller().GetPartialView().size(), 3u);
+
+  // Showing the main view even before time is up should still work.
+  task_environment_.FastForwardBy(base::Seconds(14));
+  EXPECT_EQ(controller().GetPartialView().size(), 0u);
+  EXPECT_EQ(controller().GetMainView().size(), 3u);
+
+  // Main view resets the partial view time, so the partial view can now be
+  // shown.
+  EXPECT_CALL(display_controller(), OnNewItem(true, true)).Times(1);
+  InitDownloadItem(FILE_PATH_LITERAL("/foo/bar4.pdf"),
+                   download::DownloadItem::IN_PROGRESS, ids[3]);
+  EXPECT_EQ(controller().GetPartialView().size(), 1u);
+}
+
 class DownloadBubbleUIControllerIncognitoTest
     : public DownloadBubbleUIControllerTest {
  public:
diff --git a/chrome/browser/extensions/BUILD.gn b/chrome/browser/extensions/BUILD.gn
index a49bd4a..c5b695f 100644
--- a/chrome/browser/extensions/BUILD.gn
+++ b/chrome/browser/extensions/BUILD.gn
@@ -1348,6 +1348,12 @@
   }
 
   if (enable_pdf) {
+    sources += [
+      "api/pdf_viewer_private/pdf_viewer_private_event_router.cc",
+      "api/pdf_viewer_private/pdf_viewer_private_event_router.h",
+      "api/pdf_viewer_private/pdf_viewer_private_event_router_factory.cc",
+      "api/pdf_viewer_private/pdf_viewer_private_event_router_factory.h",
+    ]
     deps += [
       "//chrome/browser/pdf",
       "//components/pdf/browser",
diff --git a/chrome/browser/extensions/api/api_browser_context_keyed_service_factories.cc b/chrome/browser/extensions/api/api_browser_context_keyed_service_factories.cc
index ebd92d3..271b1e6 100644
--- a/chrome/browser/extensions/api/api_browser_context_keyed_service_factories.cc
+++ b/chrome/browser/extensions/api/api_browser_context_keyed_service_factories.cc
@@ -37,6 +37,7 @@
 #include "chrome/browser/extensions/api/webrtc_audio_private/webrtc_audio_private_api.h"
 #include "chrome/browser/speech/extension_api/tts_extension_api.h"
 #include "chrome/common/buildflags.h"
+#include "components/services/screen_ai/buildflags/buildflags.h"
 #include "extensions/browser/api/bluetooth_low_energy/bluetooth_low_energy_api.h"
 #include "extensions/browser/api/networking_private/networking_private_delegate_factory.h"
 
@@ -50,6 +51,10 @@
 #include "chrome/browser/extensions/api/terminal/terminal_private_api.h"
 #endif
 
+#if BUILDFLAG(ENABLE_SCREEN_AI_SERVICE)
+#include "chrome/browser/extensions/api/pdf_viewer_private/pdf_viewer_private_event_router_factory.h"
+#endif  // BUILDFLAG(ENABLE_SCREEN_AI_SERVICE)
+
 #if BUILDFLAG(ENABLE_SERVICE_DISCOVERY)
 #include "chrome/browser/extensions/api/mdns/mdns_api.h"
 #endif
@@ -85,6 +90,9 @@
 #endif
   extensions::OmniboxAPI::GetFactoryInstance();
   extensions::PasswordsPrivateEventRouterFactory::GetInstance();
+#if BUILDFLAG(ENABLE_SCREEN_AI_SERVICE)
+  extensions::PdfViewerPrivateEventRouterFactory::GetInstance();
+#endif  // BUILDFLAG(ENABLE_SCREEN_AI_SERVICE)
   extensions::PreferenceAPI::GetFactoryInstance();
   extensions::ProcessesAPI::GetFactoryInstance();
   extensions::SafeBrowsingPrivateEventRouterFactory::GetInstance();
diff --git a/chrome/browser/extensions/api/developer_private/developer_private_api_unittest.cc b/chrome/browser/extensions/api/developer_private/developer_private_api_unittest.cc
index 05085497..4a4c004e 100644
--- a/chrome/browser/extensions/api/developer_private/developer_private_api_unittest.cc
+++ b/chrome/browser/extensions/api/developer_private/developer_private_api_unittest.cc
@@ -16,6 +16,7 @@
 #include "base/stl_util.h"
 #include "base/strings/strcat.h"
 #include "base/strings/stringprintf.h"
+#include "base/test/gtest_util.h"
 #include "base/test/values_test_util.h"
 #include "chrome/browser/extensions/chrome_test_extension_loader.h"
 #include "chrome/browser/extensions/error_console/error_console.h"
@@ -1986,6 +1987,125 @@
 // Test developerPrivate.getUserSiteSettings.
 TEST_F(DeveloperPrivateApiUnitTest, DeveloperPrivateGetUserSiteSettings) {
   PermissionsManager* manager = PermissionsManager::Get(browser_context());
+  const url::Origin restricted_url =
+      url::Origin::Create(GURL("http://example.com"));
+
+  manager->AddUserRestrictedSite(restricted_url);
+
+  auto function =
+      base::MakeRefCounted<api::DeveloperPrivateGetUserSiteSettingsFunction>();
+
+  absl::optional<base::Value> result =
+      api_test_utils::RunFunctionAndReturnSingleResult(
+          function.get(), /*args=*/"[]", profile());
+  ASSERT_TRUE(result.has_value());
+  std::unique_ptr<api::developer_private::UserSiteSettings> settings =
+      api::developer_private::UserSiteSettings::FromValue(result.value());
+
+  ASSERT_TRUE(settings);
+  EXPECT_THAT(settings->permitted_sites, testing::IsEmpty());
+  EXPECT_THAT(settings->restricted_sites,
+              testing::UnorderedElementsAre("http://example.com"));
+}
+
+// Test developerPrivate.addUserSpecifiedSite and removeUserSpecifiedSite for
+// restricted sites.
+TEST_F(DeveloperPrivateApiUnitTest, DeveloperPrivateModifyUserSiteSettings) {
+  static constexpr char kExample[] = "http://example.com";
+  static constexpr char kChromium[] = "http://chromium.org";
+
+  const url::Origin example_url = url::Origin::Create(GURL(kExample));
+  const url::Origin chromium_url = url::Origin::Create(GURL(kChromium));
+
+  // Add restricted sites, and check that these sites are stored in the manager.
+  EXPECT_NO_FATAL_FAILURE(AddUserSpecifiedSites(
+      profile(), base::StringPrintf(R"(["%s","%s"])", kExample, kChromium),
+      /*restricted=*/true));
+
+  PermissionsManager* manager = PermissionsManager::Get(browser_context());
+  EXPECT_THAT(manager->GetUserPermissionsSettings().permitted_sites,
+              testing::IsEmpty());
+  EXPECT_THAT(manager->GetUserPermissionsSettings().restricted_sites,
+              testing::UnorderedElementsAre(example_url, chromium_url));
+
+  // Remove restricted site, and check that the site was removed in the manager.
+  EXPECT_NO_FATAL_FAILURE(RemoveUserSpecifiedSites(
+      profile(), base::StringPrintf(R"(["%s"])", kExample),
+      /*restricted=*/true));
+
+  EXPECT_THAT(manager->GetUserPermissionsSettings().permitted_sites,
+              testing::IsEmpty());
+  EXPECT_THAT(manager->GetUserPermissionsSettings().restricted_sites,
+              testing::UnorderedElementsAre(chromium_url));
+}
+
+// Test that the OnUserSiteSettingsChanged event is fired whenever the user
+// defined site settings update.
+TEST_F(DeveloperPrivateApiUnitTest, OnUserSiteSettingsChanged) {
+  static constexpr char kExample[] = "http://example.com";
+
+  // We need to call DeveloperPrivateAPI::Get() in order to instantiate the
+  // keyed service, since it's not created by default in unit tests.
+  DeveloperPrivateAPI::Get(profile());
+  EventRouter* event_router = EventRouter::Get(profile());
+
+  // The DeveloperPrivateEventRouter will only dispatch events if there's at
+  // least one listener to dispatch to. Create one.
+  const char* kEventName =
+      api::developer_private::OnUserSiteSettingsChanged::kEventName;
+  event_router->AddEventListener(kEventName, render_process_host(),
+                                 crx_file::id_util::GenerateId("listener"));
+
+  TestEventRouterObserver test_observer(event_router);
+
+  api::developer_private::UserSiteSettings settings;
+  EXPECT_FALSE(
+      WasUserSiteSettingsChangedEventDispatched(test_observer, &settings));
+
+  // Add a restricted site, and check the event that it's
+  // only contained in the restricted list.
+  const std::string kExampleArg = base::StringPrintf(R"(["%s"])", kExample);
+  EXPECT_NO_FATAL_FAILURE(
+      AddUserSpecifiedSites(profile(), kExampleArg, /*restricted=*/true));
+  EXPECT_TRUE(
+      WasUserSiteSettingsChangedEventDispatched(test_observer, &settings));
+  EXPECT_THAT(settings.permitted_sites, testing::IsEmpty());
+  EXPECT_THAT(settings.restricted_sites,
+              testing::UnorderedElementsAre(kExample));
+
+  // Remove the site, and check the event that both lists are empty.
+  EXPECT_NO_FATAL_FAILURE(
+      RemoveUserSpecifiedSites(profile(), kExampleArg, /*restricted=*/true));
+  EXPECT_TRUE(
+      WasUserSiteSettingsChangedEventDispatched(test_observer, &settings));
+  EXPECT_THAT(settings.permitted_sites, testing::IsEmpty());
+  EXPECT_THAT(settings.restricted_sites, testing::IsEmpty());
+}
+
+class DeveloperPrivateApiWithPermittedSitesUnitTest
+    : public DeveloperPrivateApiUnitTest {
+ public:
+  DeveloperPrivateApiWithPermittedSitesUnitTest();
+  DeveloperPrivateApiWithPermittedSitesUnitTest(
+      const DeveloperPrivateApiWithPermittedSitesUnitTest&) = delete;
+  const DeveloperPrivateApiWithPermittedSitesUnitTest& operator=(
+      const DeveloperPrivateApiWithPermittedSitesUnitTest&) = delete;
+  ~DeveloperPrivateApiWithPermittedSitesUnitTest() override = default;
+
+ private:
+  base::test::ScopedFeatureList feature_list_;
+};
+
+DeveloperPrivateApiWithPermittedSitesUnitTest::
+    DeveloperPrivateApiWithPermittedSitesUnitTest() {
+  feature_list_.InitAndEnableFeature(
+      extensions_features::kExtensionsMenuAccessControlWithPermittedSites);
+}
+
+// Test developerPrivate.getUserSiteSettings.
+TEST_F(DeveloperPrivateApiWithPermittedSitesUnitTest,
+       DeveloperPrivateGetUserSiteSettings) {
+  PermissionsManager* manager = PermissionsManager::Get(browser_context());
   const url::Origin permitted_url =
       url::Origin::Create(GURL("http://a.example.com"));
   const url::Origin restricted_url =
@@ -2012,9 +2132,9 @@
               testing::UnorderedElementsAre("http://b.example.com"));
 }
 
-// Test developerPrivate.addUserPermittedSite, addUserRestrictedSite and
-// removeUserSpecifiedSite.
-TEST_F(DeveloperPrivateApiUnitTest, DeveloperPrivateModifyUserSiteSettings) {
+// Test developerPrivate.addUserSpecifiedSite and removeUserSpecifiedSite.
+TEST_F(DeveloperPrivateApiWithPermittedSitesUnitTest,
+       DeveloperPrivateModifyUserSiteSettings) {
   static constexpr char kExample[] = "http://example.com";
   static constexpr char kChromium[] = "http://chromium.org";
   static constexpr char kGoogle[] = "http://google.com";
@@ -2057,60 +2177,7 @@
   EXPECT_TRUE(manager->GetUserPermissionsSettings().restricted_sites.empty());
 }
 
-// Test that the OnUserSiteSettingsChanged event is fired whenever the user
-// defined site settings updates.
-TEST_F(DeveloperPrivateApiUnitTest, OnUserSiteSettingsChanged) {
-  static constexpr char kExample[] = "http://example.com";
-
-  // We need to call DeveloperPrivateAPI::Get() in order to instantiate the
-  // keyed service, since it's not created by default in unit tests.
-  DeveloperPrivateAPI::Get(profile());
-  EventRouter* event_router = EventRouter::Get(profile());
-
-  // The DeveloperPrivateEventRouter will only dispatch events if there's at
-  // least one listener to dispatch to. Create one.
-  const char* kEventName =
-      api::developer_private::OnUserSiteSettingsChanged::kEventName;
-  event_router->AddEventListener(kEventName, render_process_host(),
-                                 crx_file::id_util::GenerateId("listener"));
-
-  TestEventRouterObserver test_observer(event_router);
-
-  api::developer_private::UserSiteSettings settings;
-  EXPECT_FALSE(
-      WasUserSiteSettingsChangedEventDispatched(test_observer, &settings));
-
-  // Add a permitted site, and check that it is contained within the event's
-  // payload.
-  const std::string kExampleArg = base::StringPrintf(R"(["%s"])", kExample);
-  EXPECT_NO_FATAL_FAILURE(
-      AddUserSpecifiedSites(profile(), kExampleArg, /*restricted=*/false));
-  EXPECT_TRUE(
-      WasUserSiteSettingsChangedEventDispatched(test_observer, &settings));
-  EXPECT_THAT(settings.permitted_sites,
-              testing::UnorderedElementsAre(kExample));
-  EXPECT_TRUE(settings.restricted_sites.empty());
-
-  // Add the same site to the restricted site, and check the event that it's
-  // only contained in the restricted list.
-  EXPECT_NO_FATAL_FAILURE(
-      AddUserSpecifiedSites(profile(), kExampleArg, /*restricted=*/true));
-  EXPECT_TRUE(
-      WasUserSiteSettingsChangedEventDispatched(test_observer, &settings));
-  EXPECT_TRUE(settings.permitted_sites.empty());
-  EXPECT_THAT(settings.restricted_sites,
-              testing::UnorderedElementsAre(kExample));
-
-  // Remove the site, and check the event that both lists are empty.
-  EXPECT_NO_FATAL_FAILURE(
-      RemoveUserSpecifiedSites(profile(), kExampleArg, /*restricted=*/true));
-  EXPECT_TRUE(
-      WasUserSiteSettingsChangedEventDispatched(test_observer, &settings));
-  EXPECT_TRUE(settings.permitted_sites.empty());
-  EXPECT_TRUE(settings.restricted_sites.empty());
-}
-
-TEST_F(DeveloperPrivateApiUnitTest,
+TEST_F(DeveloperPrivateApiWithPermittedSitesUnitTest,
        DeveloperPrivateGetUserAndExtensionSitesByEtld_UserSites) {
   PermissionsManager* manager = PermissionsManager::Get(browser_context());
 
@@ -2151,7 +2218,7 @@
   }])"));
 }
 
-TEST_F(DeveloperPrivateApiUnitTest,
+TEST_F(DeveloperPrivateApiWithPermittedSitesUnitTest,
        DeveloperPrivateGetUserAndExtensionSitesByEtld_UserAndExtensionSites) {
   PermissionsManager* manager = PermissionsManager::Get(browser_context());
   manager->AddUserPermittedSite(
@@ -2234,7 +2301,7 @@
   }])"));
 }
 
-TEST_F(DeveloperPrivateApiUnitTest,
+TEST_F(DeveloperPrivateApiWithPermittedSitesUnitTest,
        DeveloperPrivateGetUserAndExtensionSitesByEtld_EffectiveAllHosts) {
   PermissionsManager* manager = PermissionsManager::Get(browser_context());
   manager->AddUserPermittedSite(
@@ -2263,8 +2330,8 @@
   const base::Value::List* results = function->GetResultListForTest();
   ASSERT_EQ(1u, results->size());
 
-  // `extension_2` should not be counted for https://*.google.ca/* as it cannot
-  // run on .ca sites.
+  // `extension_2` should not be counted for https://*.google.ca/* as it
+  // cannot run on .ca sites.
   EXPECT_THAT((*results)[0], base::test::IsJson(R"([{
     "etldPlusOne": "example.com",
     "numExtensions": 3,
diff --git a/chrome/browser/extensions/api/pdf_viewer_private/pdf_viewer_private_event_router.cc b/chrome/browser/extensions/api/pdf_viewer_private/pdf_viewer_private_event_router.cc
new file mode 100644
index 0000000..fa93ed64
--- /dev/null
+++ b/chrome/browser/extensions/api/pdf_viewer_private/pdf_viewer_private_event_router.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 "chrome/browser/extensions/api/pdf_viewer_private/pdf_viewer_private_event_router.h"
+
+#include "base/functional/bind.h"
+#include "chrome/common/extensions/api/pdf_viewer_private.h"
+#include "chrome/common/pref_names.h"
+#include "components/prefs/pref_service.h"
+
+namespace extensions {
+
+// static
+PdfViewerPrivateEventRouter* PdfViewerPrivateEventRouter::Create(
+    content::BrowserContext* context) {
+  DCHECK(context);
+  Profile* profile = Profile::FromBrowserContext(context);
+  return new PdfViewerPrivateEventRouter(profile);
+}
+
+PdfViewerPrivateEventRouter::PdfViewerPrivateEventRouter(Profile* profile)
+    : profile_(profile) {
+  DCHECK(profile_);
+  user_prefs_registrar_.Init(profile_->GetPrefs());
+  EventRouter::Get(profile_)->RegisterObserver(
+      this, api::pdf_viewer_private::OnPdfOcrPrefChanged::kEventName);
+  StartOrStopListeningForPdfOcrPrefChanges();
+}
+
+PdfViewerPrivateEventRouter::~PdfViewerPrivateEventRouter() {
+  DCHECK(user_prefs_registrar_.IsEmpty());
+}
+
+void PdfViewerPrivateEventRouter::Shutdown() {
+  EventRouter::Get(profile_)->UnregisterObserver(this);
+  if (!user_prefs_registrar_.IsEmpty()) {
+    user_prefs_registrar_.Remove(prefs::kAccessibilityPdfOcrAlwaysActive);
+  }
+}
+
+void PdfViewerPrivateEventRouter::OnListenerAdded(
+    const EventListenerInfo& details) {
+  // Start listening to events from the user registrar for the PDF OCR pref.
+  StartOrStopListeningForPdfOcrPrefChanges();
+}
+
+void PdfViewerPrivateEventRouter::OnListenerRemoved(
+    const EventListenerInfo& details) {
+  // Stop listening to events from the user registrar for the PDF OCR pref
+  // if there are no more listeners.
+  StartOrStopListeningForPdfOcrPrefChanges();
+}
+
+void PdfViewerPrivateEventRouter::StartOrStopListeningForPdfOcrPrefChanges() {
+  EventRouter* event_router = EventRouter::Get(profile_);
+  bool should_listen = event_router->HasEventListener(
+      api::pdf_viewer_private::OnPdfOcrPrefChanged::kEventName);
+
+  if (should_listen && user_prefs_registrar_.IsEmpty()) {
+    // base::Unretained() is safe since `this` will be destroyed after this
+    // listener is removed.
+    user_prefs_registrar_.Add(
+        prefs::kAccessibilityPdfOcrAlwaysActive,
+        base::BindRepeating(
+            &PdfViewerPrivateEventRouter::OnPdfOcrPreferenceChanged,
+            base::Unretained(this)));
+  } else if (!should_listen && !user_prefs_registrar_.IsEmpty()) {
+    user_prefs_registrar_.Remove(prefs::kAccessibilityPdfOcrAlwaysActive);
+  }
+}
+
+void PdfViewerPrivateEventRouter::OnPdfOcrPreferenceChanged() {
+  EventRouter* event_router = EventRouter::Get(profile_);
+  if (!event_router->HasEventListener(
+          api::pdf_viewer_private::OnPdfOcrPrefChanged::kEventName)) {
+    return;
+  }
+  // Send the changed value of the PDF OCR pref to observers.
+  base::Value::List event_arg;
+  bool is_pdf_ocr_always_active =
+      profile_->GetPrefs()->GetBoolean(prefs::kAccessibilityPdfOcrAlwaysActive);
+  event_arg.Append(is_pdf_ocr_always_active);
+  event_router->BroadcastEvent(std::make_unique<Event>(
+      events::PDF_VIEWER_PRIVATE_ON_PDF_OCR_PREF_CHANGED,
+      api::pdf_viewer_private::OnPdfOcrPrefChanged::kEventName,
+      std::move(event_arg)));
+}
+
+}  // namespace extensions
diff --git a/chrome/browser/extensions/api/pdf_viewer_private/pdf_viewer_private_event_router.h b/chrome/browser/extensions/api/pdf_viewer_private/pdf_viewer_private_event_router.h
new file mode 100644
index 0000000..4035fac
--- /dev/null
+++ b/chrome/browser/extensions/api/pdf_viewer_private/pdf_viewer_private_event_router.h
@@ -0,0 +1,58 @@
+// Copyright 2023 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_EXTENSIONS_API_PDF_VIEWER_PRIVATE_PDF_VIEWER_PRIVATE_EVENT_ROUTER_H_
+#define CHROME_BROWSER_EXTENSIONS_API_PDF_VIEWER_PRIVATE_PDF_VIEWER_PRIVATE_EVENT_ROUTER_H_
+
+#include "base/memory/raw_ptr.h"
+#include "chrome/browser/profiles/profile.h"
+#include "components/keyed_service/core/keyed_service.h"
+#include "components/prefs/pref_change_registrar.h"
+#include "extensions/browser/event_router.h"
+
+namespace content {
+class BrowserContext;
+}
+
+namespace extensions {
+
+class PdfViewerPrivateEventRouter : public KeyedService,
+                                    public EventRouter::Observer {
+ public:
+  static PdfViewerPrivateEventRouter* Create(content::BrowserContext* context);
+
+  explicit PdfViewerPrivateEventRouter(Profile* profile);
+
+  PdfViewerPrivateEventRouter(const PdfViewerPrivateEventRouter&) = delete;
+  PdfViewerPrivateEventRouter& operator=(const PdfViewerPrivateEventRouter&) =
+      delete;
+
+  ~PdfViewerPrivateEventRouter() override;
+
+  // KeyedService overrides:
+  void Shutdown() override;
+
+  // EventRouter::Observer overrides:
+  void OnListenerAdded(const EventListenerInfo& details) override;
+  void OnListenerRemoved(const EventListenerInfo& details) override;
+
+ private:
+  // Decide if we should listen for pref changes or not. If there are any
+  // JavaScript listeners registered for the onPdfOcrPrefChanged event, then we
+  // want to register for change notification from the user registrar.
+  // Otherwise, we want to unregister and not be listening for pref changes.
+  void StartOrStopListeningForPdfOcrPrefChanges();
+
+  // Sends a pref change to any listeners (if they exist; no-ops otherwise).
+  void OnPdfOcrPreferenceChanged();
+
+  const raw_ptr<Profile> profile_ = nullptr;
+
+  // This registrar monitors for user prefs changes.
+  PrefChangeRegistrar user_prefs_registrar_;
+};
+
+}  // namespace extensions
+
+#endif  // CHROME_BROWSER_EXTENSIONS_API_PDF_VIEWER_PRIVATE_PDF_VIEWER_PRIVATE_EVENT_ROUTER_H_
diff --git a/chrome/browser/extensions/api/pdf_viewer_private/pdf_viewer_private_event_router_factory.cc b/chrome/browser/extensions/api/pdf_viewer_private/pdf_viewer_private_event_router_factory.cc
new file mode 100644
index 0000000..eb6e6abe
--- /dev/null
+++ b/chrome/browser/extensions/api/pdf_viewer_private/pdf_viewer_private_event_router_factory.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/extensions/api/pdf_viewer_private/pdf_viewer_private_event_router_factory.h"
+
+#include "chrome/browser/extensions/api/pdf_viewer_private/pdf_viewer_private_event_router.h"
+#include "content/public/browser/browser_context.h"
+#include "extensions/browser/event_router_factory.h"
+#include "extensions/browser/extension_system_provider.h"
+#include "extensions/browser/extensions_browser_client.h"
+
+namespace extensions {
+
+// static
+PdfViewerPrivateEventRouter* PdfViewerPrivateEventRouterFactory::GetForProfile(
+    content::BrowserContext* context) {
+  return static_cast<PdfViewerPrivateEventRouter*>(
+      GetInstance()->GetServiceForBrowserContext(context, true));
+}
+
+// static
+PdfViewerPrivateEventRouterFactory*
+PdfViewerPrivateEventRouterFactory::GetInstance() {
+  return base::Singleton<PdfViewerPrivateEventRouterFactory>::get();
+}
+
+PdfViewerPrivateEventRouterFactory::PdfViewerPrivateEventRouterFactory()
+    : ProfileKeyedServiceFactory(
+          "PdfViewerPrivateEventRouter",
+          ProfileSelections::BuildForRegularAndIncognito()) {
+  DependsOn(ExtensionsBrowserClient::Get()->GetExtensionSystemFactory());
+  DependsOn(EventRouterFactory::GetInstance());
+}
+
+PdfViewerPrivateEventRouterFactory::~PdfViewerPrivateEventRouterFactory() =
+    default;
+
+KeyedService* PdfViewerPrivateEventRouterFactory::BuildServiceInstanceFor(
+    content::BrowserContext* context) const {
+  return PdfViewerPrivateEventRouter::Create(context);
+}
+
+bool PdfViewerPrivateEventRouterFactory::ServiceIsCreatedWithBrowserContext()
+    const {
+  return true;
+}
+
+bool PdfViewerPrivateEventRouterFactory::ServiceIsNULLWhileTesting() const {
+  return true;
+}
+
+}  // namespace extensions
diff --git a/chrome/browser/extensions/api/pdf_viewer_private/pdf_viewer_private_event_router_factory.h b/chrome/browser/extensions/api/pdf_viewer_private/pdf_viewer_private_event_router_factory.h
new file mode 100644
index 0000000..faebd6c6
--- /dev/null
+++ b/chrome/browser/extensions/api/pdf_viewer_private/pdf_viewer_private_event_router_factory.h
@@ -0,0 +1,52 @@
+// 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_EXTENSIONS_API_PDF_VIEWER_PRIVATE_PDF_VIEWER_PRIVATE_EVENT_ROUTER_FACTORY_H_
+#define CHROME_BROWSER_EXTENSIONS_API_PDF_VIEWER_PRIVATE_PDF_VIEWER_PRIVATE_EVENT_ROUTER_FACTORY_H_
+
+#include "base/memory/singleton.h"
+#include "chrome/browser/profiles/profile_keyed_service_factory.h"
+
+namespace extensions {
+
+class PdfViewerPrivateEventRouter;
+
+// This is a factory class used by the BrowserContextDependencyManager
+// to instantiate the pdfViewerPrivate event router per profile (since the
+// extension event router is per profile).
+class PdfViewerPrivateEventRouterFactory : public ProfileKeyedServiceFactory {
+ public:
+  PdfViewerPrivateEventRouterFactory(
+      const PdfViewerPrivateEventRouterFactory&) = delete;
+  PdfViewerPrivateEventRouterFactory& operator=(
+      const PdfViewerPrivateEventRouterFactory&) = delete;
+
+  // Returns the PdfViewerPrivateEventRouter for |profile|, creating it if
+  // it is not yet created.
+  static PdfViewerPrivateEventRouter* GetForProfile(
+      content::BrowserContext* context);
+
+  // Returns the PdfViewerPrivateEventRouterFactory instance.
+  static PdfViewerPrivateEventRouterFactory* GetInstance();
+
+ protected:
+  // BrowserContextKeyedServiceFactory overrides:
+  bool ServiceIsCreatedWithBrowserContext() const override;
+  bool ServiceIsNULLWhileTesting() const override;
+
+ private:
+  friend struct base::DefaultSingletonTraits<
+      PdfViewerPrivateEventRouterFactory>;
+
+  PdfViewerPrivateEventRouterFactory();
+  ~PdfViewerPrivateEventRouterFactory() override;
+
+  // BrowserContextKeyedServiceFactory:
+  KeyedService* BuildServiceInstanceFor(
+      content::BrowserContext* profile) const override;
+};
+
+}  // namespace extensions
+
+#endif  // CHROME_BROWSER_EXTENSIONS_API_PDF_VIEWER_PRIVATE_PDF_VIEWER_PRIVATE_EVENT_ROUTER_FACTORY_H_
diff --git a/chrome/browser/extensions/corb_and_cors_extension_browsertest.cc b/chrome/browser/extensions/corb_and_cors_extension_browsertest.cc
index f6e9a74..3a20d8c 100644
--- a/chrome/browser/extensions/corb_and_cors_extension_browsertest.cc
+++ b/chrome/browser/extensions/corb_and_cors_extension_browsertest.cc
@@ -881,8 +881,10 @@
     : public CorbAndCorsExtensionBrowserTest {
  public:
   CorbAndCorsUserHostRestrictionsBrowserTest() {
-    scoped_feature_list_.InitAndEnableFeature(
-        extensions_features::kExtensionsMenuAccessControl);
+    std::vector<base::test::FeatureRef> enabled_features = {
+        extensions_features::kExtensionsMenuAccessControl,
+        extensions_features::kExtensionsMenuAccessControlWithPermittedSites};
+    scoped_feature_list_.InitWithFeatures(enabled_features, {});
   }
 
  private:
@@ -909,12 +911,15 @@
 
   PermissionsManager* permissions_manager = PermissionsManager::Get(profile());
   {
+    // Sites can be set as restricted iff the `host controls` flag is enabled.
     PermissionsManagerWaiter waiter(permissions_manager);
     permissions_manager->AddUserRestrictedSite(
         url::Origin::Create(policy_allowed_resource));
     waiter.WaitForUserPermissionsSettingsChange();
   }
   {
+    // Sites can be set as permitted iff the `host controls` and `permitted
+    // sites` flags are enabled.
     PermissionsManagerWaiter waiter(permissions_manager);
     permissions_manager->AddUserPermittedSite(
         url::Origin::Create(policy_restricted_resource));
diff --git a/chrome/browser/extensions/extension_action_runner.cc b/chrome/browser/extensions/extension_action_runner.cc
index 4aade600..3fad63d 100644
--- a/chrome/browser/extensions/extension_action_runner.cc
+++ b/chrome/browser/extensions/extension_action_runner.cc
@@ -43,6 +43,7 @@
 #include "extensions/browser/permissions_manager.h"
 #include "extensions/common/api/extension_action/action_info.h"
 #include "extensions/common/extension.h"
+#include "extensions/common/extension_features.h"
 #include "extensions/common/extension_id.h"
 #include "extensions/common/extension_set.h"
 #include "extensions/common/manifest.h"
@@ -209,6 +210,13 @@
     const base::flat_set<ToolbarActionsModel::ActionId>& action_ids,
     const url::Origin& origin,
     PermissionsManager::UserSiteSetting new_site_settings) {
+  // Granting access to all extensions is only allowed iff feature is enabled.
+  DCHECK(
+      new_site_settings !=
+          PermissionsManager::UserSiteSetting::kGrantAllExtensions ||
+      base::FeatureList::IsEnabled(
+          extensions_features::kExtensionsMenuAccessControlWithPermittedSites));
+
   auto* registry = ExtensionRegistry::Get(browser_context_);
   std::vector<const Extension*> extensions;
   extensions.reserve(action_ids.size());
diff --git a/chrome/browser/extensions/extension_action_runner_browsertest.cc b/chrome/browser/extensions/extension_action_runner_browsertest.cc
index 6edae3d8..2e7c453c 100644
--- a/chrome/browser/extensions/extension_action_runner_browsertest.cc
+++ b/chrome/browser/extensions/extension_action_runner_browsertest.cc
@@ -771,9 +771,134 @@
             SiteAccess::kOnAllSites);
   EXPECT_TRUE(DidInjectScript(web_contents()));
 
+  // "customize by extension (on site)" -> "block all extensions":
+  // not accepting the page reload bubble maintains the same user site
+  // setting and keeps the script injected.
+  HandleUserSiteSettingModified(extension->id(), url_origin,
+                                UserSiteSetting::kBlockAllExtensions, false);
+  EXPECT_EQ(permissions_manager()->GetUserSiteSetting(url_origin),
+            UserSiteSetting::kCustomizeByExtension);
+  EXPECT_TRUE(DidInjectScript(web_contents()));
+
+  // "customize by extension (on site)" -> "block all extensions":
+  // accepting the page reload bubble revokes site access, refreshes the page
+  // and does not inject the script. (Note: we will accept all the next reload
+  // page bubbles since we previously checked the behavior of not accepting the
+  // dialog).
+  HandleUserSiteSettingModified(extension->id(), url_origin,
+                                UserSiteSetting::kBlockAllExtensions, true);
+  EXPECT_EQ(permissions_manager()->GetUserSiteSetting(url_origin),
+            UserSiteSetting::kBlockAllExtensions);
+  EXPECT_FALSE(DidInjectScript(web_contents()));
+
+  // "block all extensions" -> "customize by extension (on site)":
+  // grants site access, refreshes the page and injects the script.
+  HandleUserSiteSettingModified(extension->id(), url_origin,
+                                UserSiteSetting::kCustomizeByExtension, true);
+  EXPECT_EQ(permissions_manager()->GetUserSiteSetting(url_origin),
+            UserSiteSetting::kCustomizeByExtension);
+  EXPECT_TRUE(DidInjectScript(web_contents()));
+}
+
+// Tests changing user site settings when the extension does not have site
+// access ('on click'). Note that we don't check if extension
+// `WantsToRun` because on user-restricted sites, actions are blocked rather
+// than withheld.
+IN_PROC_BROWSER_TEST_F(ExtensionActionRunnerWithUserHostControlsBrowserTest,
+                       HandleUserSiteSettingModified_ExtensionHasNoAccess) {
+  // Load an extension that wants to run on every page at document start.
+  const Extension* extension = LoadExtension(
+      test_data_dir_.AppendASCII("blocked_actions/content_scripts"));
+  ASSERT_TRUE(extension);
+
+  // Withheld extension's host permission, so extension has "on click" site
+  // access.
+  ScriptingPermissionsModifier(profile(), extension)
+      .SetWithholdHostPermissions(true);
+
+  // Navigate to a page where the extension wants to run.
+  const GURL url = embedded_test_server()->GetURL("/simple.html");
+  const url::Origin url_origin = url::Origin::Create(url);
+  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url));
+
+  // The page should have "customize by extension" user site setting and "on
+  // click" site access. The extension should not have injected.
+  EXPECT_EQ(permissions_manager()->GetUserSiteSetting(url_origin),
+            UserSiteSetting::kCustomizeByExtension);
+  EXPECT_EQ(SitePermissionsHelper(profile()).GetSiteAccess(*extension, url),
+            SiteAccess::kOnClick);
+  EXPECT_FALSE(DidInjectScript(web_contents()));
+
+  // "customize by extension (on click)" -> "block all extensions":
+  // maintains current site access, and script is still not injected. No refresh
+  // is required.
+  HandleUserSiteSettingModified(extension->id(), url_origin,
+                                UserSiteSetting::kBlockAllExtensions, false);
+  EXPECT_EQ(permissions_manager()->GetUserSiteSetting(url_origin),
+            UserSiteSetting::kBlockAllExtensions);
+  EXPECT_FALSE(DidInjectScript(web_contents()));
+
+  // "block all extensions" -> "customize by extension (on click)":
+  // maintains current site access, refreshes the page and still does not inject
+  // the script.
+  HandleUserSiteSettingModified(extension->id(), url_origin,
+                                UserSiteSetting::kCustomizeByExtension, true);
+  EXPECT_EQ(permissions_manager()->GetUserSiteSetting(url_origin),
+            UserSiteSetting::kCustomizeByExtension);
+  EXPECT_FALSE(DidInjectScript(web_contents()));
+}
+
+class ExtensionActionRunnerWithUserHostControlsAndPermittedSitesBrowserTest
+    : public ExtensionActionRunnerWithUserHostControlsBrowserTest {
+ public:
+  ExtensionActionRunnerWithUserHostControlsAndPermittedSitesBrowserTest() {
+    feature_list_.InitAndEnableFeature(
+        extensions_features::kExtensionsMenuAccessControlWithPermittedSites);
+  }
+  ~ExtensionActionRunnerWithUserHostControlsAndPermittedSitesBrowserTest()
+      override = default;
+
+ private:
+  base::test::ScopedFeatureList feature_list_;
+};
+
+// Tests changing user site settings when the extension has site access (which
+// is either 'on all sites' or 'on site'). Note that we don't check if extension
+// `WantsToRun` because on user-restricted sites, actions are blocked rather
+// than withheld.
+// TODO(crbug.com/1363781): Flaky on Win 7 and Mac 12.
+#if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC)
+#define MAYBE_HandleUserSiteSettingModified_ExtensionHasAccess \
+  DISABLED_HandleUserSiteSettingModified_ExtensionHasAccess
+#else
+#define MAYBE_HandleUserSiteSettingModified_ExtensionHasAccess \
+  HandleUserSiteSettingModified_ExtensionHasAccess
+#endif
+IN_PROC_BROWSER_TEST_F(
+    ExtensionActionRunnerWithUserHostControlsAndPermittedSitesBrowserTest,
+    MAYBE_HandleUserSiteSettingModified_ExtensionHasAccess) {
+  // Load an extension that wants to run on every page at document start.
+  const Extension* extension = LoadExtension(
+      test_data_dir_.AppendASCII("blocked_actions/content_scripts"));
+  ASSERT_TRUE(extension);
+
+  // Navigate to a page where the extension can run.
+  const GURL url = embedded_test_server()->GetURL("/simple.html");
+  const url::Origin url_origin = url::Origin::Create(url);
+  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url));
+
+  // By default, the page should have "customize by extension" user site
+  // setting and the extensions should have "on all sites" site access. The
+  // extension should have injected.
+  EXPECT_EQ(permissions_manager()->GetUserSiteSetting(url_origin),
+            UserSiteSetting::kCustomizeByExtension);
+  EXPECT_EQ(SitePermissionsHelper(profile()).GetSiteAccess(*extension, url),
+            SiteAccess::kOnAllSites);
+  EXPECT_TRUE(DidInjectScript(web_contents()));
+
   // "customize by extension (on site) -> "grant all extensions":
-  // maintains current site access and keeps the script injected. No refresh is
-  // required.
+  // maintains current site access and keeps the script injected. No refresh
+  // is required.
   HandleUserSiteSettingModified(extension->id(), url_origin,
                                 UserSiteSetting::kGrantAllExtensions, false);
   EXPECT_EQ(permissions_manager()->GetUserSiteSetting(url_origin),
@@ -792,8 +917,8 @@
   // "grant all extensions" -> "block all extensions":
   // accepting the page reload bubble revokes site access, refreshes the page
   // and does not inject the script. (Note: we will accept all the next reload
-  // page bubbles since we previously checked the behavior of not accepting the
-  // dialog).
+  // page bubbles since we previously checked the behavior of not accepting
+  // the dialog).
   HandleUserSiteSettingModified(extension->id(), url_origin,
                                 UserSiteSetting::kBlockAllExtensions, true);
   EXPECT_EQ(permissions_manager()->GetUserSiteSetting(url_origin),
@@ -839,8 +964,9 @@
 // access ('on click'). Note that we don't check if extension
 // `WantsToRun` because on user-restricted sites, actions are blocked rather
 // than withheld.
-IN_PROC_BROWSER_TEST_F(ExtensionActionRunnerWithUserHostControlsBrowserTest,
-                       HandleUserSiteSettingModified_ExtensionHasNoAccess) {
+IN_PROC_BROWSER_TEST_F(
+    ExtensionActionRunnerWithUserHostControlsAndPermittedSitesBrowserTest,
+    HandleUserSiteSettingModified_ExtensionHasNoAccess) {
   // Load an extension that wants to run on every page at document start.
   const Extension* extension = LoadExtension(
       test_data_dir_.AppendASCII("blocked_actions/content_scripts"));
diff --git a/chrome/browser/extensions/extension_context_menu_model_unittest.cc b/chrome/browser/extensions/extension_context_menu_model_unittest.cc
index 7307a7f..ae85701 100644
--- a/chrome/browser/extensions/extension_context_menu_model_unittest.cc
+++ b/chrome/browser/extensions/extension_context_menu_model_unittest.cc
@@ -1923,12 +1923,8 @@
       public testing::WithParamInterface<bool> {
  public:
   ExtensionContextMenuModelWithUserHostControlsTest() {
-    const base::Feature& feature =
-        extensions_features::kExtensionsMenuAccessControl;
-    if (GetParam())
-      feature_list_.InitAndEnableFeature(feature);
-    else
-      feature_list_.InitAndDisableFeature(feature);
+    feature_list_.InitWithFeatureState(
+        extensions_features::kExtensionsMenuAccessControl, GetParam());
   }
   ~ExtensionContextMenuModelWithUserHostControlsTest() override = default;
 
@@ -1984,54 +1980,7 @@
   auto* manager = extensions::PermissionsManager::Get(profile());
 
   {
-    // Add site as a user permitted site.
-    extensions::PermissionsManagerWaiter manager_waiter(manager);
-    manager->AddUserPermittedSite(url::Origin::Create(url));
-    manager_waiter.WaitForUserPermissionsSettingsChange();
-
-    ExtensionContextMenuModel menu(extension, GetBrowser(),
-                                   ExtensionContextMenuModel::PINNED, nullptr,
-                                   true, ContextMenuSource::kToolbarAction);
-
-    if (GetParam()) {
-      // Verify "grant all extensions" item is visible and disabled, and the
-      // "learn more" and "permissions page" item are in the context menu.
-      EXPECT_EQ(GetCommandState(menu, kGrantAllExtensions),
-                CommandState::kDisabled);
-      EXPECT_EQ(GetCommandState(menu, kBlockAllExtensions),
-                CommandState::kAbsent);
-      EXPECT_EQ(GetCommandState(menu, kPageAccessSubmenu),
-                CommandState::kAbsent);
-      EXPECT_EQ(GetCommandState(menu, kLearnMore), CommandState::kEnabled);
-      EXPECT_EQ(GetPageAccessCommandState(menu, kLearnMore),
-                CommandState::kAbsent);
-      EXPECT_EQ(GetCommandState(menu, kPermissionsPage),
-                CommandState::kEnabled);
-      EXPECT_EQ(GetPageAccessCommandState(menu, kLearnMore),
-                CommandState::kAbsent);
-    } else {
-      // Even though we added a site as a user permitted site, the site
-      // permission behaves as "customize by extension". Verify page access
-      // submenu is visible and enabled, the "learn more" item is in in the
-      // submenu and the "permissions page" item is nowhere visible.
-      EXPECT_EQ(GetCommandState(menu, kGrantAllExtensions),
-                CommandState::kAbsent);
-      EXPECT_EQ(GetCommandState(menu, kBlockAllExtensions),
-                CommandState::kAbsent);
-      EXPECT_EQ(GetCommandState(menu, kPageAccessSubmenu),
-                CommandState::kEnabled);
-      EXPECT_EQ(GetCommandState(menu, kLearnMore), CommandState::kAbsent);
-      EXPECT_EQ(GetPageAccessCommandState(menu, kLearnMore),
-                CommandState::kEnabled);
-      EXPECT_EQ(GetCommandState(menu, kPermissionsPage), CommandState::kAbsent);
-      EXPECT_EQ(GetPageAccessCommandState(menu, kPermissionsPage),
-                CommandState::kAbsent);
-    }
-  }
-
-  {
-    // Add site as a user restricted site. Note that adding a site as restricted
-    // site removes it from the permitted sites.
+    // Add site as a user restricted site.
     extensions::PermissionsManagerWaiter manager_waiter(manager);
     manager->AddUserRestrictedSite(url::Origin::Create(url));
     manager_waiter.WaitForUserPermissionsSettingsChange();
@@ -2128,4 +2077,113 @@
             GURL(chrome_extension_constants::kExtensionsSitePermissionsURL));
 }
 
+class ExtensionContextMenuModelWithUserHostControlsAndPermittedSitesTest
+    : public ExtensionContextMenuModelWithUserHostControlsTest {
+ public:
+  ExtensionContextMenuModelWithUserHostControlsAndPermittedSitesTest() {
+    feature_list_.InitAndEnableFeature(
+        extensions_features::kExtensionsMenuAccessControlWithPermittedSites);
+  }
+  ~ExtensionContextMenuModelWithUserHostControlsAndPermittedSitesTest()
+      override = default;
+
+ private:
+  base::test::ScopedFeatureList feature_list_;
+};
+
+INSTANTIATE_TEST_SUITE_P(
+    All,
+    ExtensionContextMenuModelWithUserHostControlsAndPermittedSitesTest,
+    testing::Bool());
+
+TEST_P(ExtensionContextMenuModelWithUserHostControlsAndPermittedSitesTest,
+       PageAccessItemsVisibilityBasedOnSiteSettings) {
+  InitializeEmptyExtensionService();
+
+  const Extension* extension =
+      AddExtensionWithHostPermission("extension", manifest_keys::kBrowserAction,
+                                     ManifestLocation::kInternal, "<all_urls>");
+
+  // Add a tab to the browser.
+  const GURL url("http://www.example.com/");
+  AddTab(url);
+
+  {
+    // By default, the site permission is set to "customize by extension".
+    // Verify page access submenu is visible and enabled, and the "learn more"
+    // item is in in the submenu.
+    ExtensionContextMenuModel menu(extension, GetBrowser(),
+                                   ExtensionContextMenuModel::PINNED, nullptr,
+                                   true, ContextMenuSource::kToolbarAction);
+    EXPECT_EQ(GetCommandState(menu, kGrantAllExtensions),
+              CommandState::kAbsent);
+    EXPECT_EQ(GetCommandState(menu, kBlockAllExtensions),
+              CommandState::kAbsent);
+    EXPECT_EQ(GetCommandState(menu, kPageAccessSubmenu),
+              CommandState::kEnabled);
+    EXPECT_EQ(GetCommandState(menu, kLearnMore), CommandState::kAbsent);
+    EXPECT_EQ(GetPageAccessCommandState(menu, kLearnMore),
+              CommandState::kEnabled);
+    // The "permissions page" item is in the submenu only if the feature is
+    // enabled.
+    EXPECT_EQ(GetCommandState(menu, kPermissionsPage), CommandState::kAbsent);
+    CommandState permission_page_state =
+        GetParam() ? CommandState::kEnabled : CommandState::kAbsent;
+    EXPECT_EQ(GetPageAccessCommandState(menu, kPermissionsPage),
+              permission_page_state);
+  }
+
+  // User site settings are only taken into account for site access computations
+  // when the kExtensionsMenuAccessControl feature is enabled, even if they are
+  // added by the manager. Therefore, the context menu should not take into
+  // account user site settings when the feature is disabled.
+  auto* manager = extensions::PermissionsManager::Get(profile());
+
+  {
+    // Add site as a user permitted site.
+    extensions::PermissionsManagerWaiter manager_waiter(manager);
+    manager->AddUserPermittedSite(url::Origin::Create(url));
+    manager_waiter.WaitForUserPermissionsSettingsChange();
+
+    ExtensionContextMenuModel menu(extension, GetBrowser(),
+                                   ExtensionContextMenuModel::PINNED, nullptr,
+                                   true, ContextMenuSource::kToolbarAction);
+
+    if (GetParam()) {
+      // Verify "grant all extensions" item is visible and disabled, and the
+      // "learn more" and "permissions page" item are in the context menu.
+      EXPECT_EQ(GetCommandState(menu, kGrantAllExtensions),
+                CommandState::kDisabled);
+      EXPECT_EQ(GetCommandState(menu, kBlockAllExtensions),
+                CommandState::kAbsent);
+      EXPECT_EQ(GetCommandState(menu, kPageAccessSubmenu),
+                CommandState::kAbsent);
+      EXPECT_EQ(GetCommandState(menu, kLearnMore), CommandState::kEnabled);
+      EXPECT_EQ(GetPageAccessCommandState(menu, kLearnMore),
+                CommandState::kAbsent);
+      EXPECT_EQ(GetCommandState(menu, kPermissionsPage),
+                CommandState::kEnabled);
+      EXPECT_EQ(GetPageAccessCommandState(menu, kLearnMore),
+                CommandState::kAbsent);
+    } else {
+      // Even though we added a site as a user permitted site, the site
+      // permission behaves as "customize by extension". Verify page access
+      // submenu is visible and enabled, the "learn more" item is in in the
+      // submenu and the "permissions page" item is nowhere visible.
+      EXPECT_EQ(GetCommandState(menu, kGrantAllExtensions),
+                CommandState::kAbsent);
+      EXPECT_EQ(GetCommandState(menu, kBlockAllExtensions),
+                CommandState::kAbsent);
+      EXPECT_EQ(GetCommandState(menu, kPageAccessSubmenu),
+                CommandState::kEnabled);
+      EXPECT_EQ(GetCommandState(menu, kLearnMore), CommandState::kAbsent);
+      EXPECT_EQ(GetPageAccessCommandState(menu, kLearnMore),
+                CommandState::kEnabled);
+      EXPECT_EQ(GetCommandState(menu, kPermissionsPage), CommandState::kAbsent);
+      EXPECT_EQ(GetPageAccessCommandState(menu, kPermissionsPage),
+                CommandState::kAbsent);
+    }
+  }
+}
+
 }  // namespace extensions
diff --git a/chrome/browser/extensions/permissions_manager_browsertest.cc b/chrome/browser/extensions/permissions_manager_browsertest.cc
index 46e12cd..cb6303c 100644
--- a/chrome/browser/extensions/permissions_manager_browsertest.cc
+++ b/chrome/browser/extensions/permissions_manager_browsertest.cc
@@ -5,67 +5,84 @@
 #include "chrome/browser/extensions/extension_browsertest.h"
 #include "content/public/test/browser_test.h"
 #include "extensions/browser/permissions_manager.h"
-#include "extensions/browser/state_store.h"
+#include "extensions/common/extension_features.h"
+#include "testing/gtest/include/gtest/gtest.h"
 
 namespace extensions {
 
 using PermissionsManagerBrowserTest = ExtensionBrowserTest;
 
 IN_PROC_BROWSER_TEST_F(PermissionsManagerBrowserTest,
-                       PRE_UserPermissionsArePersisted) {
+                       PRE_RestrictedSitesArePersisted) {
   auto* manager = PermissionsManager::Get(profile());
 
   // Verify the restricted sites list is empty.
   EXPECT_EQ(manager->GetUserPermissionsSettings().restricted_sites,
             std::set<url::Origin>());
 
-  {
-    // Add a url to restricted sites. Verify the site is stored as a restricted
-    // site.
-    const url::Origin url =
-        url::Origin::Create(GURL("http://restricted.example.com"));
-    std::set<url::Origin> set_with_url;
-    set_with_url.insert(url);
-    manager->AddUserRestrictedSite(url);
-    EXPECT_EQ(manager->GetUserPermissionsSettings().restricted_sites,
-              set_with_url);
-  }
-
-  {
-    // Add a different url to permitted sites. Verify the site is stored as a
-    // permitted site.
-    const url::Origin url =
-        url::Origin::Create(GURL("http://permitted.example.com"));
-    std::set<url::Origin> set_with_url;
-    set_with_url.insert(url);
-    manager->AddUserPermittedSite(url);
-    EXPECT_EQ(manager->GetUserPermissionsSettings().permitted_sites,
-              set_with_url);
-  }
+  // Add a url to restricted sites. Verify the site is stored as a restricted
+  // site.
+  const url::Origin url =
+      url::Origin::Create(GURL("http://restricted.example.com"));
+  std::set<url::Origin> set_with_url;
+  set_with_url.insert(url);
+  manager->AddUserRestrictedSite(url);
+  EXPECT_EQ(manager->GetUserPermissionsSettings().restricted_sites,
+            set_with_url);
 }
 
 // Tests that user-level permissions are properly persisted across sessions.
 IN_PROC_BROWSER_TEST_F(PermissionsManagerBrowserTest,
-                       UserPermissionsArePersisted) {
+                       RestrictedSitesArePersisted) {
   auto* manager = PermissionsManager::Get(profile());
 
-  {
-    // Verify the restricted site stored in previous session is persisted.
-    std::set<url::Origin> set_with_url;
-    set_with_url.insert(
-        url::Origin::Create(GURL("http://restricted.example.com")));
-    EXPECT_EQ(manager->GetUserPermissionsSettings().restricted_sites,
-              set_with_url);
-  }
+  // Verify the restricted site stored in previous session is persisted.
+  std::set<url::Origin> set_with_url;
+  set_with_url.insert(
+      url::Origin::Create(GURL("http://restricted.example.com")));
+  EXPECT_EQ(manager->GetUserPermissionsSettings().restricted_sites,
+            set_with_url);
+}
 
-  {
-    // Verify the permitted site stored in previous session is persisted.
-    std::set<url::Origin> set_with_url;
-    set_with_url.insert(
-        url::Origin::Create(GURL("http://permitted.example.com")));
-    EXPECT_EQ(manager->GetUserPermissionsSettings().permitted_sites,
-              set_with_url);
+class PermissionsManagerWithPermittedSitesBrowserTest
+    : public ExtensionBrowserTest {
+ public:
+  PermissionsManagerWithPermittedSitesBrowserTest() {
+    scoped_feature_list_.InitAndEnableFeature(
+        extensions_features::kExtensionsMenuAccessControlWithPermittedSites);
   }
+  ~PermissionsManagerWithPermittedSitesBrowserTest() override = default;
+
+ private:
+  base::test::ScopedFeatureList scoped_feature_list_;
+};
+
+IN_PROC_BROWSER_TEST_F(PermissionsManagerWithPermittedSitesBrowserTest,
+                       PRE_PermittedSitesArePersisted) {
+  auto* manager = PermissionsManager::Get(profile());
+
+  // Add a url to permitted sites. Verify the site is stored as a permitted
+  // site.
+  const url::Origin url =
+      url::Origin::Create(GURL("http://permitted.example.com"));
+  std::set<url::Origin> set_with_url;
+  set_with_url.insert(url);
+  manager->AddUserPermittedSite(url);
+  EXPECT_EQ(manager->GetUserPermissionsSettings().permitted_sites,
+            set_with_url);
+}
+
+// Tests that user-level permissions are properly persisted across sessions.
+IN_PROC_BROWSER_TEST_F(PermissionsManagerWithPermittedSitesBrowserTest,
+                       PermittedSitesArePersisted) {
+  auto* manager = PermissionsManager::Get(profile());
+
+  // Verify the permitted site stored in previous session is persisted.
+  std::set<url::Origin> set_with_url;
+  set_with_url.insert(
+      url::Origin::Create(GURL("http://permitted.example.com")));
+  EXPECT_EQ(manager->GetUserPermissionsSettings().permitted_sites,
+            set_with_url);
 }
 
 }  // namespace extensions
diff --git a/chrome/browser/extensions/permissions_updater_unittest.cc b/chrome/browser/extensions/permissions_updater_unittest.cc
index 96304a6..e24e1c6 100644
--- a/chrome/browser/extensions/permissions_updater_unittest.cc
+++ b/chrome/browser/extensions/permissions_updater_unittest.cc
@@ -937,16 +937,14 @@
 }
 
 class PermissionsUpdaterTestWithEnhancedHostControls
-    : public PermissionsUpdaterTest,
-      public testing::WithParamInterface<bool> {
+    : public PermissionsUpdaterTest {
  public:
   PermissionsUpdaterTestWithEnhancedHostControls() {
-    const base::Feature& feature =
-        extensions_features::kExtensionsMenuAccessControl;
-    if (GetParam())
-      feature_list_.InitAndEnableFeature(feature);
-    else
-      feature_list_.InitAndDisableFeature(feature);
+    std::vector<base::test::FeatureRef> enabled_features = {
+        extensions_features::kExtensionsMenuAccessControl,
+        extensions_features::kExtensionsMenuAccessControlWithPermittedSites};
+    std::vector<base::test::FeatureRef> disabled_features = {};
+    feature_list_.InitWithFeatures(enabled_features, disabled_features);
   }
   ~PermissionsUpdaterTestWithEnhancedHostControls() override = default;
 
@@ -954,13 +952,9 @@
   base::test::ScopedFeatureList feature_list_;
 };
 
-INSTANTIATE_TEST_SUITE_P(All,
-                         PermissionsUpdaterTestWithEnhancedHostControls,
-                         testing::Bool());
-
 // Tests the behavior of revoking permissions from the extension while the
 // user has specified a set of sites that all extensions are allowed to run on.
-TEST_P(PermissionsUpdaterTestWithEnhancedHostControls,
+TEST_F(PermissionsUpdaterTestWithEnhancedHostControls,
        RevokingPermissionsWithUserPermittedSites) {
   InitializeEmptyExtensionService();
 
@@ -1032,20 +1026,6 @@
   ScriptingPermissionsModifier(profile(), extension)
       .SetWithholdHostPermissions(true);
 
-  // If the enhanced host controls feature is disabled, then both hosts are
-  // withheld.
-  if (!GetParam()) {
-    EXPECT_EQ(PermissionsData::PageAccess::kWithheld,
-              get_site_access(first_url));
-    EXPECT_EQ(PermissionsData::PageAccess::kWithheld,
-              get_site_access(second_url));
-    // There's nothing more we need to test in this case.
-    return;
-  }
-
-  // Otherwise, the feature is enabled, and user host settings are considered
-  // in the permissions adjustment.
-
   // The extension should be allowed to run on `first_url`, since the
   // user indicated all extensions can always run there. However, it should not
   // be allowed on `second_url`.
diff --git a/chrome/browser/extensions/site_permissions_helper_unittest.cc b/chrome/browser/extensions/site_permissions_helper_unittest.cc
index c196cbb..9d3909f 100644
--- a/chrome/browser/extensions/site_permissions_helper_unittest.cc
+++ b/chrome/browser/extensions/site_permissions_helper_unittest.cc
@@ -368,8 +368,11 @@
     : public SitePermissionsHelperUnitTest {
  public:
   SitePermissionsHelperWithUserHostControlsUnitTest() {
-    feature_list_.InitAndEnableFeature(
-        extensions_features::kExtensionsMenuAccessControl);
+    std::vector<base::test::FeatureRef> enabled_features = {
+        extensions_features::kExtensionsMenuAccessControl,
+        extensions_features::kExtensionsMenuAccessControlWithPermittedSites};
+    std::vector<base::test::FeatureRef> disabled_features;
+    feature_list_.InitWithFeatures(enabled_features, disabled_features);
   }
   ~SitePermissionsHelperWithUserHostControlsUnitTest() override = default;
 
diff --git a/chrome/browser/extensions/user_host_restrictions_browsertest.cc b/chrome/browser/extensions/user_host_restrictions_browsertest.cc
index 38ff0f4..8d6a4a5 100644
--- a/chrome/browser/extensions/user_host_restrictions_browsertest.cc
+++ b/chrome/browser/extensions/user_host_restrictions_browsertest.cc
@@ -21,6 +21,7 @@
 #include "extensions/test/result_catcher.h"
 #include "extensions/test/test_extension_dir.h"
 #include "net/dns/mock_host_resolver.h"
+#include "testing/gtest/include/gtest/gtest.h"
 
 namespace extensions {
 
@@ -32,12 +33,8 @@
       public testing::WithParamInterface<bool> {
  public:
   UserHostRestrictionsBrowserTest() {
-    const base::Feature& feature =
-        extensions_features::kExtensionsMenuAccessControl;
-    if (GetParam())
-      feature_list_.InitAndEnableFeature(feature);
-    else
-      feature_list_.InitAndDisableFeature(feature);
+    feature_list_.InitWithFeatureState(
+        extensions_features::kExtensionsMenuAccessControl, GetParam());
   }
   ~UserHostRestrictionsBrowserTest() override = default;
 
@@ -67,16 +64,6 @@
     waiter.WaitForExtensionPermissionsUpdate();
   }
 
-  // Adds `url` as a new user-permitted site and waits for the change to take
-  // effect.
-  void AddUserPermittedSite(const GURL& url) {
-    PermissionsManager* permissions_manager =
-        PermissionsManager::Get(profile());
-    PermissionsManagerWaiter waiter(permissions_manager);
-    permissions_manager->AddUserPermittedSite(url::Origin::Create(url));
-    waiter.WaitForUserPermissionsSettingsChange();
-  }
-
  private:
   base::test::ScopedFeatureList feature_list_;
 };
@@ -279,9 +266,44 @@
   }
 }
 
+class UserHostRestrictionsWithPermittedSitesBrowserTest
+    : public UserHostRestrictionsBrowserTest {
+ public:
+  UserHostRestrictionsWithPermittedSitesBrowserTest();
+  UserHostRestrictionsWithPermittedSitesBrowserTest(
+      const UserHostRestrictionsWithPermittedSitesBrowserTest&) = delete;
+  const UserHostRestrictionsWithPermittedSitesBrowserTest& operator=(
+      const UserHostRestrictionsWithPermittedSitesBrowserTest&) = delete;
+  ~UserHostRestrictionsWithPermittedSitesBrowserTest() override = default;
+
+  // Adds `url` as a new user-permitted site and waits for the change to take
+  // effect.
+  void AddUserPermittedSite(const GURL& url) {
+    PermissionsManager* permissions_manager =
+        PermissionsManager::Get(profile());
+    PermissionsManagerWaiter waiter(permissions_manager);
+    permissions_manager->AddUserPermittedSite(url::Origin::Create(url));
+    waiter.WaitForUserPermissionsSettingsChange();
+  }
+
+ private:
+  base::test::ScopedFeatureList feature_list_;
+};
+
+UserHostRestrictionsWithPermittedSitesBrowserTest::
+    UserHostRestrictionsWithPermittedSitesBrowserTest() {
+  feature_list_.InitAndEnableFeature(
+      extensions_features::kExtensionsMenuAccessControlWithPermittedSites);
+}
+
+INSTANTIATE_TEST_SUITE_P(All,
+                         UserHostRestrictionsWithPermittedSitesBrowserTest,
+                         testing::Bool());
+
 // Tests that extensions with withheld host permissions are automatically
 // allowed to run on sites the user allows all extensions to run on.
-IN_PROC_BROWSER_TEST_P(UserHostRestrictionsBrowserTest, UserPermittedSites) {
+IN_PROC_BROWSER_TEST_P(UserHostRestrictionsWithPermittedSitesBrowserTest,
+                       UserPermittedSites) {
   ASSERT_TRUE(StartEmbeddedTestServer());
 
   static constexpr char kManifest[] =
@@ -402,7 +424,7 @@
 }
 
 // Tests that user permitted sites are persisted and granted on extension load.
-IN_PROC_BROWSER_TEST_P(UserHostRestrictionsBrowserTest,
+IN_PROC_BROWSER_TEST_P(UserHostRestrictionsWithPermittedSitesBrowserTest,
                        PRE_UserPermittedSitesArePersisted) {
   // Note: We need a "real" extension here (instead of just a TestExtensionDir)
   // because it needs to persist for the next test.
@@ -433,7 +455,7 @@
                 allowed_url, extension_misc::kUnknownTabId, nullptr));
 }
 
-IN_PROC_BROWSER_TEST_P(UserHostRestrictionsBrowserTest,
+IN_PROC_BROWSER_TEST_P(UserHostRestrictionsWithPermittedSitesBrowserTest,
                        UserPermittedSitesArePersisted) {
   const Extension* found_extension = nullptr;
   for (const auto& extension :
@@ -446,9 +468,9 @@
   ASSERT_TRUE(found_extension);
 
   const GURL example_com("https://example.com");
-  // The user-permitted site should be allowed if and only if the feature is
-  // enabled (unlike the test above, our load-time granting *is* guarded behind
-  // the feature flag).
+  // The user-permitted site should be allowed iff the
+  // kExtensionsMenuAccessControl feature is enabled (unlike the test above, our
+  // load-time granting *is* guarded behind the feature flag).
   if (GetParam()) {
     EXPECT_EQ(PermissionsData::PageAccess::kAllowed,
               found_extension->permissions_data()->GetPageAccess(
@@ -462,7 +484,7 @@
 
 // Tests that sites the user indicated all extensions may run on are still
 // available to extensions after a permissions withholding change.
-IN_PROC_BROWSER_TEST_P(UserHostRestrictionsBrowserTest,
+IN_PROC_BROWSER_TEST_P(UserHostRestrictionsWithPermittedSitesBrowserTest,
                        UserPermittedSitesAreAppliedOnWithholdingChange) {
   ASSERT_TRUE(StartEmbeddedTestServer());
 
@@ -495,9 +517,9 @@
 
   WithholdExtensionPermissions(*extension);
 
-  // Once permissions are withheld, with the feature enabled, the extension may
-  // still run on the user-permitted site (without the feature enabled, the
-  // site is withheld).
+  // Once permissions are withheld, with the kExtensionsMenuAccessControl
+  // feature enabled, the extension may still run on the user-permitted site
+  // (without the feature enabled, the site is withheld).
   if (GetParam()) {
     EXPECT_EQ(PermissionsData::PageAccess::kAllowed,
               extension->permissions_data()->GetPageAccess(
@@ -515,7 +537,7 @@
           non_user_permitted_site, extension_misc::kUnknownTabId, nullptr));
 }
 
-IN_PROC_BROWSER_TEST_P(UserHostRestrictionsBrowserTest,
+IN_PROC_BROWSER_TEST_P(UserHostRestrictionsWithPermittedSitesBrowserTest,
                        UserPermittedSitesAndChromeFavicon) {
   ASSERT_TRUE(StartEmbeddedTestServer());
 
diff --git a/chrome/browser/fast_checkout/fast_checkout_client_impl.cc b/chrome/browser/fast_checkout/fast_checkout_client_impl.cc
index 93a4e57b..7eeb8cf 100644
--- a/chrome/browser/fast_checkout/fast_checkout_client_impl.cc
+++ b/chrome/browser/fast_checkout/fast_checkout_client_impl.cc
@@ -19,6 +19,7 @@
 #include "components/autofill/core/browser/data_model/autofill_profile.h"
 #include "components/autofill/core/browser/data_model/credit_card.h"
 #include "components/autofill/core/common/dense_set.h"
+#include "components/autofill/core/common/signatures.h"
 #include "content/public/browser/web_contents_user_data.h"
 #include "services/metrics/public/cpp/ukm_builders.h"
 #include "ui/base/l10n/l10n_util.h"
@@ -171,11 +172,34 @@
 
 void FastCheckoutClientImpl::OnRunComplete(FastCheckoutRunOutcome run_outcome,
                                            bool allow_further_runs) {
-  ukm::builders::Autofill_FastCheckoutRunOutcome builder(
+  ukm::builders::Autofill_FastCheckoutRunOutcome run_outcome_builder(
       GetWebContents().GetPrimaryMainFrame()->GetPageUkmSourceId());
-  builder.SetRunOutcome(static_cast<int64_t>(run_outcome));
-  builder.SetRunId(run_id_);
-  builder.Record(ukm::UkmRecorder::Get());
+  run_outcome_builder.SetRunOutcome(static_cast<int64_t>(run_outcome));
+  run_outcome_builder.SetRunId(run_id_);
+  run_outcome_builder.Record(ukm::UkmRecorder::Get());
+
+  if (autofill_manager_) {
+    for (auto [form_id, filling_state] : form_filling_states_) {
+      autofill::FormSignature form_signature = form_id.first;
+      autofill::DenseSet<autofill::FormType> form_types;
+      for (auto& [_, form] : autofill_manager_->form_structures()) {
+        if (form->form_signature() == form_signature) {
+          form_types = form->GetFormTypes();
+          break;
+        }
+      }
+      ukm::builders::Autofill_FastCheckoutFormStatus form_status_builder(
+          GetWebContents().GetPrimaryMainFrame()->GetPageUkmSourceId());
+      form_status_builder.SetFilled(filling_state == FillingState::kFilled);
+      form_status_builder.SetFormSignature(
+          autofill::HashFormSignature(form_signature));
+      form_status_builder.SetRunId(run_id_);
+      form_status_builder.SetFormTypes(
+          autofill::AutofillMetrics::FormTypesToBitVector(form_types));
+      form_status_builder.Record(ukm::UkmRecorder::Get());
+    }
+  }
+
   Stop(allow_further_runs);
 }
 
diff --git a/chrome/browser/fast_checkout/fast_checkout_client_impl_unittest.cc b/chrome/browser/fast_checkout/fast_checkout_client_impl_unittest.cc
index 86af02c..2d9f8af 100644
--- a/chrome/browser/fast_checkout/fast_checkout_client_impl_unittest.cc
+++ b/chrome/browser/fast_checkout/fast_checkout_client_impl_unittest.cc
@@ -46,6 +46,7 @@
 using ::testing::Return;
 using ::testing::SaveArg;
 using ::testing::UnorderedElementsAre;
+using ::ukm::builders::Autofill_FastCheckoutFormStatus;
 using ::ukm::builders::Autofill_FastCheckoutRunOutcome;
 
 namespace {
@@ -686,6 +687,7 @@
       {address_form->form_signature(), credit_card_form->form_signature()});
 
   EXPECT_TRUE(fast_checkout_client()->IsRunning());
+  int64_t run_id = fast_checkout_client()->run_id_;
 
   autofill::payments::FullCardRequest* full_card_request =
       autofill_client()->GetCvcAuthenticator()->GetFullCardRequest();
@@ -716,6 +718,32 @@
 
   EXPECT_FALSE(fast_checkout_client()->IsRunning());
   ExpectRunOutcomeUkm(FastCheckoutRunOutcome::kSuccess);
+  auto ukm_entries = ukm_recorder_.GetEntries(
+      Autofill_FastCheckoutFormStatus::kEntryName,
+      {Autofill_FastCheckoutFormStatus::kRunIdName,
+       Autofill_FastCheckoutFormStatus::kFilledName,
+       Autofill_FastCheckoutFormStatus::kFormSignatureName,
+       Autofill_FastCheckoutFormStatus::kFormTypesName});
+  EXPECT_EQ(ukm_entries.size(), 2UL);
+  base::flat_set<ukm::TestAutoSetUkmRecorder::HumanReadableUkmMetrics> metrics;
+  metrics.emplace(ukm_entries[0].metrics);
+  metrics.emplace(ukm_entries[1].metrics);
+  EXPECT_THAT(
+      metrics,
+      UnorderedElementsAre(
+          UnorderedElementsAre(
+              Pair(Autofill_FastCheckoutFormStatus::kRunIdName, run_id),
+              Pair(Autofill_FastCheckoutFormStatus::kFilledName, 1),
+              Pair(Autofill_FastCheckoutFormStatus::kFormSignatureName,
+                   autofill::HashFormSignature(address_form->form_signature())),
+              Pair(Autofill_FastCheckoutFormStatus::kFormTypesName, 3)),
+          UnorderedElementsAre(
+              Pair(Autofill_FastCheckoutFormStatus::kRunIdName, run_id),
+              Pair(Autofill_FastCheckoutFormStatus::kFilledName, 1),
+              Pair(Autofill_FastCheckoutFormStatus::kFormSignatureName,
+                   autofill::HashFormSignature(
+                       credit_card_form->form_signature())),
+              Pair(Autofill_FastCheckoutFormStatus::kFormTypesName, 5))));
 }
 
 TEST_F(FastCheckoutClientImplTest, OnAutofillManagerReset_ResetsState) {
diff --git a/chrome/browser/feed/android/java/res/values/styles.xml b/chrome/browser/feed/android/java/res/values/styles.xml
index f853ddd..0e626c60 100644
--- a/chrome/browser/feed/android/java/res/values/styles.xml
+++ b/chrome/browser/feed/android/java/res/values/styles.xml
@@ -40,6 +40,14 @@
         <item name="android:textColor">@color/tab_layout_text_color_list</item>
     </style>
 
+    <style name="TextAppearance.ClickableButton" parent="TextAppearance.Button.Text.Filled">
+        <item name="android:textColor">@macro/default_text_color_on_accent1</item>
+    </style>
+
+    <style name="TextAppearance.ClickableButtonInverse" parent="TextAppearance.Button.Text.Filled">
+        <item name="android:textColor">@macro/default_text_color_accent1</item>
+    </style>
+
     <style name="TextAppearance.HeaderTitle" parent="TextAppearance.TextAccentMediumThick">
         <item name="android:textColor">@color/header_title_text_color_list</item>
     </style>
diff --git a/chrome/browser/feed/android/java/src/org/chromium/chrome/browser/feed/webfeed/ShadowedClickableTextBubble.java b/chrome/browser/feed/android/java/src/org/chromium/chrome/browser/feed/webfeed/ShadowedClickableTextBubble.java
index cf2beba..67681c2 100644
--- a/chrome/browser/feed/android/java/src/org/chromium/chrome/browser/feed/webfeed/ShadowedClickableTextBubble.java
+++ b/chrome/browser/feed/android/java/src/org/chromium/chrome/browser/feed/webfeed/ShadowedClickableTextBubble.java
@@ -7,11 +7,13 @@
 import android.content.Context;
 import android.graphics.drawable.Drawable;
 import android.view.View;
+import android.widget.TextView;
 
 import androidx.annotation.DrawableRes;
 import androidx.annotation.StringRes;
 import androidx.core.content.res.ResourcesCompat;
 
+import org.chromium.base.ApiCompatibilityUtils;
 import org.chromium.chrome.browser.feed.R;
 import org.chromium.components.browser_ui.widget.textbubble.TextBubble;
 import org.chromium.ui.widget.LoadingView;
@@ -98,6 +100,16 @@
         return background;
     }
 
+    @Override
+    protected void updateTextStyle(TextView view, boolean isInverse) {
+        if (isInverse) {
+            ApiCompatibilityUtils.setTextAppearance(
+                    view, R.style.TextAppearance_ClickableButtonInverse);
+        } else {
+            ApiCompatibilityUtils.setTextAppearance(view, R.style.TextAppearance_ClickableButton);
+        }
+    }
+
     /**
      * Replaces image with loading spinner. Dismisses the entire button when loading spinner is
      * hidden.
diff --git a/chrome/browser/flag-metadata.json b/chrome/browser/flag-metadata.json
index 1cbbd58..e3032bd 100644
--- a/chrome/browser/flag-metadata.json
+++ b/chrome/browser/flag-metadata.json
@@ -820,7 +820,7 @@
   {
     "name": "calculate-native-win-occlusion",
     "owners": [ "davidbienvenu", "fdoray" ],
-    "expiry_milestone": 112
+    "expiry_milestone": 120
   },
   {
     "name": "calendar-jelly",
@@ -1834,11 +1834,6 @@
     "expiry_milestone": 120
   },
   {
-    "name": "enable-browsing-data-lifetime-manager",
-    "owners": ["ydago"],
-    "expiry_milestone": 106
-  },
-  {
     "name": "enable-cardboard",
     "owners": [ "alcooper", "chrome-xr-eng@google.com"],
     "expiry_milestone": 120
diff --git a/chrome/browser/flag_descriptions.cc b/chrome/browser/flag_descriptions.cc
index 4b7e6fb..3a7d803d 100644
--- a/chrome/browser/flag_descriptions.cc
+++ b/chrome/browser/flag_descriptions.cc
@@ -879,12 +879,6 @@
     "Enables UI on chrome://settings/siteData to remove all third-party "
     "cookies and site data.";
 
-const char kEnableBrowsingDataLifetimeManagerName[] =
-    "Enables the BrowsingDataLifetimeManager service to run.";
-const char kEnableBrowsingDataLifetimeManagerDescription[] =
-    "Enables the BrowsingDataLifetimeManager service to run and periodically "
-    "delete browsing data as specified by the BrowsingDataLifetime policy.";
-
 const char kDesktopPWAsAdditionalWindowingControlsName[] =
     "Desktop PWA Window Minimize/maximize/restore";
 const char kDesktopPWAsAdditionalWindowingControlsDescription[] =
diff --git a/chrome/browser/flag_descriptions.h b/chrome/browser/flag_descriptions.h
index f5c20dc6..72833a2 100644
--- a/chrome/browser/flag_descriptions.h
+++ b/chrome/browser/flag_descriptions.h
@@ -499,9 +499,6 @@
 extern const char kEnableRemovingAllThirdPartyCookiesName[];
 extern const char kEnableRemovingAllThirdPartyCookiesDescription[];
 
-extern const char kEnableBrowsingDataLifetimeManagerName[];
-extern const char kEnableBrowsingDataLifetimeManagerDescription[];
-
 extern const char kDesktopPWAsAdditionalWindowingControlsName[];
 extern const char kDesktopPWAsAdditionalWindowingControlsDescription[];
 
diff --git a/chrome/browser/media/cast_mirroring_performance_browsertest.cc b/chrome/browser/media/cast_mirroring_performance_browsertest.cc
index 6af3633..d300f47 100644
--- a/chrome/browser/media/cast_mirroring_performance_browsertest.cc
+++ b/chrome/browser/media/cast_mirroring_performance_browsertest.cc
@@ -893,7 +893,8 @@
         mirroring::mojom::SessionType::AUDIO_AND_VIDEO, endpoint.address(),
         "model_name", "friendly_name", "sender-123", "receiver-456",
         base::Milliseconds(kTargetPlayoutDelayMs),
-        false /* is_remote_playback */, false /** force_letterboxing */);
+        false /* is_remote_playback */, false /** force_letterboxing */,
+        false /* should_enable_rtcp_reporting */);
     host_->Start(std::move(session_params), std::move(observer_remote),
                  std::move(channel_remote),
                  channel_to_service_.BindNewPipeAndPassReceiver(), "Sink Name");
diff --git a/chrome/browser/media/encrypted_media_supported_types_browsertest.cc b/chrome/browser/media/encrypted_media_supported_types_browsertest.cc
index ca76cd25..45d902c 100644
--- a/chrome/browser/media/encrypted_media_supported_types_browsertest.cc
+++ b/chrome/browser/media/encrypted_media_supported_types_browsertest.cc
@@ -603,6 +603,7 @@
   EncryptedMediaSupportedTypesWidevineHwSecureForceClearLeadSupportTest() {
     enabled_features_.push_back({media::kHardwareSecureDecryption,
                                  {{"force_support_clear_lead", "true"}}});
+    EnableFeature(media::kHardwareSecureDecryptionExperiment);
   }
 
   void SetUpCommandLine(base::CommandLine* command_line) override {
diff --git a/chrome/browser/media/router/providers/cast/mirroring_activity.cc b/chrome/browser/media/router/providers/cast/mirroring_activity.cc
index 92b78ca..c21ce18 100644
--- a/chrome/browser/media/router/providers/cast/mirroring_activity.cc
+++ b/chrome/browser/media/router/providers/cast/mirroring_activity.cc
@@ -36,7 +36,6 @@
 #include "components/media_router/common/providers/cast/channel/cast_socket.h"
 #include "components/media_router/common/providers/cast/channel/enum_table.h"
 #include "components/media_router/common/route_request_result.h"
-#include "components/mirroring/mojom/session_parameters.mojom-forward.h"
 #include "components/mirroring/mojom/session_parameters.mojom.h"
 #include "content/public/browser/browser_context.h"
 #include "content/public/browser/browser_task_traits.h"
@@ -188,6 +187,13 @@
       base::TimeDelta(), incognito);
 }
 
+bool IsRtcpReportingEnabled(int frame_tree_node_id) {
+  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+  auto* debugger = MediaRouterDebugger::GetForFrameTreeNode(frame_tree_node_id);
+
+  return debugger ? debugger->IsRtcpReportsEnabled() : false;
+}
+
 }  // namespace
 
 MirroringActivity::MirroringActivity(
@@ -305,8 +311,8 @@
     // Record the error for access code discovery types.
     CastDiscoveryType discovery_type = cast_data_.discovery_type;
     if (discovery_type == CastDiscoveryType::kAccessCodeManualEntry) {
-      base::UmaHistogramEnumeration(
-          kHistogramStartFailureAccessCodeManualEntry, error);
+      base::UmaHistogramEnumeration(kHistogramStartFailureAccessCodeManualEntry,
+                                    error);
     } else if (discovery_type ==
                CastDiscoveryType::kAccessCodeRememberedDevice) {
       base::UmaHistogramEnumeration(
@@ -337,8 +343,7 @@
   if (discovery_type == CastDiscoveryType::kAccessCodeManualEntry) {
     base::UmaHistogramEnumeration(kHistogramStartSuccessAccessCodeManualEntry,
                                   *mirroring_type_);
-  } else if (discovery_type ==
-             CastDiscoveryType::kAccessCodeRememberedDevice) {
+  } else if (discovery_type == CastDiscoveryType::kAccessCodeRememberedDevice) {
     base::UmaHistogramEnumeration(
         kHistogramStartSuccessAccessCodeRememberedDevice, *mirroring_type_);
   }
@@ -542,10 +547,10 @@
   if (!has_audio && !has_video) {
     return;
   }
-  const SessionType session_type =
-      has_audio && has_video
-          ? SessionType::AUDIO_AND_VIDEO
-          : has_audio ? SessionType::AUDIO_ONLY : SessionType::VIDEO_ONLY;
+  const SessionType session_type = has_audio && has_video
+                                       ? SessionType::AUDIO_AND_VIDEO
+                                   : has_audio ? SessionType::AUDIO_ONLY
+                                               : SessionType::VIDEO_ONLY;
 
   will_start_mirroring_timestamp_ = base::Time::Now();
 
@@ -562,16 +567,41 @@
   content::GetUIThreadTaskRunner({})->PostTask(
       FROM_HERE,
       base::BindOnce(
-          &mirroring::MirroringServiceHost::Start, host_->GetWeakPtr(),
+          &MirroringActivity::StartOnUiThread, weak_ptr_factory_.GetWeakPtr(),
+          host_->GetWeakPtr(),
           SessionParameters::New(
               session_type, cast_data_.ip_endpoint.address(),
               cast_data_.model_name, sink_.sink().name(),
               session.destination_id(), message_handler_->source_id(),
               cast_source->target_playout_delay(),
               route().media_source().IsRemotePlaybackSource(),
-              ShouldForceLetterboxing(cast_data_.model_name)),
+              ShouldForceLetterboxing(cast_data_.model_name),
+              false /* enable_rtcp_reporting */),
           std::move(observer_remote), std::move(channel_remote),
-          std::move(channel_to_service_receiver_), route_.media_sink_name()));
+          std::move(channel_to_service_receiver_), route_.media_sink_name(),
+          frame_tree_node_id_));
+}
+
+void MirroringActivity::StartOnUiThread(
+    base::WeakPtr<mirroring::MirroringServiceHost> host,
+    mirroring::mojom::SessionParametersPtr session_params,
+    mojo::PendingRemote<mirroring::mojom::SessionObserver> observer,
+    mojo::PendingRemote<mirroring::mojom::CastMessageChannel> outbound_channel,
+    mojo::PendingReceiver<mirroring::mojom::CastMessageChannel> inbound_channel,
+    const std::string& sink_name,
+    int frame_tree_node_id) {
+  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+
+  if (!host) {
+    return;
+  }
+
+  session_params->enable_rtcp_reporting =
+      IsRtcpReportingEnabled(frame_tree_node_id);
+
+  host->Start(std::move(session_params), std::move(observer),
+              std::move(outbound_channel), std::move(inbound_channel),
+              sink_name);
 }
 
 void MirroringActivity::StopMirroring() {
diff --git a/chrome/browser/media/router/providers/cast/mirroring_activity.h b/chrome/browser/media/router/providers/cast/mirroring_activity.h
index 63946c4..3db8e930 100644
--- a/chrome/browser/media/router/providers/cast/mirroring_activity.h
+++ b/chrome/browser/media/router/providers/cast/mirroring_activity.h
@@ -20,6 +20,7 @@
 #include "components/media_router/common/providers/cast/channel/cast_message_handler.h"
 #include "components/mirroring/mojom/cast_message_channel.mojom.h"
 #include "components/mirroring/mojom/session_observer.mojom.h"
+#include "components/mirroring/mojom/session_parameters.mojom-forward.h"
 #include "mojo/public/cpp/bindings/pending_receiver.h"
 #include "mojo/public/cpp/bindings/pending_remote.h"
 #include "mojo/public/cpp/bindings/receiver.h"
@@ -98,6 +99,19 @@
   // Scrubs AES related data in messages with type "OFFER".
   static std::string GetScrubbedLogMessage(const base::Value::Dict& message);
 
+  // Starts the mirroring service via the Ui thread. Can only be called on the
+  // Ui thread.
+  void StartOnUiThread(
+      base::WeakPtr<mirroring::MirroringServiceHost> host,
+      mirroring::mojom::SessionParametersPtr session_params,
+      mojo::PendingRemote<mirroring::mojom::SessionObserver> observer,
+      mojo::PendingRemote<mirroring::mojom::CastMessageChannel>
+          outbound_channel,
+      mojo::PendingReceiver<mirroring::mojom::CastMessageChannel>
+          inbound_channel,
+      const std::string& sink_name,
+      int frame_tree_node_id);
+
   std::unique_ptr<mirroring::MirroringServiceHost> host_;
 
   // Sends Cast messages from the mirroring receiver to the mirroring service.
diff --git a/chrome/browser/media/router/providers/dial/dial_internal_message_util_unittest.cc b/chrome/browser/media/router/providers/dial/dial_internal_message_util_unittest.cc
index 5905699..0815d6e 100644
--- a/chrome/browser/media/router/providers/dial/dial_internal_message_util_unittest.cc
+++ b/chrome/browser/media/router/providers/dial/dial_internal_message_util_unittest.cc
@@ -32,11 +32,10 @@
 
   void ExpectMessagesEqual(const std::string& expected_message,
                            const std::string& message) {
-    auto expected_message_value =
-        base::JSONReader::ReadDeprecated(expected_message);
+    auto expected_message_value = base::JSONReader::Read(expected_message);
     ASSERT_TRUE(expected_message_value);
 
-    auto message_value = base::JSONReader::ReadDeprecated(message);
+    auto message_value = base::JSONReader::Read(message);
     ASSERT_TRUE(message_value);
 
     EXPECT_EQ(*expected_message_value, *message_value);
diff --git a/chrome/browser/memory/memory_ablation_study_unittest.cc b/chrome/browser/memory/memory_ablation_study_unittest.cc
index 059f903..e9d5da35 100644
--- a/chrome/browser/memory/memory_ablation_study_unittest.cc
+++ b/chrome/browser/memory/memory_ablation_study_unittest.cc
@@ -25,13 +25,7 @@
 };
 
 // Tests basic functionality of the MemoryAblationStudy class.
-#if BUILDFLAG(IS_WIN)
-// TODO(https://crbug.com/1245173) Flaky on Win7
-#define MAYBE_Basic DISABLED_Basic
-#else
-#define MAYBE_Basic Basic
-#endif
-TEST_F(MemoryAblationStudyTest, MAYBE_Basic) {
+TEST_F(MemoryAblationStudyTest, Basic) {
   // Ablate 137MB.
   base::FieldTrialParams params;
   params["ablation-size-mb"] = "137";
diff --git a/chrome/browser/policy/networking/device_network_configuration_updater_ash.cc b/chrome/browser/policy/networking/device_network_configuration_updater_ash.cc
index 4997bc29..d63a071 100644
--- a/chrome/browser/policy/networking/device_network_configuration_updater_ash.cc
+++ b/chrome/browser/policy/networking/device_network_configuration_updater_ash.cc
@@ -110,8 +110,8 @@
 }
 
 void DeviceNetworkConfigurationUpdaterAsh::ApplyNetworkPolicy(
-    base::Value::List network_configs_onc,
-    base::Value::Dict global_network_config) {
+    const base::Value::List& network_configs_onc,
+    const base::Value::Dict& global_network_config) {
   // Ensure this is runnng on the UI thead because we're accessing global data
   // to populate the substitutions.
   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
@@ -131,8 +131,8 @@
       /*userhash=*/std::string(), std::move(substitutions));
   network_config_handler_->SetPolicy(
       onc_source_, /*userhash=*/std::string(),
-      base::Value(std::move(network_configs_onc)),
-      base::Value(std::move(global_network_config)));
+      base::Value(network_configs_onc.Clone()),
+      base::Value(global_network_config.Clone()));
 }
 
 void DeviceNetworkConfigurationUpdaterAsh::OnDataRoamingSettingChanged() {
diff --git a/chrome/browser/policy/networking/device_network_configuration_updater_ash.h b/chrome/browser/policy/networking/device_network_configuration_updater_ash.h
index 542210b..5ec91ec 100644
--- a/chrome/browser/policy/networking/device_network_configuration_updater_ash.h
+++ b/chrome/browser/policy/networking/device_network_configuration_updater_ash.h
@@ -69,8 +69,9 @@
   // NetworkConfigurationUpdater:
   void Init() override;
   void ImportClientCertificates() override;
-  void ApplyNetworkPolicy(base::Value::List network_configs_onc,
-                          base::Value::Dict global_network_config) override;
+  void ApplyNetworkPolicy(
+      const base::Value::List& network_configs_onc,
+      const base::Value::Dict& global_network_config) override;
   void OnDataRoamingSettingChanged();
 
   // Pointer to the global singleton or a test instance.
diff --git a/chrome/browser/policy/networking/network_configuration_updater.cc b/chrome/browser/policy/networking/network_configuration_updater.cc
index dd287c6..7fe55125 100644
--- a/chrome/browser/policy/networking/network_configuration_updater.cc
+++ b/chrome/browser/policy/networking/network_configuration_updater.cc
@@ -214,8 +214,7 @@
 
   ImportCertificates(std::move(certificates));
   MarkFieldsAsRecommendedForBackwardsCompatibility(network_configs);
-  ApplyNetworkPolicy(std::move(network_configs),
-                     std::move(global_network_config));
+  ApplyNetworkPolicy(network_configs, global_network_config);
 }
 
 void NetworkConfigurationUpdater::
diff --git a/chrome/browser/policy/networking/network_configuration_updater.h b/chrome/browser/policy/networking/network_configuration_updater.h
index 9a529050..6abb79db 100644
--- a/chrome/browser/policy/networking/network_configuration_updater.h
+++ b/chrome/browser/policy/networking/network_configuration_updater.h
@@ -84,8 +84,9 @@
   // Parses the incoming policy, applies server and authority certificates.
   // Calls the specialized methods from subclasses to handle client certificates
   // and network configs.
-  virtual void ApplyNetworkPolicy(base::Value::List network_configs_onc,
-                                  base::Value::Dict global_network_config) = 0;
+  virtual void ApplyNetworkPolicy(
+      const base::Value::List& network_configs_onc,
+      const base::Value::Dict& global_network_config) = 0;
 
   // Parses the current value of the ONC policy. Clears |network_configs|,
   // |global_network_config| and |certificates| and fills them with the
diff --git a/chrome/browser/policy/networking/user_network_configuration_updater.h b/chrome/browser/policy/networking/user_network_configuration_updater.h
index d436119b..1483448f 100644
--- a/chrome/browser/policy/networking/user_network_configuration_updater.h
+++ b/chrome/browser/policy/networking/user_network_configuration_updater.h
@@ -29,8 +29,9 @@
 
   // NetworkConfigurationUpdater
   void ImportClientCertificates() override {}
-  void ApplyNetworkPolicy(base::Value::List network_configs_onc,
-                          base::Value::Dict global_network_config) override {}
+  void ApplyNetworkPolicy(
+      const base::Value::List& network_configs_onc,
+      const base::Value::Dict& global_network_config) override {}
 };
 
 }  // namespace policy
diff --git a/chrome/browser/policy/networking/user_network_configuration_updater_ash.cc b/chrome/browser/policy/networking/user_network_configuration_updater_ash.cc
index 360c23d0..39ccc4d 100644
--- a/chrome/browser/policy/networking/user_network_configuration_updater_ash.cc
+++ b/chrome/browser/policy/networking/user_network_configuration_updater_ash.cc
@@ -151,8 +151,8 @@
 }
 
 void UserNetworkConfigurationUpdaterAsh::ApplyNetworkPolicy(
-    base::Value::List network_configs_onc,
-    base::Value::Dict global_network_config) {
+    const base::Value::List& network_configs_onc,
+    const base::Value::Dict& global_network_config) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   DCHECK(user_);
 
@@ -166,8 +166,8 @@
 
   network_config_handler_->SetPolicy(
       onc_source_, user_->username_hash(),
-      base::Value(std::move(network_configs_onc)),
-      base::Value(std::move(global_network_config)));
+      base::Value(network_configs_onc.Clone()),
+      base::Value(global_network_config.Clone()));
 }
 
 void UserNetworkConfigurationUpdaterAsh::OnProfileInitializationComplete(
diff --git a/chrome/browser/policy/networking/user_network_configuration_updater_ash.h b/chrome/browser/policy/networking/user_network_configuration_updater_ash.h
index 7ce9165..e341bf2 100644
--- a/chrome/browser/policy/networking/user_network_configuration_updater_ash.h
+++ b/chrome/browser/policy/networking/user_network_configuration_updater_ash.h
@@ -94,8 +94,9 @@
   // NetworkConfigurationUpdater:
   void ImportClientCertificates() override;
 
-  void ApplyNetworkPolicy(base::Value::List network_configs_onc,
-                          base::Value::Dict global_network_config) override;
+  void ApplyNetworkPolicy(
+      const base::Value::List& network_configs_onc,
+      const base::Value::Dict& global_network_config) override;
 
   // ProfileObserver implementation
   void OnProfileInitializationComplete(Profile* profile) override;
diff --git a/chrome/browser/preloading/prefetch/no_state_prefetch/prerender_nostate_prefetch_browsertest.cc b/chrome/browser/preloading/prefetch/no_state_prefetch/prerender_nostate_prefetch_browsertest.cc
index 321bac79..ae84cc79 100644
--- a/chrome/browser/preloading/prefetch/no_state_prefetch/prerender_nostate_prefetch_browsertest.cc
+++ b/chrome/browser/preloading/prefetch/no_state_prefetch/prerender_nostate_prefetch_browsertest.cc
@@ -481,9 +481,8 @@
   void SetUp() override {
     bool split_cache_by_network_isolation_key = GetParam();
     if (split_cache_by_network_isolation_key) {
-      feature_list_.InitWithFeatures(
-          {net::features::kSplitCacheByNetworkIsolationKey},
-          {net::features::kForceIsolationInfoFrameOriginToTopLevelFrame});
+      feature_list_.InitAndEnableFeature(
+          net::features::kSplitCacheByNetworkIsolationKey);
     } else {
       feature_list_.InitAndDisableFeature(
           net::features::kSplitCacheByNetworkIsolationKey);
diff --git a/chrome/browser/profiles/profile_keyed_service_browsertest.cc b/chrome/browser/profiles/profile_keyed_service_browsertest.cc
index e281fcb..0c49c8a 100644
--- a/chrome/browser/profiles/profile_keyed_service_browsertest.cc
+++ b/chrome/browser/profiles/profile_keyed_service_browsertest.cc
@@ -18,6 +18,7 @@
 #include "components/keyed_service/core/keyed_service_base_factory.h"
 #include "components/supervised_user/core/common/buildflags.h"
 #include "content/public/test/browser_test.h"
+#include "pdf/buildflags.h"
 #include "printing/buildflags/buildflags.h"
 #include "third_party/blink/public/common/features.h"
 
@@ -236,6 +237,9 @@
     "MediaRouterUIService",
     "NotificationDisplayService",
     "OptimizationGuideKeyedService",
+#if BUILDFLAG(ENABLE_PDF)
+    "PdfViewerPrivateEventRouter",
+#endif  // BUILDFLAG(ENABLE_PDF)
     "PlatformNotificationService",
     "PrefWatcher",
     "PrivacySandboxSettings",
@@ -415,6 +419,9 @@
     "OptimizationGuideKeyedService",
     "PageContentAnnotationsService",
     "PasswordsPrivateEventRouter",
+#if BUILDFLAG(ENABLE_PDF)
+    "PdfViewerPrivateEventRouter",
+#endif  // BUILDFLAG(ENABLE_PDF)
     "PermissionHelper",
     "PermissionsManager",
     "PermissionsUpdaterShutdownFactory",
diff --git a/chrome/browser/resources/new_tab_page/preprocess_if_expr_sourcemaps.gni b/chrome/browser/resources/new_tab_page/preprocess_if_expr_sourcemaps.gni
deleted file mode 100644
index 114cd926..0000000
--- a/chrome/browser/resources/new_tab_page/preprocess_if_expr_sourcemaps.gni
+++ /dev/null
@@ -1,55 +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.
-
-import(
-    "//tools/code_coverage/js_source_maps/create_js_source_maps/create_js_source_maps.gni")
-import("//tools/grit/preprocess_if_expr.gni")
-
-template("preprocess_if_expr_sourcemaps") {
-  _out_folder = target_gen_dir
-  if (defined(invoker.out_folder)) {
-    _out_folder = invoker.out_folder
-  }
-
-  if (enable_webui_inline_sourcemaps) {
-    _preprocess_if_expr_target_name = "${target_name}__preprocess_if_expr"
-    _preprocess_if_expr_out_folder = "$_out_folder/preprocess_if_expr"
-  } else {
-    _preprocess_if_expr_target_name = target_name
-    _preprocess_if_expr_out_folder = _out_folder
-  }
-
-  preprocess_if_expr(_preprocess_if_expr_target_name) {
-    forward_variables_from(invoker,
-                           "*",
-                           [
-                             "out_folder",
-                             "enable_removal_comments",
-                           ])
-    out_folder = _preprocess_if_expr_out_folder
-    enable_removal_comments = enable_webui_inline_sourcemaps
-  }
-
-  if (enable_webui_inline_sourcemaps) {
-    _in_folder = "."
-    if (defined(invoker.in_folder)) {
-      _in_folder = invoker.in_folder
-    }
-
-    create_js_source_maps(target_name) {
-      inline_sourcemaps = true
-      originals = []
-      sources = []
-      outputs = []
-      foreach(in_file, invoker.in_files) {
-        assert(get_path_info(in_file, "extension") == "ts" ||
-               get_path_info(in_file, "extension") == "js")
-        originals += [ "$_in_folder/$in_file" ]
-        sources += [ "$_preprocess_if_expr_out_folder/" + in_file ]
-        outputs += [ "$_out_folder/" + in_file ]
-      }
-      deps = [ ":$_preprocess_if_expr_target_name" ]
-    }
-  }
-}
diff --git a/chrome/browser/resources/new_tab_page/ts_library_sourcemaps.gni b/chrome/browser/resources/new_tab_page/ts_library_sourcemaps.gni
deleted file mode 100644
index 483689c8..0000000
--- a/chrome/browser/resources/new_tab_page/ts_library_sourcemaps.gni
+++ /dev/null
@@ -1,51 +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.
-
-import(
-    "//tools/code_coverage/js_source_maps/merge_js_source_maps/merge_js_source_maps.gni")
-import("//tools/typescript/ts_library.gni")
-
-template("ts_library_sourcemaps") {
-  _out_dir = target_gen_dir
-  if (defined(invoker.out_dir)) {
-    _out_dir = invoker.out_dir
-  }
-
-  if (enable_webui_inline_sourcemaps) {
-    _ts_library_target_name = "${target_name}__ts_library"
-    _ts_library_out_dir = "$_out_dir/ts_library"
-  } else {
-    _ts_library_target_name = target_name
-    _ts_library_out_dir = _out_dir
-  }
-
-  ts_library(_ts_library_target_name) {
-    forward_variables_from(invoker, "*", [ "out_dir" ])
-    out_dir = _ts_library_out_dir
-  }
-
-  if (enable_webui_inline_sourcemaps) {
-    merge_js_source_maps(target_name) {
-      deps = [ ":$_ts_library_target_name" ]
-      manifest_files = []
-      sources = []
-      outputs = []
-      out_dir = _out_dir
-      foreach(_output, get_target_outputs(":$_ts_library_target_name")) {
-        if (get_path_info(_output, "extension") == "manifest") {
-          manifest_files += [ _output ]
-        } else if (get_path_info(_output, "extension") == "ts" ||
-                   get_path_info(_output, "extension") == "js") {
-          sources += [ _output ]
-          outputs += [ string_replace(_output, _ts_library_out_dir, _out_dir) ]
-        } else {
-          _ts_config = "$target_gen_dir/tsconfig_$_ts_library_target_name.json"
-          assert(_output == _ts_config)
-          sources += [ _ts_config ]
-          outputs += [ "$target_gen_dir/tsconfig_$target_name.json" ]
-        }
-      }
-    }
-  }
-}
diff --git a/chrome/browser/resources/pdf/elements/viewer-toolbar.ts b/chrome/browser/resources/pdf/elements/viewer-toolbar.ts
index 684b56a..4a3c978 100644
--- a/chrome/browser/resources/pdf/elements/viewer-toolbar.ts
+++ b/chrome/browser/resources/pdf/elements/viewer-toolbar.ts
@@ -24,7 +24,7 @@
 import {FittingType} from '../constants.js';
 import {record, UserAction} from '../metrics.js';
 // <if expr="enable_screen_ai_service">
-import {PdfViewerPrivateProxyImpl} from '../pdf_viewer_private_proxy.js';
+import {PdfOcrPrefCallback, PdfViewerPrivateProxyImpl} from '../pdf_viewer_private_proxy.js';
 
 // </if>
 
@@ -165,11 +165,22 @@
   // <if expr="enable_screen_ai_service">
   pdfOcrEnabled: boolean;
   private pdfOcrAlwaysActive_: boolean;
+  private pdfOcrPrefChanged_: PdfOcrPrefCallback = null;
 
   override async connectedCallback() {
     super.connectedCallback();
     this.pdfOcrAlwaysActive_ =
         await PdfViewerPrivateProxyImpl.getInstance().isPdfOcrAlwaysActive();
+    this.pdfOcrPrefChanged_ = this.onPdfOcrPrefChanged.bind(this);
+    PdfViewerPrivateProxyImpl.getInstance().addPdfOcrPrefChangedListener(
+        this.pdfOcrPrefChanged_);
+  }
+
+  override disconnectedCallback() {
+    super.disconnectedCallback();
+    PdfViewerPrivateProxyImpl.getInstance().removePdfOcrPrefChangedListener(
+        this.pdfOcrPrefChanged_);
+    this.pdfOcrPrefChanged_ = null;
   }
   // </if>
 
@@ -394,6 +405,10 @@
       // TODO(crbug.com/1393069): Start/stop PDF OCR accordingly.
     }
   }
+
+  private onPdfOcrPrefChanged(isPdfOcrAlwaysActive: boolean) {
+    this.pdfOcrAlwaysActive_ = isPdfOcrAlwaysActive;
+  }
   // </if>
 }
 
diff --git a/chrome/browser/resources/pdf/pdf_viewer_private_proxy.ts b/chrome/browser/resources/pdf/pdf_viewer_private_proxy.ts
index 19f7d07..d1f929f 100644
--- a/chrome/browser/resources/pdf/pdf_viewer_private_proxy.ts
+++ b/chrome/browser/resources/pdf/pdf_viewer_private_proxy.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.
 
+export type PdfOcrPrefCallback = chrome.pdfViewerPrivate.PdfOcrPrefCallback;
+
 // TODO(crbug.com/1302465): Move the other chrome.pdfViewerPrivate calls across
 // the PDF UI under this proxy.
 // `chrome.pdfViewerPrivate.isAllowedLocalFileAccess` is currently located in
@@ -9,6 +11,8 @@
 interface PdfViewerPrivateProxy {
   isPdfOcrAlwaysActive(): Promise<boolean>;
   setPdfOcrPref(value: boolean): Promise<boolean>;
+  addPdfOcrPrefChangedListener(listener: PdfOcrPrefCallback): void;
+  removePdfOcrPrefChangedListener(listener: PdfOcrPrefCallback): void;
 }
 
 export class PdfViewerPrivateProxyImpl implements PdfViewerPrivateProxy {
@@ -24,6 +28,14 @@
     });
   }
 
+  addPdfOcrPrefChangedListener(listener: PdfOcrPrefCallback): void {
+    chrome.pdfViewerPrivate.onPdfOcrPrefChanged.addListener(listener);
+  }
+
+  removePdfOcrPrefChangedListener(listener: PdfOcrPrefCallback): void {
+    chrome.pdfViewerPrivate.onPdfOcrPrefChanged.removeListener(listener);
+  }
+
   static getInstance(): PdfViewerPrivateProxy {
     return instance || (instance = new PdfViewerPrivateProxyImpl());
   }
diff --git a/chrome/browser/resources/settings/autofill_page/passwords_device_section.ts b/chrome/browser/resources/settings/autofill_page/passwords_device_section.ts
index c9e340c..1b23096 100644
--- a/chrome/browser/resources/settings/autofill_page/passwords_device_section.ts
+++ b/chrome/browser/resources/settings/autofill_page/passwords_device_section.ts
@@ -26,7 +26,7 @@
 import {WebUiListenerMixin} from 'chrome://resources/cr_elements/web_ui_listener_mixin.js';
 import {OpenWindowProxyImpl} from 'chrome://resources/js/open_window_proxy.js';
 import {PluralStringProxyImpl} from 'chrome://resources/js/plural_string_proxy.js';
-import {getDeepActiveElement} from 'chrome://resources/js/util_ts.js';
+import {getDeepActiveElement, isUndoKeyboardEvent} from 'chrome://resources/js/util_ts.js';
 import {IronListElement} from 'chrome://resources/polymer/v3_0/iron-list/iron-list.js';
 import {PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
@@ -241,17 +241,10 @@
   override ready() {
     super.ready();
 
-    document.addEventListener('keydown', keyboardEvent => {
-      // <if expr="is_macosx">
-      if (keyboardEvent.metaKey && keyboardEvent.key === 'z') {
+    document.addEventListener('keydown', (keyboardEvent: KeyboardEvent) => {
+      if (isUndoKeyboardEvent(keyboardEvent)) {
         this.onUndoKeyBinding_(keyboardEvent);
       }
-      // </if>
-      // <if expr="not is_macosx">
-      if (keyboardEvent.ctrlKey && keyboardEvent.key === 'z') {
-        this.onUndoKeyBinding_(keyboardEvent);
-      }
-      // </if>
     });
   }
 
diff --git a/chrome/browser/resources/settings/autofill_page/passwords_section.ts b/chrome/browser/resources/settings/autofill_page/passwords_section.ts
index 47fed02..796d4b3 100644
--- a/chrome/browser/resources/settings/autofill_page/passwords_section.ts
+++ b/chrome/browser/resources/settings/autofill_page/passwords_section.ts
@@ -41,7 +41,7 @@
 import {assert, assertNotReached} from 'chrome://resources/js/assert_ts.js';
 import {focusWithoutInk} from 'chrome://resources/js/focus_without_ink.js';
 import {OpenWindowProxyImpl} from 'chrome://resources/js/open_window_proxy.js';
-import {getDeepActiveElement} from 'chrome://resources/js/util_ts.js';
+import {getDeepActiveElement, isUndoKeyboardEvent} from 'chrome://resources/js/util_ts.js';
 import {DomRepeat, DomRepeatEvent, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
 import {SettingsToggleButtonElement} from '../controls/settings_toggle_button.js';
@@ -300,17 +300,10 @@
   override ready() {
     super.ready();
 
-    document.addEventListener('keydown', e => {
-      // <if expr="is_macosx">
-      if (e.metaKey && e.key === 'z') {
+    document.addEventListener('keydown', (e: KeyboardEvent) => {
+      if (isUndoKeyboardEvent(e)) {
         this.onUndoKeyBinding_(e);
       }
-      // </if>
-      // <if expr="not is_macosx">
-      if (e.ctrlKey && e.key === 'z') {
-        this.onUndoKeyBinding_(e);
-      }
-      // </if>
     });
 
     // <if expr="is_win or is_macosx">
diff --git a/chrome/browser/resources/settings/chromeos/device_page/audio.html b/chrome/browser/resources/settings/chromeos/device_page/audio.html
index a6a64674..bc640bc 100644
--- a/chrome/browser/resources/settings/chromeos/device_page/audio.html
+++ b/chrome/browser/resources/settings/chromeos/device_page/audio.html
@@ -107,9 +107,14 @@
               on-click="onOutputMuteButtonClicked"
               disabled="[[isOutputMutedByPolicy_(
                   audioSystemProperties_.outputMuteState
-                )]]">
+                )]]"
+              aria-description="[[getOutputMuteButtonAriaLabel(
+                  isOutputMuted_
+                )]]"
+              aria-labelledby="audioOutputVolumeLabel"
+              aria-pressed="[[isOutputMuted_]]">
           </cr-icon-button>
-          <paper-tooltip id="audioOutputMuteButtonTooltip"
+          <paper-tooltip id="audioOutputMuteButtonTooltip" aria-hidden="true"
               for="audioOutputMuteButton">
             [[getMuteTooltip_(audioSystemProperties_.outputMuteState)]]
           </paper-tooltip>
@@ -159,9 +164,15 @@
               iron-icon="[[getInputIcon_(isInputMuted_)]]"
               on-click="onInputMuteClicked"
               class="audio-mute-button"
-              disabled="[[shouldDisableInputGainControls(isInputMuted_)]]">
+              disabled="[[shouldDisableInputGainControls(isInputMuted_)]]"
+              aria-description$="[[getInputMuteButtonAriaLabel(
+                  audioSystemProperties_.inputMuteState,
+                  isInputMuted_
+                )]]"
+              aria-labelledby="audioInputGainLabel"
+              aria-pressed="[[isInputMuted_]]">
           </cr-icon-button>
-          <paper-tooltip id="audioInputMuteButtonTooltip"
+          <paper-tooltip id="audioInputMuteButtonTooltip" aria-hidden="true"
               for="audioInputGainMuteButton">
             [[getMuteTooltip_(audioSystemProperties_.inputMuteState)]]
           </paper-tooltip>
diff --git a/chrome/browser/resources/settings/chromeos/device_page/audio.ts b/chrome/browser/resources/settings/chromeos/device_page/audio.ts
index ccb43f9..414c423 100644
--- a/chrome/browser/resources/settings/chromeos/device_page/audio.ts
+++ b/chrome/browser/resources/settings/chromeos/device_page/audio.ts
@@ -339,6 +339,25 @@
         return '';
     }
   }
+
+  /** Returns the appropriate aria-label for input mute button. */
+  protected getInputMuteButtonAriaLabel(): string {
+    if (this.audioSystemProperties_.inputMuteState ===
+        MuteState.kMutedExternally) {
+      return this.i18n('audioInputMuteButtonAriaLabelMutedByHardwareSwitch');
+    }
+
+    return this.isInputMuted_ ?
+        this.i18n('audioInputMuteButtonAriaLabelMuted') :
+        this.i18n('audioInputMuteButtonAriaLabelNotMuted');
+  }
+
+  /** Returns the appropriate aria-label for output mute button. */
+  protected getOutputMuteButtonAriaLabel(): string {
+    return this.isOutputMuted_ ?
+        this.i18n('audioOutputMuteButtonAriaLabelMuted') :
+        this.i18n('audioOutputMuteButtonAriaLabelNotMuted');
+  }
 }
 
 declare global {
diff --git a/chrome/browser/resources/settings/site_settings/review_notification_permissions.ts b/chrome/browser/resources/settings/site_settings/review_notification_permissions.ts
index 295c510..c80ce89 100644
--- a/chrome/browser/resources/settings/site_settings/review_notification_permissions.ts
+++ b/chrome/browser/resources/settings/site_settings/review_notification_permissions.ts
@@ -16,6 +16,7 @@
 import {assert, assertNotReached} from 'chrome://resources/js/assert_ts.js';
 import {EventTracker} from 'chrome://resources/js/event_tracker.js';
 import {PluralStringProxyImpl} from 'chrome://resources/js/plural_string_proxy.js';
+import {isUndoKeyboardEvent} from 'chrome://resources/js/util_ts.js';
 import {DomRepeatEvent, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
 import {BaseMixin} from '../base_mixin.js';
@@ -305,10 +306,10 @@
 
   private onUndoButtonClick_(e: Event) {
     e.stopPropagation();
-    this.undoLastAction();
+    this.undoLastAction_();
   }
 
-  private undoLastAction() {
+  private undoLastAction_() {
     switch (this.lastUserAction_) {
       // As BLOCK and RESET actions just change the notification permission,
       // undoing them only requires allowing notification permissions again.
@@ -350,28 +351,8 @@
       return;
     }
 
-    /**
-     * TODO(crbug.com/1392664): Unify the implementation of ctrl+z that are in
-     * the current codebase.
-     *
-     * Undo should be done when ctrl+z (or meta+z on macOS) is pressed. No other
-     * modifier should be pressed simultaneously (alt, shift, meta on non-mac
-     * and ctrl on mac).
-     */
-    if (e.key !== 'z') {
-      return;
-    }
-    const excludedModifiers = [e.altKey, e.shiftKey];
-    // <if expr="is_macosx">
-    let targetModifier = e.metaKey;
-    excludedModifiers.push(e.ctrlKey);
-    // </if>
-    // <if expr="not is_macosx">
-    let targetModifier = e.ctrlKey;
-    excludedModifiers.push(e.metaKey);
-    // </if>
-    if (!excludedModifiers.some(Boolean) && targetModifier) {
-      this.undoLastAction();
+    if (isUndoKeyboardEvent(e)) {
+      this.undoLastAction_();
     }
   }
 
diff --git a/chrome/browser/safe_xml_parser_browsertest.cc b/chrome/browser/safe_xml_parser_browsertest.cc
index 2a98f0b3..d6a7c58 100644
--- a/chrome/browser/safe_xml_parser_browsertest.cc
+++ b/chrome/browser/safe_xml_parser_browsertest.cc
@@ -42,9 +42,9 @@
     SCOPED_TRACE(xml);
 
     base::RunLoop run_loop;
-    std::unique_ptr<base::Value> expected_value;
+    absl::optional<base::Value> expected_value;
     if (!expected_json.empty()) {
-      expected_value = base::JSONReader::ReadDeprecated(expected_json);
+      expected_value = base::JSONReader::Read(expected_json);
       DCHECK(expected_value) << "Bad test, incorrect JSON: " << expected_json;
     }
 
@@ -59,7 +59,7 @@
 
  private:
   void XmlParsingDone(base::OnceClosure quit_loop_closure,
-                      std::unique_ptr<base::Value> expected_value,
+                      absl::optional<base::Value> expected_value,
                       data_decoder::DataDecoder::ValueOrError result) {
     base::ScopedClosureRunner runner(std::move(quit_loop_closure));
     if (!expected_value) {
diff --git a/chrome/browser/ssl/https_only_mode_tab_helper.cc b/chrome/browser/ssl/https_only_mode_tab_helper.cc
index c8b81f2..817eabca 100644
--- a/chrome/browser/ssl/https_only_mode_tab_helper.cc
+++ b/chrome/browser/ssl/https_only_mode_tab_helper.cc
@@ -4,29 +4,26 @@
 
 #include "chrome/browser/ssl/https_only_mode_tab_helper.h"
 
-#include "components/security_interstitials/content/https_only_mode_blocking_page.h"
-#include "components/security_interstitials/content/security_interstitial_tab_helper.h"
+#include "chrome/common/chrome_features.h"
 #include "content/public/browser/navigation_handle.h"
 
 HttpsOnlyModeTabHelper::~HttpsOnlyModeTabHelper() = default;
 
-void HttpsOnlyModeTabHelper::ReadyToCommitNavigation(
+void HttpsOnlyModeTabHelper::DidStartNavigation(
     content::NavigationHandle* navigation_handle) {
-  if (is_timer_interstitial()) {
-    set_is_timer_interstitial(false);
-    std::unique_ptr<security_interstitials::HttpsOnlyModeBlockingPage>
-        blocking_page = factory_->CreateHttpsOnlyModeBlockingPage(
-            navigation_handle->GetWebContents(), fallback_url());
-    security_interstitials::SecurityInterstitialTabHelper::
-        AssociateBlockingPage(navigation_handle, std::move(blocking_page));
+  // The original HTTPS-First Mode implementation expects these to stay set
+  // across navigations and handles clearing them separately. Only reset if
+  // the HFMv2 implementation is being used to avoid interfering with HFMv1.
+  if (base::FeatureList::IsEnabled(features::kHttpsFirstModeV2)) {
+    set_fallback_url(GURL());
+    set_is_navigation_fallback(false);
+    set_is_navigation_upgraded(false);
   }
 }
 
 HttpsOnlyModeTabHelper::HttpsOnlyModeTabHelper(
     content::WebContents* web_contents)
     : WebContentsObserver(web_contents),
-      content::WebContentsUserData<HttpsOnlyModeTabHelper>(*web_contents) {
-  factory_ = std::make_unique<ChromeSecurityBlockingPageFactory>();
-}
+      content::WebContentsUserData<HttpsOnlyModeTabHelper>(*web_contents) {}
 
 WEB_CONTENTS_USER_DATA_KEY_IMPL(HttpsOnlyModeTabHelper);
diff --git a/chrome/browser/ssl/https_only_mode_tab_helper.h b/chrome/browser/ssl/https_only_mode_tab_helper.h
index 35166381a..fc3f8813 100644
--- a/chrome/browser/ssl/https_only_mode_tab_helper.h
+++ b/chrome/browser/ssl/https_only_mode_tab_helper.h
@@ -5,7 +5,6 @@
 #ifndef CHROME_BROWSER_SSL_HTTPS_ONLY_MODE_TAB_HELPER_H_
 #define CHROME_BROWSER_SSL_HTTPS_ONLY_MODE_TAB_HELPER_H_
 
-#include "chrome/browser/ssl/chrome_security_blocking_page_factory.h"
 #include "content/public/browser/web_contents_observer.h"
 #include "content/public/browser/web_contents_user_data.h"
 
@@ -15,8 +14,7 @@
 }  // namespace content
 
 // A short-lived, per-tab helper for tracking HTTPS-Only Mode data about the
-// navigation and for creating the blocking page for the early-timeout code
-// path.
+// navigation.
 class HttpsOnlyModeTabHelper
     : public content::WebContentsObserver,
       public content::WebContentsUserData<HttpsOnlyModeTabHelper> {
@@ -26,7 +24,7 @@
   ~HttpsOnlyModeTabHelper() override;
 
   // content::WebContentsObserver:
-  void ReadyToCommitNavigation(
+  void DidStartNavigation(
       content::NavigationHandle* navigation_handle) override;
 
   // HTTPS-Only Mode metadata getters and setters:
@@ -40,22 +38,20 @@
   }
   bool is_navigation_fallback() const { return is_navigation_fallback_; }
 
-  void set_is_timer_interstitial(bool fallback) {
-    is_timer_interstitial_ = fallback;
-  }
-  bool is_timer_interstitial() const { return is_timer_interstitial_; }
-
   void set_fallback_url(const GURL& fallback_url) {
     fallback_url_ = fallback_url;
   }
   GURL fallback_url() const { return fallback_url_; }
 
+  bool has_failed_upgrade(const GURL& url) {
+    return failed_upgrade_urls_.contains(url);
+  }
+  void add_failed_upgrade(const GURL& url) { failed_upgrade_urls_.insert(url); }
+
  private:
   explicit HttpsOnlyModeTabHelper(content::WebContents* web_contents);
   friend class content::WebContentsUserData<HttpsOnlyModeTabHelper>;
 
-  std::unique_ptr<ChromeSecurityBlockingPageFactory> factory_;
-
   // TODO(crbug.com/1218526): Track upgrade status per-navigation rather than
   // per-WebContents, in case multiple navigations occur in the WebContents and
   // the metadata is not cleared. This may be tricky however as the Interceptor
@@ -67,13 +63,19 @@
   // Set to true if the current navigation is a fallback to HTTP.
   bool is_navigation_fallback_ = false;
 
-  // Set to true if an interstitial triggered due to an HTTPS timeout is about
-  // to be shown.
-  bool is_timer_interstitial_ = false;
-
   // HTTP URL that the current navigation should fall back to on failure.
   GURL fallback_url_;
 
+  // Holds the set of URLs that have failed to be upgraded to HTTPS in this
+  // WebContents. This is used to immediately show the HTTP interstitial without
+  // re-trying to upgrade the navigation -- currently this is only applied to
+  // back/forward navigations as they interact badly with interceptors, and this
+  // acts as the browser "remembering" the navigation state.
+  //
+  // In the case of HTTPS Upgrades, without HTTPS-First Mode enabled, these
+  // hostnames will also be on the HTTP allowlist, bypassing upgrade attempts.
+  std::set<GURL> failed_upgrade_urls_;
+
   WEB_CONTENTS_USER_DATA_KEY_DECL();
 };
 
diff --git a/chrome/browser/ssl/https_upgrades_browsertest.cc b/chrome/browser/ssl/https_upgrades_browsertest.cc
index e805920..a82bba8 100644
--- a/chrome/browser/ssl/https_upgrades_browsertest.cc
+++ b/chrome/browser/ssl/https_upgrades_browsertest.cc
@@ -1036,10 +1036,8 @@
 // Regression test for crbug.com/1272781. Previously, performing back/forward
 // navigations around the HTTPS-First Mode interstitial could cause history
 // entries to dropped.
-// TODO(crbug.com/1272781): Broken when BFCache is disabled (like on the
-// linux-bfcache-rel bots).
-IN_PROC_BROWSER_TEST_F(HttpsUpgradesBrowserTest,
-                       DISABLED_InterstitialFallbackMaintainsHistory) {
+IN_PROC_BROWSER_TEST_P(HttpsUpgradesBrowserTest,
+                       InterstitialFallbackMaintainsHistory) {
   // This test only applies to HTTPS-First Mode.
   if (!IsHttpInterstitialEnabled()) {
     return;
@@ -1088,18 +1086,16 @@
   // upgraded to HTTPS and fail, triggering the HTTPS-First Mode
   // interstitial.
   content::NavigateToURLBlockUntilNavigationsComplete(contents,
-                                                      downgrading_http_url, 2);
+                                                      downgrading_http_url, 1);
   EXPECT_EQ(downgrading_http_url, contents->GetLastCommittedURL());
   EXPECT_TRUE(chrome_browser_interstitials::IsShowingHttpsFirstModeInterstitial(
       contents));
 
   // Simulate clicking the browser "back" button.
-  // TODO(crbug.com/1394910): The incorrect WARNING security state is retained
-  // from the interstitial page.
   EXPECT_TRUE(content::HistoryGoBack(contents));
   EXPECT_EQ(good_https_url, contents->GetLastCommittedURL());
   auto* helper = SecurityStateTabHelper::FromWebContents(contents);
-  EXPECT_EQ(security_state::WARNING, helper->GetSecurityLevel());
+  EXPECT_EQ(security_state::SECURE, helper->GetSecurityLevel());
 
   // Simulate clicking the browser "forward" button. The HistoryGoForward()
   // call returns `false` because it is an error page.
diff --git a/chrome/browser/ssl/https_upgrades_interceptor.cc b/chrome/browser/ssl/https_upgrades_interceptor.cc
index 75bf497..1a21c97 100644
--- a/chrome/browser/ssl/https_upgrades_interceptor.cc
+++ b/chrome/browser/ssl/https_upgrades_interceptor.cc
@@ -15,6 +15,7 @@
 #include "components/prefs/pref_service.h"
 #include "components/security_interstitials/content/stateful_ssl_host_state_delegate.h"
 #include "components/security_interstitials/core/https_only_mode_metrics.h"
+#include "content/public/browser/navigation_entry.h"
 #include "content/public/browser/storage_partition.h"
 #include "content/public/browser/url_loader_request_interceptor.h"
 #include "content/public/browser/web_contents.h"
@@ -34,6 +35,7 @@
 #include "services/network/public/mojom/network_context.mojom.h"
 #include "services/network/public/mojom/url_loader.mojom.h"
 #include "services/network/public/mojom/url_response_head.mojom.h"
+#include "ui/base/page_transition_types.h"
 #include "url/gurl.h"
 #include "url/url_constants.h"
 
@@ -85,8 +87,7 @@
   if (resource_request.is_outermost_main_frame &&
       resource_request.method == "GET" &&
       !net::IsLocalhost(resource_request.url) &&
-      resource_request.url.SchemeIs(url::kHttpScheme) &&
-      !tab_helper->is_navigation_fallback()) {
+      resource_request.url.SchemeIs(url::kHttpScheme)) {
     return true;
   }
   return false;
@@ -256,6 +257,30 @@
     return;
   }
 
+  // If this is a back/forward navigation to a failed upgrade, then don't
+  // intercept to upgrade the navigation. Other forms of re-visiting a URL
+  // that previously failed to be upgraded to HTTPS *should* be intercepted so
+  // the upgrade can be attempted again (e.g., the user reloading the tab, the
+  // user navigating around and ending back on this URL in the same tab, etc.).
+  //
+  // This effectively "caches" the HTTPS-First Mode interstitial for the
+  // history entry of a failed upgrade for the lifetime of the tab. This means
+  // that it is possible for a user to come back much later (say, a week later),
+  // after a site has fixed its HTTPS configuration, and still see the
+  // interstitial for that URL.
+  //
+  // Without this check, resetting the HTTPS-Upgrades flags in
+  // HttpsOnlyModeTabHelper::DidStartNavigation() means the Interceptor would
+  // fire on back/forward navigation to the interstitial, which causes an
+  // "extra" interstitial entry to be added to the history list and lose other
+  // entries.
+  auto* entry = web_contents->GetController().GetPendingEntry();
+  if (entry && entry->GetTransitionType() & ui::PAGE_TRANSITION_FORWARD_BACK &&
+      tab_helper->has_failed_upgrade(tentative_resource_request.url)) {
+    std::move(callback).Run({});
+    return;
+  }
+
   if (!ShouldCreateLoader(tentative_resource_request, tab_helper)) {
     std::move(callback).Run({});
     return;
@@ -377,8 +402,9 @@
 
   tab_helper->set_is_navigation_upgraded(false);
   tab_helper->set_is_navigation_fallback(true);
+  tab_helper->add_failed_upgrade(tab_helper->fallback_url());
 
-  // `client_` may have been previously boudn from handling the initial upgrade
+  // `client_` may have been previously bound from handling the initial upgrade
   // in MaybeCreateLoader(), so reset it before re-binding it to handle this
   // response.
   client_.reset();
diff --git a/chrome/browser/ssl/https_upgrades_navigation_throttle.cc b/chrome/browser/ssl/https_upgrades_navigation_throttle.cc
index c922b38f5..563e171e 100644
--- a/chrome/browser/ssl/https_upgrades_navigation_throttle.cc
+++ b/chrome/browser/ssl/https_upgrades_navigation_throttle.cc
@@ -23,6 +23,7 @@
 #include "content/public/browser/navigation_throttle.h"
 #include "content/public/browser/web_contents.h"
 #include "net/base/net_errors.h"
+#include "ui/base/page_transition_types.h"
 
 using security_interstitials::https_only_mode::Event;
 
@@ -91,6 +92,42 @@
 
 content::NavigationThrottle::ThrottleCheckResult
 HttpsUpgradesNavigationThrottle::WillStartRequest() {
+  // If the navigation is fallback to HTTP, trigger the HTTP interstitial (if
+  // enabled). The interceptor creates a redirect for the fallback navigation,
+  // which will trigger MaybeCreateLoader() in the interceptor for the redirect
+  // but *doesn't* trigger WillStartRequest() because it's all part of the same
+  // request. Here, we skip directly to showing the HTTP interstitial if this
+  // is:
+  //   (1) a back/forward navigation, and
+  //   (2) the URL already failed upgrades before.
+  // This lets us avoid triggering the Interceptor during a back/forward
+  // navigation (which breaks history state) and acts like the browser
+  // "remembering" the state of the tab as being on the interstitial for that
+  // URL.
+  //
+  // Other cases for starting a navigation to a URL that previously failed
+  // to be upgraded should go through the full upgrade flow -- better to assume
+  // that something may have changed in the time since. For example: a user
+  // reloading the tab showing the interstitial should re-try the upgrade.
+  auto* handle = navigation_handle();
+  auto* contents = handle->GetWebContents();
+  auto* tab_helper = HttpsOnlyModeTabHelper::FromWebContents(contents);
+  if ((handle->GetPageTransition() & ui::PAGE_TRANSITION_FORWARD_BACK &&
+       tab_helper->has_failed_upgrade(handle->GetURL())) &&
+      !handle->GetURL().SchemeIsCryptographic() && http_interstitial_enabled_) {
+    // Mark this as a fallback HTTP navigation and trigger the interstitial.
+    tab_helper->set_is_navigation_fallback(true);
+    std::unique_ptr<security_interstitials::HttpsOnlyModeBlockingPage>
+        blocking_page = blocking_page_factory_->CreateHttpsOnlyModeBlockingPage(
+            contents, handle->GetURL());
+    std::string interstitial_html = blocking_page->GetHTMLContents();
+    security_interstitials::SecurityInterstitialTabHelper::
+        AssociateBlockingPage(handle, std::move(blocking_page));
+    return content::NavigationThrottle::ThrottleCheckResult(
+        content::NavigationThrottle::CANCEL, net::ERR_BLOCKED_BY_CLIENT,
+        interstitial_html);
+  }
+
   // Navigation is HTTPS or an initial HTTP navigation (which will get
   // upgraded by the interceptor). Fallback HTTP navigations are handled in
   // WillRedirectRequest().
@@ -106,11 +143,11 @@
 
 content::NavigationThrottle::ThrottleCheckResult
 HttpsUpgradesNavigationThrottle::WillRedirectRequest() {
-  // If the navigation is fallback to HTTP, trigger the HTTP interstitial (if
-  // enabled). The interceptor creates a redirect for the fallback navigation,
-  // which will trigger MaybeCreateLoader() in the interceptor for the redirect
-  // but *doesn't* trigger WillStartRequest() because it's all part of the same
-  // request.
+  // If the navigation is doing a fallback redirect to HTTP, trigger the HTTP
+  // interstitial (if enabled). The interceptor creates a redirect for the
+  // fallback navigation, which will trigger MaybeCreateLoader() in the
+  // interceptor for the redirect but *doesn't* trigger WillStartRequest()
+  // because it's all part of the same request.
   auto* handle = navigation_handle();
   auto* contents = handle->GetWebContents();
   auto* tab_helper = HttpsOnlyModeTabHelper::FromWebContents(contents);
diff --git a/chrome/browser/ui/BUILD.gn b/chrome/browser/ui/BUILD.gn
index 0b8ec41..f14b6fa 100644
--- a/chrome/browser/ui/BUILD.gn
+++ b/chrome/browser/ui/BUILD.gn
@@ -1525,8 +1525,6 @@
       "webui/history/history_ui.h",
       "webui/history/navigation_handler.cc",
       "webui/history/navigation_handler.h",
-      "webui/history_clusters/history_cluster_type_utils.cc",
-      "webui/history_clusters/history_cluster_type_utils.h",
       "webui/history_clusters/history_clusters_handler.cc",
       "webui/history_clusters/history_clusters_handler.h",
       "webui/identity_internals_ui.cc",
@@ -3983,11 +3981,11 @@
       "views/apps/app_window_desktop_native_widget_aura_win.h",
       "views/apps/app_window_desktop_window_tree_host_win.cc",
       "views/apps/app_window_desktop_window_tree_host_win.h",
+      "views/apps/app_window_frame_view_win.cc",
+      "views/apps/app_window_frame_view_win.h",
       "views/apps/chrome_app_window_client_views_win.cc",
       "views/apps/chrome_native_app_window_views_win.cc",
       "views/apps/chrome_native_app_window_views_win.h",
-      "views/apps/glass_app_window_frame_view_win.cc",
-      "views/apps/glass_app_window_frame_view_win.h",
       "views/certificate_viewer_win.cc",
       "views/chrome_cleaner_dialog_win.cc",
       "views/chrome_cleaner_dialog_win.h",
diff --git a/chrome/browser/ui/android/fast_checkout/internal/BUILD.gn b/chrome/browser/ui/android/fast_checkout/internal/BUILD.gn
index 5af7fc9..ea39c6e 100644
--- a/chrome/browser/ui/android/fast_checkout/internal/BUILD.gn
+++ b/chrome/browser/ui/android/fast_checkout/internal/BUILD.gn
@@ -14,6 +14,7 @@
     "java/src/org/chromium/chrome/browser/ui/fast_checkout/FastCheckoutMediator.java",
     "java/src/org/chromium/chrome/browser/ui/fast_checkout/FastCheckoutProperties.java",
     "java/src/org/chromium/chrome/browser/ui/fast_checkout/FastCheckoutSheetContent.java",
+    "java/src/org/chromium/chrome/browser/ui/fast_checkout/FastCheckoutSheetState.java",
     "java/src/org/chromium/chrome/browser/ui/fast_checkout/FastCheckoutUserActions.java",
     "java/src/org/chromium/chrome/browser/ui/fast_checkout/detail_screen/AutofillProfileItemProperties.java",
     "java/src/org/chromium/chrome/browser/ui/fast_checkout/detail_screen/AutofillProfileItemViewBinder.java",
@@ -100,6 +101,7 @@
     "junit/src/org/chromium/chrome/browser/ui/fast_checkout/FastCheckoutDetailScreenViewTest.java",
     "junit/src/org/chromium/chrome/browser/ui/fast_checkout/FastCheckoutHomeScreenViewTest.java",
     "junit/src/org/chromium/chrome/browser/ui/fast_checkout/FastCheckoutMediatorTest.java",
+    "junit/src/org/chromium/chrome/browser/ui/fast_checkout/FastCheckoutSheetContentTest.java",
   ]
   deps = [
     ":java",
diff --git a/chrome/browser/ui/android/fast_checkout/internal/java/res/layout/fast_checkout_detail_screen_sheet.xml b/chrome/browser/ui/android/fast_checkout/internal/java/res/layout/fast_checkout_detail_screen_sheet.xml
index 154c73c1..af23c79aa 100644
--- a/chrome/browser/ui/android/fast_checkout/internal/java/res/layout/fast_checkout_detail_screen_sheet.xml
+++ b/chrome/browser/ui/android/fast_checkout/internal/java/res/layout/fast_checkout_detail_screen_sheet.xml
@@ -5,31 +5,28 @@
 found in the LICENSE file.
 -->
 
-<ScrollView
+<LinearLayout
     xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:app="http://schemas.android.com/apk/res-auto"
     xmlns:tools="http://schemas.android.com/tools"
-    android:layout_width="match_parent"
     android:layout_height="wrap_content"
+    android:layout_width="match_parent"
+    android:orientation="vertical"
     android:layout_marginTop="12dp">
-    <LinearLayout
-        android:layout_height="wrap_content"
-        android:layout_width="match_parent"
-        android:orientation="vertical">
 
-        <androidx.appcompat.widget.Toolbar
-            android:id="@+id/action_bar"
-            android:layout_gravity="center_horizontal"
-            android:layout_width="match_parent"
-            android:layout_height="?attr/actionBarSize"
-            android:focusable="true"
-            android:titleTextAppearance="@style/TextAppearance.Headline"
-            style="@style/ModernToolbar" />
+    <androidx.appcompat.widget.Toolbar
+        android:id="@+id/action_bar"
+        android:layout_gravity="center_horizontal"
+        android:layout_width="match_parent"
+        android:layout_height="?attr/actionBarSize"
+        android:focusable="true"
+        android:titleTextAppearance="@style/TextAppearance.Headline"
+        style="@style/ModernToolbar" />
 
       <FrameLayout
           android:id="@+id/sheet_item_list_container"
           android:layout_width="match_parent"
-          android:layout_height="match_parent">
+          android:layout_height="wrap_content">
           <androidx.recyclerview.widget.RecyclerView
               android:id="@+id/fast_checkout_detail_screen_recycler_view"
               android:layout_width="match_parent"
@@ -40,4 +37,3 @@
       </FrameLayout>
 
     </LinearLayout>
-</ScrollView>
diff --git a/chrome/browser/ui/android/fast_checkout/internal/java/res/values/dimens.xml b/chrome/browser/ui/android/fast_checkout/internal/java/res/values/dimens.xml
index 584e726..b18469b2 100644
--- a/chrome/browser/ui/android/fast_checkout/internal/java/res/values/dimens.xml
+++ b/chrome/browser/ui/android/fast_checkout/internal/java/res/values/dimens.xml
@@ -34,4 +34,5 @@
     <dimen name="fast_checkout_detail_sheet_selected_icon_margin_end">16dp</dimen>
     <dimen name="fast_checkout_detail_sheet_height_single_address">160dp</dimen>
     <dimen name="fast_checkout_detail_sheet_height_single_credit_card">92dp</dimen>
+    <dimen name="fast_checkout_detail_sheet_header_height">77dp</dimen>
 </resources>
diff --git a/chrome/browser/ui/android/fast_checkout/internal/java/src/org/chromium/chrome/browser/ui/fast_checkout/FastCheckoutCoordinator.java b/chrome/browser/ui/android/fast_checkout/internal/java/src/org/chromium/chrome/browser/ui/fast_checkout/FastCheckoutCoordinator.java
index a69becb..d1b01e0 100644
--- a/chrome/browser/ui/android/fast_checkout/internal/java/src/org/chromium/chrome/browser/ui/fast_checkout/FastCheckoutCoordinator.java
+++ b/chrome/browser/ui/android/fast_checkout/internal/java/src/org/chromium/chrome/browser/ui/fast_checkout/FastCheckoutCoordinator.java
@@ -28,15 +28,11 @@
     public void initialize(Context context, BottomSheetController sheetController,
             FastCheckoutComponent.Delegate delegate) {
         mBottomSheetController = sheetController;
-        mMediator.initialize(delegate, mModel, mBottomSheetController,
-                context.getResources().getDimensionPixelSize(
-                        R.dimen.fast_checkout_detail_sheet_height_single_address),
-                context.getResources().getDimensionPixelSize(
-                        R.dimen.fast_checkout_detail_sheet_height_single_credit_card));
+        mMediator.initialize(delegate, mModel, mBottomSheetController);
 
         LinearLayout rootView = (LinearLayout) LayoutInflater.from(context).inflate(
                 R.layout.fast_checkout_bottom_sheet, null);
-        mContent = new FastCheckoutSheetContent(rootView);
+        mContent = new FastCheckoutSheetContent(mMediator, rootView);
 
         View homeScreenView = rootView.findViewById(R.id.fast_checkout_home_screen_sheet);
         HomeScreenCoordinator homeScreenCoordinator =
diff --git a/chrome/browser/ui/android/fast_checkout/internal/java/src/org/chromium/chrome/browser/ui/fast_checkout/FastCheckoutMediator.java b/chrome/browser/ui/android/fast_checkout/internal/java/src/org/chromium/chrome/browser/ui/fast_checkout/FastCheckoutMediator.java
index cc304ec..64d196b 100644
--- a/chrome/browser/ui/android/fast_checkout/internal/java/src/org/chromium/chrome/browser/ui/fast_checkout/FastCheckoutMediator.java
+++ b/chrome/browser/ui/android/fast_checkout/internal/java/src/org/chromium/chrome/browser/ui/fast_checkout/FastCheckoutMediator.java
@@ -5,10 +5,8 @@
 package org.chromium.chrome.browser.ui.fast_checkout;
 
 import android.view.MenuItem;
-import android.widget.FrameLayout;
 
 import androidx.annotation.MainThread;
-import androidx.annotation.Px;
 import androidx.appcompat.widget.Toolbar.OnMenuItemClickListener;
 
 import org.chromium.chrome.browser.ui.fast_checkout.FastCheckoutProperties.DetailItemType;
@@ -32,25 +30,17 @@
  * Contains the logic for the FastCheckout component. It sets the state of the model and reacts
  * to events like clicks.
  */
-public class FastCheckoutMediator {
-    private static final float MAX_VISIBLE_ADDRESSES = 2.5f;
-    private static final float MAX_VISIBLE_CREDIT_CARDS = 3.5f;
-
+public class FastCheckoutMediator implements FastCheckoutSheetState {
     private PropertyModel mModel;
     private FastCheckoutComponent.Delegate mDelegate;
     private BottomSheetController mBottomSheetController;
     private BottomSheetObserver mBottomSheetDismissedObserver;
-    private @Px int mAddressItemHeight;
-    private @Px int mCreditCardItemHeight;
 
     void initialize(FastCheckoutComponent.Delegate delegate, PropertyModel model,
-            BottomSheetController bottomSheetController, @Px int addressItemHeight,
-            @Px int creditCardItemHeight) {
+            BottomSheetController bottomSheetController) {
         mModel = model;
         mDelegate = delegate;
         mBottomSheetController = bottomSheetController;
-        mAddressItemHeight = addressItemHeight;
-        mCreditCardItemHeight = creditCardItemHeight;
 
         mBottomSheetDismissedObserver = new EmptyBottomSheetObserver() {
             @Override
@@ -322,8 +312,6 @@
                     }));
             mModel.set(FastCheckoutProperties.DETAIL_SCREEN_MODEL_LIST,
                     mModel.get(FastCheckoutProperties.PROFILE_MODEL_LIST));
-            mModel.set(FastCheckoutProperties.DETAIL_SCREEN_LIST_HEIGHT_IN_PX,
-                    computeAddressListSheetHeight());
         } else if (screenType == ScreenType.CREDIT_CARD_SCREEN) {
             mModel.set(FastCheckoutProperties.DETAIL_SCREEN_TITLE,
                     R.string.fast_checkout_credit_card_sheet_title);
@@ -338,48 +326,11 @@
                     }));
             mModel.set(FastCheckoutProperties.DETAIL_SCREEN_MODEL_LIST,
                     mModel.get(FastCheckoutProperties.CREDIT_CARD_MODEL_LIST));
-            mModel.set(FastCheckoutProperties.DETAIL_SCREEN_LIST_HEIGHT_IN_PX,
-                    computeCreditCardListSheetHeight());
         }
 
         mModel.set(FastCheckoutProperties.CURRENT_SCREEN, screenType);
-    }
-
-    /**
-     * Computes the height of the detail address list.
-     *
-     * If there are 1 or 2 items, it shows all items fully. For 3+ suggestions, it shows the
-     * first 2.5 suggestions to encourage scrolling.
-     */
-    private @Px int computeAddressListSheetHeight() {
-        // Remove the "Add item" button at the end of the list.
-        int numItems = mModel.get(FastCheckoutProperties.PROFILE_MODEL_LIST).size() - 1;
-        // When there are more than {@link MAX_VISIBLE_ADDRESSES} items, resize the list
-        // so that only {@link MAX_VISIBLE_ADDRESSES} items and part of the next one are visible.
-        if (numItems > MAX_VISIBLE_ADDRESSES) {
-            return Math.round((float) mAddressItemHeight * MAX_VISIBLE_ADDRESSES);
-        }
-        // Otherwise display all the items.
-        return FrameLayout.LayoutParams.WRAP_CONTENT;
-    }
-
-    /**
-     * Computes the height of the detail credit card list.
-     *
-     * if there are less than 4 items, it shows all items fully. For 4+ suggestions, it shows the
-     * first 3.5 suggestions to encourage scrolling.
-     */
-    private @Px int computeCreditCardListSheetHeight() {
-        // Remove the "Add item" button at the end of the list.
-        int numItems = mModel.get(FastCheckoutProperties.CREDIT_CARD_MODEL_LIST).size() - 1;
-        // When there are more than {@link MAX_VISIBLE_CREDIT_CARDS} items, resize the
-        // list so that only {@link MAX_VISIBLE_CREDIT_CARDS} items and part of the next one are
-        // visible.
-        if (numItems > MAX_VISIBLE_CREDIT_CARDS) {
-            return Math.round((float) mCreditCardItemHeight * MAX_VISIBLE_CREDIT_CARDS);
-        }
-        // Otherwise display all the items.
-        return FrameLayout.LayoutParams.WRAP_CONTENT;
+        // Sets bottom sheet to half height, if enabled. Otherwise to full.
+        mBottomSheetController.expandSheet();
     }
 
     /**
@@ -408,4 +359,28 @@
         FastCheckoutUserActions.DESTROYED.log();
         mModel.set(FastCheckoutProperties.VISIBLE, false);
     }
+
+    @Override
+    public @ScreenType int getCurrentScreen() {
+        return mModel.get(FastCheckoutProperties.CURRENT_SCREEN);
+    }
+
+    @Override
+    public int getNumOfAutofillProfiles() {
+        // The list contains Autofill profiles and one footer at the end. Subtracts 1 to not count
+        // the footer.
+        return mModel.get(FastCheckoutProperties.PROFILE_MODEL_LIST).size() - 1;
+    }
+
+    @Override
+    public int getNumOfCreditCards() {
+        // The list contains credit cards and one footer at the end. Subtracts 1 to not count the
+        // footer.
+        return mModel.get(FastCheckoutProperties.CREDIT_CARD_MODEL_LIST).size() - 1;
+    }
+
+    @Override
+    public int getContainerHeight() {
+        return mBottomSheetController.getContainerHeight();
+    }
 }
diff --git a/chrome/browser/ui/android/fast_checkout/internal/java/src/org/chromium/chrome/browser/ui/fast_checkout/FastCheckoutProperties.java b/chrome/browser/ui/android/fast_checkout/internal/java/src/org/chromium/chrome/browser/ui/fast_checkout/FastCheckoutProperties.java
index 21d29dd..35f4f44 100644
--- a/chrome/browser/ui/android/fast_checkout/internal/java/src/org/chromium/chrome/browser/ui/fast_checkout/FastCheckoutProperties.java
+++ b/chrome/browser/ui/android/fast_checkout/internal/java/src/org/chromium/chrome/browser/ui/fast_checkout/FastCheckoutProperties.java
@@ -112,10 +112,6 @@
     public static final WritableObjectPropertyKey<ModelList> DETAIL_SCREEN_MODEL_LIST =
             new WritableObjectPropertyKey<>("detail_screen_model_list");
 
-    /** The height in px for the detail sheet item list. */
-    public static final WritableIntPropertyKey DETAIL_SCREEN_LIST_HEIGHT_IN_PX =
-            new WritableIntPropertyKey("detail_screen_recycler_view_height");
-
     public static PropertyModel createDefaultModel() {
         return new PropertyModel.Builder(ALL_KEYS)
                 .with(VISIBLE, false)
@@ -130,6 +126,5 @@
             SELECTED_PROFILE, PROFILE_MODEL_LIST, SELECTED_CREDIT_CARD, CREDIT_CARD_MODEL_LIST,
             HOME_SCREEN_DELEGATE, DETAIL_SCREEN_TITLE, DETAIL_SCREEN_TITLE_DESCRIPTION,
             DETAIL_SCREEN_SETTINGS_MENU_TITLE, DETAIL_SCREEN_BACK_CLICK_HANDLER,
-            DETAIL_SCREEN_SETTINGS_CLICK_HANDLER, DETAIL_SCREEN_MODEL_LIST,
-            DETAIL_SCREEN_LIST_HEIGHT_IN_PX};
+            DETAIL_SCREEN_SETTINGS_CLICK_HANDLER, DETAIL_SCREEN_MODEL_LIST};
 }
diff --git a/chrome/browser/ui/android/fast_checkout/internal/java/src/org/chromium/chrome/browser/ui/fast_checkout/FastCheckoutSheetContent.java b/chrome/browser/ui/android/fast_checkout/internal/java/src/org/chromium/chrome/browser/ui/fast_checkout/FastCheckoutSheetContent.java
index c2955e7..dde21f6a8 100644
--- a/chrome/browser/ui/android/fast_checkout/internal/java/src/org/chromium/chrome/browser/ui/fast_checkout/FastCheckoutSheetContent.java
+++ b/chrome/browser/ui/android/fast_checkout/internal/java/src/org/chromium/chrome/browser/ui/fast_checkout/FastCheckoutSheetContent.java
@@ -4,7 +4,13 @@
 
 package org.chromium.chrome.browser.ui.fast_checkout;
 
+import static org.chromium.chrome.browser.ui.fast_checkout.FastCheckoutProperties.ScreenType.AUTOFILL_PROFILE_SCREEN;
+import static org.chromium.chrome.browser.ui.fast_checkout.FastCheckoutProperties.ScreenType.CREDIT_CARD_SCREEN;
+import static org.chromium.chrome.browser.ui.fast_checkout.FastCheckoutProperties.ScreenType.HOME_SCREEN;
+
 import android.view.View;
+import android.view.View.MeasureSpec;
+import android.view.ViewGroup;
 
 import androidx.annotation.Nullable;
 
@@ -14,12 +20,17 @@
  * The {@link BottomSheetContent} for Fast Checkout.
  */
 public class FastCheckoutSheetContent implements BottomSheetContent {
+    private static final float MAX_VISIBLE_WHOLE_ADDRESSES = 2.5f;
+    private static final float MAX_VISIBLE_WHOLE_CREDIT_CARDS = 3.5f;
+
+    private final FastCheckoutSheetState mState;
     private final View mContentView;
 
     /**
      * Constructs a FastCheckoutSheetContent which creates, modifies, and shows the bottom sheet.
      */
-    FastCheckoutSheetContent(View contentView) {
+    FastCheckoutSheetContent(FastCheckoutSheetState state, View contentView) {
+        mState = state;
         mContentView = contentView;
     }
 
@@ -63,8 +74,24 @@
     }
 
     @Override
+    public float getHalfHeightRatio() {
+        if (shouldWrapContent()) {
+            return HeightMode.DISABLED;
+        }
+        return Math.min(getDesiredDetailSheetHeight(), mState.getContainerHeight())
+                / (float) mState.getContainerHeight();
+    }
+
+    @Override
     public float getFullHeightRatio() {
-        return HeightMode.WRAP_CONTENT;
+        if (shouldWrapContent()) {
+            return HeightMode.WRAP_CONTENT;
+        }
+        // This would ideally also be `WRAP_CONTENT` but that disables half height mode.
+        // `mBottomSheetController.getContainerHeight()` is the height of the bottom sheet's
+        // container, i.e. the screen.
+        return Math.min(getBottomSheetHeight(), mState.getContainerHeight())
+                / (float) mState.getContainerHeight();
     }
 
     @Override
@@ -86,4 +113,56 @@
     public int getSheetFullHeightAccessibilityStringId() {
         return R.string.fast_checkout_content_description;
     }
+
+    private boolean isHomeScreen() {
+        return mState.getCurrentScreen() == HOME_SCREEN;
+    }
+
+    private boolean isAutofillProfileScreen() {
+        return mState.getCurrentScreen() == AUTOFILL_PROFILE_SCREEN;
+    }
+
+    private boolean isCreditCardScreen() {
+        return mState.getCurrentScreen() == CREDIT_CARD_SCREEN;
+    }
+
+    private float getBottomSheetHeight() {
+        ViewGroup parent = (ViewGroup) getContentView().getParent();
+        getContentView().measure(
+                MeasureSpec.makeMeasureSpec(parent.getWidth(), MeasureSpec.EXACTLY),
+                MeasureSpec.makeMeasureSpec(parent.getHeight(), MeasureSpec.AT_MOST));
+        return getContentView().getMeasuredHeight();
+    }
+
+    private boolean shouldWrapContent() {
+        // If there are 1 or 2 Autofill profiles, it shows all items fully. For 3+ suggestions, it
+        // shows the first 2.5 suggestions to encourage scrolling.
+        boolean shouldWrapAutofillProfiles = isAutofillProfileScreen()
+                && mState.getNumOfAutofillProfiles() < MAX_VISIBLE_WHOLE_ADDRESSES;
+        // If there are less than 4 credit cards, it shows all items fully. For 4+ suggestions, it
+        // shows the first 3.5 suggestions to encourage scrolling.
+        boolean shouldWrapCreditCards = isCreditCardScreen()
+                && mState.getNumOfCreditCards() < MAX_VISIBLE_WHOLE_CREDIT_CARDS;
+        return isHomeScreen() || shouldWrapAutofillProfiles || shouldWrapCreditCards;
+    }
+
+    private int getDesiredDetailSheetHeight() {
+        // TODO(crbug.com/1334642): Investigate measuring heights dynamically instead of using
+        // hard-coded values.
+        int height = getDimensionPixelSize(R.dimen.fast_checkout_detail_sheet_header_height);
+        if (isAutofillProfileScreen()) {
+            height += Math.round(MAX_VISIBLE_WHOLE_ADDRESSES
+                    * getDimensionPixelSize(
+                            R.dimen.fast_checkout_detail_sheet_height_single_address));
+        } else {
+            height += Math.round(MAX_VISIBLE_WHOLE_CREDIT_CARDS
+                    * getDimensionPixelSize(
+                            R.dimen.fast_checkout_detail_sheet_height_single_credit_card));
+        }
+        return height;
+    }
+
+    private int getDimensionPixelSize(int id) {
+        return mContentView.getContext().getResources().getDimensionPixelSize(id);
+    }
 }
diff --git a/chrome/browser/ui/android/fast_checkout/internal/java/src/org/chromium/chrome/browser/ui/fast_checkout/FastCheckoutSheetState.java b/chrome/browser/ui/android/fast_checkout/internal/java/src/org/chromium/chrome/browser/ui/fast_checkout/FastCheckoutSheetState.java
new file mode 100644
index 0000000..b7d670d
--- /dev/null
+++ b/chrome/browser/ui/android/fast_checkout/internal/java/src/org/chromium/chrome/browser/ui/fast_checkout/FastCheckoutSheetState.java
@@ -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.
+
+package org.chromium.chrome.browser.ui.fast_checkout;
+
+import org.chromium.chrome.browser.ui.fast_checkout.FastCheckoutProperties.ScreenType;
+
+/**
+ * Provides read-only information about the state of the Fast Checkout bottomsheet.
+ */
+public interface FastCheckoutSheetState {
+    /**
+     * Returns the current screen type of the bottomsheet.
+     */
+    @ScreenType
+    int getCurrentScreen();
+
+    /**
+     * Returns the number of Autofill profiles that would currently be displayed to the user.
+     */
+    int getNumOfAutofillProfiles();
+
+    /**
+     * Returns the number of credit cards that would currently be displayed to the user.
+     */
+    int getNumOfCreditCards();
+
+    /**
+     * Returns the height of the bottomsheet's container, i.e. the screen.
+     */
+    int getContainerHeight();
+}
diff --git a/chrome/browser/ui/android/fast_checkout/internal/java/src/org/chromium/chrome/browser/ui/fast_checkout/detail_screen/DetailScreenViewBinder.java b/chrome/browser/ui/android/fast_checkout/internal/java/src/org/chromium/chrome/browser/ui/fast_checkout/detail_screen/DetailScreenViewBinder.java
index 0a1127d..432c753 100644
--- a/chrome/browser/ui/android/fast_checkout/internal/java/src/org/chromium/chrome/browser/ui/fast_checkout/detail_screen/DetailScreenViewBinder.java
+++ b/chrome/browser/ui/android/fast_checkout/internal/java/src/org/chromium/chrome/browser/ui/fast_checkout/detail_screen/DetailScreenViewBinder.java
@@ -5,7 +5,6 @@
 package org.chromium.chrome.browser.ui.fast_checkout.detail_screen;
 
 import static org.chromium.chrome.browser.ui.fast_checkout.FastCheckoutProperties.DETAIL_SCREEN_BACK_CLICK_HANDLER;
-import static org.chromium.chrome.browser.ui.fast_checkout.FastCheckoutProperties.DETAIL_SCREEN_LIST_HEIGHT_IN_PX;
 import static org.chromium.chrome.browser.ui.fast_checkout.FastCheckoutProperties.DETAIL_SCREEN_MODEL_LIST;
 import static org.chromium.chrome.browser.ui.fast_checkout.FastCheckoutProperties.DETAIL_SCREEN_SETTINGS_CLICK_HANDLER;
 import static org.chromium.chrome.browser.ui.fast_checkout.FastCheckoutProperties.DETAIL_SCREEN_SETTINGS_MENU_TITLE;
@@ -82,9 +81,6 @@
             adapter.registerType(DetailItemType.PROFILE, AutofillProfileItemViewBinder::create,
                     AutofillProfileItemViewBinder::bind);
             view.setAdapter(adapter);
-        } else if (propertyKey == DETAIL_SCREEN_LIST_HEIGHT_IN_PX) {
-            view.mSheetItemListContainer.getLayoutParams().height =
-                    model.get(DETAIL_SCREEN_LIST_HEIGHT_IN_PX);
         }
     }
 }
diff --git a/chrome/browser/ui/android/fast_checkout/internal/junit/src/org/chromium/chrome/browser/ui/fast_checkout/FastCheckoutMediatorTest.java b/chrome/browser/ui/android/fast_checkout/internal/junit/src/org/chromium/chrome/browser/ui/fast_checkout/FastCheckoutMediatorTest.java
index 6d08e449..8db3860 100644
--- a/chrome/browser/ui/android/fast_checkout/internal/junit/src/org/chromium/chrome/browser/ui/fast_checkout/FastCheckoutMediatorTest.java
+++ b/chrome/browser/ui/android/fast_checkout/internal/junit/src/org/chromium/chrome/browser/ui/fast_checkout/FastCheckoutMediatorTest.java
@@ -19,7 +19,6 @@
 import static org.chromium.chrome.browser.ui.fast_checkout.FastCheckoutProperties.CREDIT_CARD_MODEL_LIST;
 import static org.chromium.chrome.browser.ui.fast_checkout.FastCheckoutProperties.CURRENT_SCREEN;
 import static org.chromium.chrome.browser.ui.fast_checkout.FastCheckoutProperties.DETAIL_SCREEN_BACK_CLICK_HANDLER;
-import static org.chromium.chrome.browser.ui.fast_checkout.FastCheckoutProperties.DETAIL_SCREEN_LIST_HEIGHT_IN_PX;
 import static org.chromium.chrome.browser.ui.fast_checkout.FastCheckoutProperties.DETAIL_SCREEN_MODEL_LIST;
 import static org.chromium.chrome.browser.ui.fast_checkout.FastCheckoutProperties.DETAIL_SCREEN_SETTINGS_CLICK_HANDLER;
 import static org.chromium.chrome.browser.ui.fast_checkout.FastCheckoutProperties.DETAIL_SCREEN_SETTINGS_MENU_TITLE;
@@ -31,7 +30,6 @@
 import static org.chromium.chrome.browser.ui.fast_checkout.FastCheckoutProperties.VISIBLE;
 
 import android.view.MenuItem;
-import android.widget.FrameLayout;
 
 import androidx.appcompat.widget.Toolbar.OnMenuItemClickListener;
 import androidx.recyclerview.widget.RecyclerView;
@@ -112,7 +110,7 @@
     public void setUp() {
         MockitoAnnotations.initMocks(this);
         mActionTester = new UserActionTester();
-        mMediator.initialize(mMockDelegate, mModel, mMockBottomSheetController, 160, 92);
+        mMediator.initialize(mMockDelegate, mModel, mMockBottomSheetController);
     }
 
     @After
@@ -428,38 +426,6 @@
         assertActionRecorded(FastCheckoutUserActions.DESTROYED);
     }
 
-    @Test
-    public void testHeightOfAddressItemList() {
-        mMediator.showOptions(
-                DUMMY_PROFILES, DUMMY_CARDS); /* 3 addresses, should show 2 and a half items. */
-        mMediator.setCurrentScreen(ScreenType.AUTOFILL_PROFILE_SCREEN);
-        assertThat(mModel.get(VISIBLE), is(true));
-        assertThat(mModel.get(DETAIL_SCREEN_LIST_HEIGHT_IN_PX), is(400));
-
-        mMediator.showOptions(
-                new FastCheckoutAutofillProfile[] {DUMMY_PROFILES[0], DUMMY_PROFILES[1]},
-                DUMMY_CARDS); /* 2 addresses, should show all items */
-        mMediator.setCurrentScreen(ScreenType.AUTOFILL_PROFILE_SCREEN);
-        assertThat(mModel.get(DETAIL_SCREEN_LIST_HEIGHT_IN_PX),
-                is(FrameLayout.LayoutParams.WRAP_CONTENT));
-    }
-
-    @Test
-    public void testHeightOfCreditCardItemList() {
-        mMediator.showOptions(
-                DUMMY_PROFILES, DUMMY_CARDS); /* 4 credit cards, should show 3 and a half items. */
-        mMediator.setCurrentScreen(ScreenType.CREDIT_CARD_SCREEN);
-        assertThat(mModel.get(VISIBLE), is(true));
-        assertThat(mModel.get(DETAIL_SCREEN_LIST_HEIGHT_IN_PX), is(322));
-
-        mMediator.showOptions(DUMMY_PROFILES,
-                new FastCheckoutCreditCard[] {DUMMY_CARDS[0], DUMMY_CARDS[1],
-                        DUMMY_CARDS[2]}); /* 3 addresses, should show all items */
-        mMediator.setCurrentScreen(ScreenType.CREDIT_CARD_SCREEN);
-        assertThat(mModel.get(DETAIL_SCREEN_LIST_HEIGHT_IN_PX),
-                is(FrameLayout.LayoutParams.WRAP_CONTENT));
-    }
-
     private void assertActionRecorded(FastCheckoutUserActions action) {
         assertTrue(mActionTester.getActions().contains(action.getAction()));
     }
diff --git a/chrome/browser/ui/android/fast_checkout/internal/junit/src/org/chromium/chrome/browser/ui/fast_checkout/FastCheckoutSheetContentTest.java b/chrome/browser/ui/android/fast_checkout/internal/junit/src/org/chromium/chrome/browser/ui/fast_checkout/FastCheckoutSheetContentTest.java
new file mode 100644
index 0000000..d3a3af81
--- /dev/null
+++ b/chrome/browser/ui/android/fast_checkout/internal/junit/src/org/chromium/chrome/browser/ui/fast_checkout/FastCheckoutSheetContentTest.java
@@ -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.
+
+package org.chromium.chrome.browser.ui.fast_checkout;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.Mockito.when;
+
+import static org.chromium.chrome.browser.ui.fast_checkout.FastCheckoutProperties.ScreenType.AUTOFILL_PROFILE_SCREEN;
+import static org.chromium.chrome.browser.ui.fast_checkout.FastCheckoutProperties.ScreenType.CREDIT_CARD_SCREEN;
+import static org.chromium.chrome.browser.ui.fast_checkout.FastCheckoutProperties.ScreenType.HOME_SCREEN;
+import static org.chromium.components.browser_ui.bottomsheet.BottomSheetContent.HeightMode.DISABLED;
+import static org.chromium.components.browser_ui.bottomsheet.BottomSheetContent.HeightMode.WRAP_CONTENT;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.view.View;
+import android.view.ViewGroup;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.annotation.Config;
+
+import org.chromium.base.test.BaseRobolectricTestRunner;
+
+/**
+ * Unit tests for the `FastCheckoutSheetContent` class.
+ */
+@RunWith(BaseRobolectricTestRunner.class)
+@Config(manifest = Config.NONE)
+public class FastCheckoutSheetContentTest {
+    private static final double DELTA = 0.001;
+    private static final int HEADER_HEIGHT = 10;
+    private static final int PROFILE_HEIGHT = 15;
+    private static final int CREDIT_CARD_HEIGHT = 10;
+    private static final int CONTENT_VIEW_HEIGHT = 40;
+    private static final int CONTAINER_HEIGHT = 100;
+
+    private FastCheckoutSheetContent mSheetContent;
+    @Mock
+    private View mContentView;
+    @Mock
+    private ViewGroup mContentViewParent;
+    @Mock
+    private Context mContext;
+    @Mock
+    private Resources mResources;
+    @Mock
+    private FastCheckoutSheetState mState;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        mSheetContent = new FastCheckoutSheetContent(mState, mContentView);
+
+        when(mResources.getDimensionPixelSize(R.dimen.fast_checkout_detail_sheet_header_height))
+                .thenReturn(HEADER_HEIGHT);
+        when(mResources.getDimensionPixelSize(
+                     R.dimen.fast_checkout_detail_sheet_height_single_address))
+                .thenReturn(PROFILE_HEIGHT);
+        when(mResources.getDimensionPixelSize(
+                     R.dimen.fast_checkout_detail_sheet_height_single_credit_card))
+                .thenReturn(CREDIT_CARD_HEIGHT);
+        when(mContext.getResources()).thenReturn(mResources);
+        when(mContentView.getContext()).thenReturn(mContext);
+        when(mContentView.getParent()).thenReturn(mContentViewParent);
+        when(mContentView.getMeasuredHeight()).thenReturn(CONTENT_VIEW_HEIGHT);
+        when(mState.getContainerHeight()).thenReturn(CONTAINER_HEIGHT);
+    }
+
+    @Test
+    public void getFullHeightRatio_HomeScreen_ReturnsWrapContent() {
+        when(mState.getCurrentScreen()).thenReturn(HOME_SCREEN);
+        assertEquals(WRAP_CONTENT, mSheetContent.getFullHeightRatio(), DELTA);
+    }
+
+    @Test
+    public void getHalfHeightRatio_HomeScreen_ReturnsDisabled() {
+        when(mState.getCurrentScreen()).thenReturn(HOME_SCREEN);
+        assertEquals(DISABLED, mSheetContent.getHalfHeightRatio(), DELTA);
+    }
+
+    @Test
+    public void
+    getFullHeightRatio_ProfileScreenAboveThreshold_ReturnsBottomsheetHeightByContainerHeight() {
+        when(mState.getCurrentScreen()).thenReturn(AUTOFILL_PROFILE_SCREEN);
+        when(mState.getNumOfAutofillProfiles()).thenReturn(3);
+        assertEquals(0.4f, mSheetContent.getFullHeightRatio(), DELTA);
+    }
+
+    @Test
+    public void getFullHeightRatio_ProfileScreenBelowThreshold_ReturnsWrapContent() {
+        when(mState.getCurrentScreen()).thenReturn(AUTOFILL_PROFILE_SCREEN);
+        when(mState.getNumOfAutofillProfiles()).thenReturn(2);
+        assertEquals(WRAP_CONTENT, mSheetContent.getFullHeightRatio(), DELTA);
+    }
+
+    @Test
+    public void
+    getHalfHeightRatio_ProfileScreenAboveThreshold_ReturnsDesiredHeightByContainerHeight() {
+        when(mState.getCurrentScreen()).thenReturn(AUTOFILL_PROFILE_SCREEN);
+        when(mState.getNumOfAutofillProfiles()).thenReturn(3);
+        assertEquals(0.48f, mSheetContent.getHalfHeightRatio(), DELTA);
+    }
+
+    @Test
+    public void getHalfHeightRatio_ProfileScreenBelowThreshold_ReturnsDisabled() {
+        when(mState.getCurrentScreen()).thenReturn(AUTOFILL_PROFILE_SCREEN);
+        when(mState.getNumOfAutofillProfiles()).thenReturn(2);
+        assertEquals(DISABLED, mSheetContent.getHalfHeightRatio(), DELTA);
+    }
+
+    @Test
+    public void
+    getFullHeightRatio_CreditCardScreenAboveThreshold_ReturnsBottomsheetHeightByContainerHeight() {
+        when(mState.getCurrentScreen()).thenReturn(CREDIT_CARD_SCREEN);
+        when(mState.getNumOfCreditCards()).thenReturn(4);
+        assertEquals(0.4f, mSheetContent.getFullHeightRatio(), DELTA);
+    }
+
+    @Test
+    public void getFullHeightRatio_CreditCardScreenBelowThreshold_ReturnsWrapContent() {
+        when(mState.getCurrentScreen()).thenReturn(CREDIT_CARD_SCREEN);
+        when(mState.getNumOfCreditCards()).thenReturn(3);
+        assertEquals(WRAP_CONTENT, mSheetContent.getFullHeightRatio(), DELTA);
+    }
+
+    @Test
+    public void
+    getHalfHeightRatio_CreditCardScreenAboveThreshold_ReturnsDesiredHeightByContainerHeight() {
+        when(mState.getCurrentScreen()).thenReturn(CREDIT_CARD_SCREEN);
+        when(mState.getNumOfCreditCards()).thenReturn(4);
+        assertEquals(0.45f, mSheetContent.getHalfHeightRatio(), DELTA);
+    }
+
+    @Test
+    public void getHalfHeightRatio_CreditCardScreenBelowThreshold_ReturnsDisabled() {
+        when(mState.getCurrentScreen()).thenReturn(CREDIT_CARD_SCREEN);
+        when(mState.getNumOfCreditCards()).thenReturn(3);
+        assertEquals(DISABLED, mSheetContent.getHalfHeightRatio(), DELTA);
+    }
+}
diff --git a/chrome/browser/ui/ash/app_access_notifier.cc b/chrome/browser/ui/ash/app_access_notifier.cc
index fdd55f0..e02800a 100644
--- a/chrome/browser/ui/ash/app_access_notifier.cc
+++ b/chrome/browser/ui/ash/app_access_notifier.cc
@@ -184,15 +184,13 @@
     auto launch_app = absl::nullopt;
     auto launch_settings =
         base::BindRepeating(&AppAccessNotifier::LaunchAppSettings, app_id);
-    ash::ModifyPrivacyIndicatorsNotification(
-        app_id, GetAppShortNameFromAppId(app_id), is_camera_used,
-        is_microphone_used,
+
+    ash::UpdatePrivacyIndicators(
+        app_id, /*app_name=*/GetAppShortNameFromAppId(app_id), is_camera_used,
+        is_microphone_used, /*delegate=*/
         base::MakeRefCounted<ash::PrivacyIndicatorsNotificationDelegate>(
             launch_app, launch_settings));
 
-    ash::UpdatePrivacyIndicatorsView(app_id, is_camera_used,
-                                     is_microphone_used);
-
     auto* registry_cache = GetActiveUserAppRegistryCache();
     if (registry_cache) {
       base::UmaHistogramEnumeration(
diff --git a/chrome/browser/ui/ash/app_access_notifier_unittest.cc b/chrome/browser/ui/ash/app_access_notifier_unittest.cc
index 569e62c..b4d099b 100644
--- a/chrome/browser/ui/ash/app_access_notifier_unittest.cc
+++ b/chrome/browser/ui/ash/app_access_notifier_unittest.cc
@@ -11,6 +11,7 @@
 #include "ash/public/cpp/ash_prefs.h"
 #include "ash/root_window_controller.h"
 #include "ash/shell.h"
+#include "ash/system/privacy/privacy_indicators_controller.h"
 #include "ash/system/privacy/privacy_indicators_tray_item_view.h"
 #include "ash/system/status_area_widget.h"
 #include "ash/system/unified/unified_system_tray.h"
@@ -32,6 +33,7 @@
 #include "components/user_manager/user.h"
 #include "content/public/test/browser_task_environment.h"
 #include "testing/gtest/include/gtest/gtest.h"
+#include "ui/compositor/scoped_animation_duration_scale_mode.h"
 #include "ui/display/test/display_manager_test_api.h"
 #include "ui/message_center/message_center.h"
 
@@ -42,9 +44,10 @@
 constexpr char kPrivacyIndicatorsLaunchSettingsHistogramName[] =
     "Ash.PrivacyIndicators.LaunchSettings";
 
-// Check the visibility of privacy indicators in all displays.
+// Check the visibility of privacy indicators and their camera/microphone icons
+// in all displays.
 void ExpectPrivacyIndicatorsVisible(bool visible) {
-  for (ash::RootWindowController* root_window_controller :
+  for (auto* root_window_controller :
        ash::Shell::Get()->GetAllRootWindowControllers()) {
     EXPECT_EQ(root_window_controller->GetStatusAreaWidget()
                   ->unified_system_tray()
@@ -54,9 +57,31 @@
   }
 }
 
-}  // namespace
+void ExpectPrivacyIndicatorsCameraIconVisible(bool visible) {
+  for (auto* root_window_controller :
+       ash::Shell::Get()->GetAllRootWindowControllers()) {
+    EXPECT_EQ(root_window_controller->GetStatusAreaWidget()
+                  ->unified_system_tray()
+                  ->privacy_indicators_view()
+                  ->camera_icon()
+                  ->GetVisible(),
+              visible);
+  }
+}
 
-const char kPrivacyIndicatorsNotificationIdPrefix[] = "privacy-indicators";
+void ExpectPrivacyIndicatorsMicrophoneIconVisible(bool visible) {
+  for (auto* root_window_controller :
+       ash::Shell::Get()->GetAllRootWindowControllers()) {
+    EXPECT_EQ(root_window_controller->GetStatusAreaWidget()
+                  ->unified_system_tray()
+                  ->privacy_indicators_view()
+                  ->microphone_icon()
+                  ->GetVisible(),
+              visible);
+  }
+}
+
+}  // namespace
 
 class TestAppAccessNotifier : public AppAccessNotifier {
  public:
@@ -529,32 +554,41 @@
   // or microphone.
   const std::string id1 = "test_app_id_1";
   const std::string id2 = "test_app_id_2";
+  const std::string notification_id1 =
+      ash::GetPrivacyIndicatorsNotificationId(id1);
+  const std::string notification_id2 =
+      ash::GetPrivacyIndicatorsNotificationId(id2);
 
   LaunchAppUsingCameraOrMicrophone(id1, "test_app_name", /*use_camera=*/false,
                                    /*use_microphone=*/true);
   LaunchAppUsingCameraOrMicrophone(id2, "test_app_name", /*use_camera=*/true,
                                    /*use_microphone=*/false);
   EXPECT_TRUE(message_center::MessageCenter::Get()->FindNotificationById(
-      kPrivacyIndicatorsNotificationIdPrefix + id1));
+      notification_id1));
   EXPECT_TRUE(message_center::MessageCenter::Get()->FindNotificationById(
-      kPrivacyIndicatorsNotificationIdPrefix + id2));
+      notification_id2));
 
   LaunchAppUsingCameraOrMicrophone(id1, "test_app_name", /*use_camera=*/false,
                                    /*use_microphone=*/false);
   LaunchAppUsingCameraOrMicrophone(id2, "test_app_name", /*use_camera=*/false,
                                    /*use_microphone=*/false);
   EXPECT_FALSE(message_center::MessageCenter::Get()->FindNotificationById(
-      kPrivacyIndicatorsNotificationIdPrefix + id1));
+      notification_id1));
   EXPECT_FALSE(message_center::MessageCenter::Get()->FindNotificationById(
-      kPrivacyIndicatorsNotificationIdPrefix + id2));
+      notification_id2));
 
   LaunchAppUsingCameraOrMicrophone(id1, "test_app_name", /*use_camera=*/true,
                                    /*use_microphone=*/true);
   EXPECT_TRUE(message_center::MessageCenter::Get()->FindNotificationById(
-      kPrivacyIndicatorsNotificationIdPrefix + id1));
+      notification_id1));
 }
 
 TEST_F(AppAccessNotifierPrivacyIndicatorTest, PrivacyIndicatorsVisibility) {
+  // Uses normal animation duration so that the icons would not be immediately
+  // hidden after the animation.
+  ui::ScopedAnimationDurationScaleMode animation_scale(
+      ui::ScopedAnimationDurationScaleMode::NORMAL_DURATION);
+
   // Make sure privacy indicators work on multiple displays.
   display::test::DisplayManagerTestApi(ash::Shell::Get()->display_manager())
       .UpdateDisplay("800x800,801+0-800x800");
@@ -562,11 +596,14 @@
   ExpectPrivacyIndicatorsVisible(/*visible=*/false);
 
   // Privacy indicators should show up if at least camera or microphone is being
-  // accessed.
+  // accessed. The icons should show up accordingly (only at the start of the
+  // animation).
   LaunchAppUsingCameraOrMicrophone("test_app_id", "test_app_name",
                                    /*use_camera=*/true,
                                    /*use_microphone=*/true);
   ExpectPrivacyIndicatorsVisible(/*visible=*/true);
+  ExpectPrivacyIndicatorsCameraIconVisible(/*visible=*/true);
+  ExpectPrivacyIndicatorsMicrophoneIconVisible(/*visible=*/true);
 
   LaunchAppUsingCameraOrMicrophone("test_app_id", "test_app_name",
                                    /*use_camera=*/false,
@@ -577,11 +614,15 @@
                                    /*use_camera=*/true,
                                    /*use_microphone=*/false);
   ExpectPrivacyIndicatorsVisible(/*visible=*/true);
+  ExpectPrivacyIndicatorsCameraIconVisible(/*visible=*/true);
+  ExpectPrivacyIndicatorsMicrophoneIconVisible(/*visible=*/false);
 
   LaunchAppUsingCameraOrMicrophone("test_app_id", "test_app_name",
                                    /*use_camera=*/false,
                                    /*use_microphone=*/true);
   ExpectPrivacyIndicatorsVisible(/*visible=*/true);
+  ExpectPrivacyIndicatorsCameraIconVisible(/*visible=*/false);
+  ExpectPrivacyIndicatorsMicrophoneIconVisible(/*visible=*/true);
 }
 
 TEST_F(AppAccessNotifierPrivacyIndicatorTest, RecordAppType) {
diff --git a/chrome/browser/ui/extensions/extension_action_view_controller_unittest.cc b/chrome/browser/ui/extensions/extension_action_view_controller_unittest.cc
index 1130aea3..6acdbea 100644
--- a/chrome/browser/ui/extensions/extension_action_view_controller_unittest.cc
+++ b/chrome/browser/ui/extensions/extension_action_view_controller_unittest.cc
@@ -44,6 +44,7 @@
 #include "extensions/common/extension_features.h"
 #include "extensions/common/mojom/run_location.mojom-shared.h"
 #include "extensions/test/test_extension_dir.h"
+#include "testing/gtest/include/gtest/gtest.h"
 #include "ui/base/l10n/l10n_util.h"
 #include "ui/base/models/image_model.h"
 #include "ui/gfx/image/image_skia_rep.h"
@@ -888,17 +889,6 @@
   EXPECT_EQ(GetHoverCardSiteAccessState(controllerC, web_contents),
             HoverCardState::SiteAccess::kExtensionRequestsAccess);
 
-  // Grant all extensions site access. Verify extension A hover card state is
-  // "does not want access" and extensions B and C is "all extensions allowed".
-  permissions_manager->UpdateUserSiteSetting(
-      url, UserSiteSetting::kGrantAllExtensions);
-  EXPECT_EQ(GetHoverCardSiteAccessState(controllerA, web_contents),
-            HoverCardState::SiteAccess::kExtensionDoesNotWantAccess);
-  EXPECT_EQ(GetHoverCardSiteAccessState(controllerB, web_contents),
-            HoverCardState::SiteAccess::kAllExtensionsAllowed);
-  EXPECT_EQ(GetHoverCardSiteAccessState(controllerC, web_contents),
-            HoverCardState::SiteAccess::kAllExtensionsAllowed);
-
   // Block all extensions site access. Verify all extensions appear as "all
   // extensions blocked" (even though extension A never requested access).
   permissions_manager->UpdateUserSiteSetting(
@@ -922,3 +912,80 @@
   EXPECT_EQ(GetHoverCardSiteAccessState(controllerC, web_contents),
             HoverCardState::SiteAccess::kExtensionRequestsAccess);
 }
+
+class ExtensionActionViewControllerFeatureWithPermittedSitesUnitTest
+    : public ExtensionActionViewControllerFeatureUnitTest {
+ public:
+  ExtensionActionViewControllerFeatureWithPermittedSitesUnitTest() {
+    scoped_feature_list_.InitAndEnableFeature(
+        extensions_features::kExtensionsMenuAccessControlWithPermittedSites);
+  }
+  ~ExtensionActionViewControllerFeatureWithPermittedSitesUnitTest() override =
+      default;
+
+ private:
+  base::test::ScopedFeatureList scoped_feature_list_;
+};
+
+// Tests hover card status after changing user site settings and site access.
+TEST_F(ExtensionActionViewControllerFeatureWithPermittedSitesUnitTest,
+       GetHoverCardStatus) {
+  std::string url_string = "https://example.com/";
+  auto extensionA =
+      CreateAndAddExtension("Extension A", extensions::ActionInfo::TYPE_ACTION);
+  auto extensionB = CreateAndAddExtensionWithGrantedHostPermissions(
+      "Extension B", extensions::ActionInfo::TYPE_ACTION, {url_string});
+  auto extensionC = CreateAndAddExtensionWithGrantedHostPermissions(
+      "Extension c", extensions::ActionInfo::TYPE_ACTION, {url_string});
+
+  AddTab(browser(), GURL(url_string));
+  content::WebContents* web_contents = GetActiveWebContents();
+  ASSERT_TRUE(web_contents);
+  auto url = url::Origin::Create(web_contents->GetLastCommittedURL());
+
+  ExtensionActionViewController* const controllerA =
+      GetViewControllerForId(extensionA->id());
+  ASSERT_TRUE(controllerA);
+  ExtensionActionViewController* const controllerB =
+      GetViewControllerForId(extensionB->id());
+  ASSERT_TRUE(controllerB);
+  ExtensionActionViewController* const controllerC =
+      GetViewControllerForId(extensionC->id());
+  ASSERT_TRUE(controllerC);
+
+  // By default, user site setting is "customize by extension" and site access
+  // is granted to every extension that requests them. Thus, verify extension A
+  // hover card state is "does not want access" and the rest is "have access".
+  auto* permissions_manager =
+      extensions::PermissionsManager::Get(browser()->profile());
+  ASSERT_EQ(permissions_manager->GetUserSiteSetting(url),
+            UserSiteSetting::kCustomizeByExtension);
+  EXPECT_EQ(GetHoverCardSiteAccessState(controllerA, web_contents),
+            HoverCardState::SiteAccess::kExtensionDoesNotWantAccess);
+  EXPECT_EQ(GetHoverCardSiteAccessState(controllerB, web_contents),
+            HoverCardState::SiteAccess::kExtensionHasAccess);
+  EXPECT_EQ(GetHoverCardSiteAccessState(controllerC, web_contents),
+            HoverCardState::SiteAccess::kExtensionHasAccess);
+
+  // Withhold extension C host permissions. Verify only extension C changed
+  // hover card state to "requests access".
+  extensions::ScriptingPermissionsModifier(profile(), extensionC)
+      .SetWithholdHostPermissions(true);
+  EXPECT_EQ(GetHoverCardSiteAccessState(controllerA, web_contents),
+            HoverCardState::SiteAccess::kExtensionDoesNotWantAccess);
+  EXPECT_EQ(GetHoverCardSiteAccessState(controllerB, web_contents),
+            HoverCardState::SiteAccess::kExtensionHasAccess);
+  EXPECT_EQ(GetHoverCardSiteAccessState(controllerC, web_contents),
+            HoverCardState::SiteAccess::kExtensionRequestsAccess);
+
+  // Grant all extensions site access. Verify extension A hover card state is
+  // "does not want access" and extensions B and C is "all extensions allowed".
+  permissions_manager->UpdateUserSiteSetting(
+      url, UserSiteSetting::kGrantAllExtensions);
+  EXPECT_EQ(GetHoverCardSiteAccessState(controllerA, web_contents),
+            HoverCardState::SiteAccess::kExtensionDoesNotWantAccess);
+  EXPECT_EQ(GetHoverCardSiteAccessState(controllerB, web_contents),
+            HoverCardState::SiteAccess::kAllExtensionsAllowed);
+  EXPECT_EQ(GetHoverCardSiteAccessState(controllerC, web_contents),
+            HoverCardState::SiteAccess::kAllExtensionsAllowed);
+}
diff --git a/chrome/browser/ui/layout_constants.cc b/chrome/browser/ui/layout_constants.cc
index 959034f..ff1c5e53 100644
--- a/chrome/browser/ui/layout_constants.cc
+++ b/chrome/browser/ui/layout_constants.cc
@@ -20,8 +20,6 @@
              kBookmarkBarAttachedVerticalMargin;
     case BOOKMARK_BAR_BUTTON_HEIGHT:
       return touch_ui ? 36 : 28;
-    case BOOKMARK_BAR_NTP_HEIGHT:
-      return touch_ui ? GetLayoutConstant(BOOKMARK_BAR_HEIGHT) : 39;
     case WEB_APP_MENU_BUTTON_SIZE:
       return 24;
     case WEB_APP_PAGE_ACTION_ICON_SIZE:
diff --git a/chrome/browser/ui/layout_constants.h b/chrome/browser/ui/layout_constants.h
index 335c7da..e8a41e9 100644
--- a/chrome/browser/ui/layout_constants.h
+++ b/chrome/browser/ui/layout_constants.h
@@ -18,22 +18,6 @@
   // The height of a button within the Bookmarks Bar.
   BOOKMARK_BAR_BUTTON_HEIGHT,
 
-#if BUILDFLAG(IS_MAC)
-  // This is a little smaller than the bookmarkbar height because of the visual
-  // overlap with the main toolbar. This height should not be used when
-  // computing the height of the toolbar.
-  BOOKMARK_BAR_HEIGHT_NO_OVERLAP,
-#endif
-
-  // The height of Bookmarks Bar, when visible in "New Tab Page" mode.
-  BOOKMARK_BAR_NTP_HEIGHT,
-
-#if BUILDFLAG(IS_MAC)
-  // The amount of space between the inner bookmark bar and the outer toolbar on
-  // new tab pages.
-  BOOKMARK_BAR_NTP_PADDING,
-#endif
-
   // The size of icons used in Download bubbles.
   // TODO(crbug/1296323): We should be sourcing the size of the file icon from
   // the layout
diff --git a/chrome/browser/ui/views/apps/app_window_desktop_window_tree_host_win.cc b/chrome/browser/ui/views/apps/app_window_desktop_window_tree_host_win.cc
index 93067da..367e9184 100644
--- a/chrome/browser/ui/views/apps/app_window_desktop_window_tree_host_win.cc
+++ b/chrome/browser/ui/views/apps/app_window_desktop_window_tree_host_win.cc
@@ -7,8 +7,8 @@
 #include <windows.h>
 
 #include "base/win/windows_version.h"
+#include "chrome/browser/ui/views/apps/app_window_frame_view_win.h"
 #include "chrome/browser/ui/views/apps/chrome_native_app_window_views_win.h"
-#include "chrome/browser/ui/views/apps/glass_app_window_frame_view_win.h"
 #include "ui/base/theme_provider.h"
 #include "ui/display/win/dpi.h"
 #include "ui/gfx/geometry/dip_util.h"
@@ -32,28 +32,29 @@
   // The inset added below is only necessary for the native glass frame, i.e.
   // not for colored frames drawn by Chrome, or when DWM is disabled.
   // In fullscreen the frame is not visible.
-  if (!app_window_->glass_frame_view() || IsFullscreen()) {
+  if (!app_window_->frame_view() || IsFullscreen()) {
     return false;
   }
 
-  *insets = app_window_->glass_frame_view()->GetClientAreaInsets(monitor);
+  *insets = app_window_->frame_view()->GetClientAreaInsets(monitor);
 
   return true;
 }
 
 bool AppWindowDesktopWindowTreeHostWin::GetDwmFrameInsetsInPixels(
     gfx::Insets* insets) const {
-  // If there's no glass view we never need to change DWM frame insets.
-  if (!GetWidget()->client_view() || !app_window_->glass_frame_view() ||
-      !DesktopWindowTreeHostWin::ShouldUseNativeFrame())
+  // If there's no frame view we never need to change DWM frame insets.
+  if (!GetWidget()->client_view() || !app_window_->frame_view() ||
+      !DesktopWindowTreeHostWin::ShouldUseNativeFrame()) {
     return false;
+  }
 
   if (GetWidget()->IsFullscreen()) {
     *insets = gfx::Insets();
   } else {
     // If the opaque frame is visible, we use the default (zero) margins.
     // Otherwise, we need to figure out how to extend the glass in.
-    *insets = app_window_->glass_frame_view()->GetGlassInsets();
+    *insets = app_window_->frame_view()->GetInsets();
     // The DWM API's expect values in pixels. We need to convert from DIP to
     // pixels here.
     *insets = gfx::ToFlooredInsets(
diff --git a/chrome/browser/ui/views/apps/glass_app_window_frame_view_win.cc b/chrome/browser/ui/views/apps/app_window_frame_view_win.cc
similarity index 69%
rename from chrome/browser/ui/views/apps/glass_app_window_frame_view_win.cc
rename to chrome/browser/ui/views/apps/app_window_frame_view_win.cc
index 321bd4e..16730d4 100644
--- a/chrome/browser/ui/views/apps/glass_app_window_frame_view_win.cc
+++ b/chrome/browser/ui/views/apps/app_window_frame_view_win.cc
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "chrome/browser/ui/views/apps/glass_app_window_frame_view_win.h"
+#include "chrome/browser/ui/views/apps/app_window_frame_view_win.h"
 
 #include <windows.h>
 
@@ -23,13 +23,12 @@
 
 }  // namespace
 
-GlassAppWindowFrameViewWin::GlassAppWindowFrameViewWin(views::Widget* widget)
+AppWindowFrameViewWin::AppWindowFrameViewWin(views::Widget* widget)
     : widget_(widget) {}
 
-GlassAppWindowFrameViewWin::~GlassAppWindowFrameViewWin() {
-}
+AppWindowFrameViewWin::~AppWindowFrameViewWin() {}
 
-gfx::Insets GlassAppWindowFrameViewWin::GetGlassInsets() const {
+gfx::Insets AppWindowFrameViewWin::GetFrameInsets() const {
   int caption_height =
       display::win::ScreenWin::GetSystemMetricsInDIP(SM_CYSIZEFRAME) +
       display::win::ScreenWin::GetSystemMetricsInDIP(SM_CYCAPTION);
@@ -37,30 +36,30 @@
   return gfx::Insets::TLBR(caption_height, 0, 0, 0);
 }
 
-gfx::Insets GlassAppWindowFrameViewWin::GetClientAreaInsets(
-    HMONITOR monitor) const {
+gfx::Insets AppWindowFrameViewWin::GetClientAreaInsets(HMONITOR monitor) const {
   const int frame_thickness = ui::GetFrameThickness(monitor);
   return gfx::Insets::TLBR(0, frame_thickness, frame_thickness,
                            frame_thickness);
 }
 
-gfx::Rect GlassAppWindowFrameViewWin::GetBoundsForClientView() const {
-  if (widget_->IsFullscreen())
+gfx::Rect AppWindowFrameViewWin::GetBoundsForClientView() const {
+  if (widget_->IsFullscreen()) {
     return bounds();
+  }
 
-  gfx::Insets insets = GetGlassInsets();
-  return gfx::Rect(insets.left(),
-                   insets.top(),
+  gfx::Insets insets = GetFrameInsets();
+  return gfx::Rect(insets.left(), insets.top(),
                    std::max(0, width() - insets.left() - insets.right()),
                    std::max(0, height() - insets.top() - insets.bottom()));
 }
 
-gfx::Rect GlassAppWindowFrameViewWin::GetWindowBoundsForClientBounds(
+gfx::Rect AppWindowFrameViewWin::GetWindowBoundsForClientBounds(
     const gfx::Rect& client_bounds) const {
-  if (widget_->IsFullscreen())
+  if (widget_->IsFullscreen()) {
     return bounds();
+  }
 
-  gfx::Insets insets = GetGlassInsets();
+  gfx::Insets insets = GetFrameInsets();
   insets += GetClientAreaInsets(
       MonitorFromWindow(HWNDForView(this), MONITOR_DEFAULTTONEAREST));
   gfx::Rect window_bounds(
@@ -73,12 +72,14 @@
   return window_bounds;
 }
 
-int GlassAppWindowFrameViewWin::NonClientHitTest(const gfx::Point& point) {
-  if (widget_->IsFullscreen())
+int AppWindowFrameViewWin::NonClientHitTest(const gfx::Point& point) {
+  if (widget_->IsFullscreen()) {
     return HTCLIENT;
+  }
 
-  if (!bounds().Contains(point))
+  if (!bounds().Contains(point)) {
     return HTNOWHERE;
+  }
 
   // Check the frame first, as we allow a small area overlapping the contents
   // to be used for resize handles.
@@ -92,23 +93,25 @@
   int frame_component = GetHTComponentForFrame(
       point, gfx::Insets(resize_border), kResizeAreaCornerSize - resize_border,
       kResizeAreaCornerSize - resize_border, can_ever_resize);
-  if (frame_component != HTNOWHERE)
+  if (frame_component != HTNOWHERE) {
     return frame_component;
+  }
 
   int client_component = widget_->client_view()->NonClientHitTest(point);
-  if (client_component != HTNOWHERE)
+  if (client_component != HTNOWHERE) {
     return client_component;
+  }
 
   // Caption is a safe default.
   return HTCAPTION;
 }
 
-void GlassAppWindowFrameViewWin::GetWindowMask(const gfx::Size& size,
-                                               SkPath* window_mask) {
+void AppWindowFrameViewWin::GetWindowMask(const gfx::Size& size,
+                                          SkPath* window_mask) {
   // We got nothing to say about no window mask.
 }
 
-gfx::Size GlassAppWindowFrameViewWin::CalculatePreferredSize() const {
+gfx::Size AppWindowFrameViewWin::CalculatePreferredSize() const {
   gfx::Size pref = widget_->client_view()->GetPreferredSize();
   gfx::Rect bounds(0, 0, pref.width(), pref.height());
   return widget_->non_client_view()
@@ -116,27 +119,29 @@
       .size();
 }
 
-gfx::Size GlassAppWindowFrameViewWin::GetMinimumSize() const {
+gfx::Size AppWindowFrameViewWin::GetMinimumSize() const {
   gfx::Size min_size = widget_->client_view()->GetMinimumSize();
 
-  gfx::Insets insets = GetGlassInsets();
+  gfx::Insets insets = GetFrameInsets();
   min_size.Enlarge(insets.left() + insets.right(),
                    insets.top() + insets.bottom());
 
   return min_size;
 }
 
-gfx::Size GlassAppWindowFrameViewWin::GetMaximumSize() const {
+gfx::Size AppWindowFrameViewWin::GetMaximumSize() const {
   gfx::Size max_size = widget_->client_view()->GetMaximumSize();
 
-  gfx::Insets insets = GetGlassInsets();
-  if (max_size.width())
+  gfx::Insets insets = GetFrameInsets();
+  if (max_size.width()) {
     max_size.Enlarge(insets.left() + insets.right(), 0);
-  if (max_size.height())
+  }
+  if (max_size.height()) {
     max_size.Enlarge(0, insets.top() + insets.bottom());
+  }
 
   return max_size;
 }
 
-BEGIN_METADATA(GlassAppWindowFrameViewWin, views::NonClientFrameView)
+BEGIN_METADATA(AppWindowFrameViewWin, views::NonClientFrameView)
 END_METADATA
diff --git a/chrome/browser/ui/views/apps/glass_app_window_frame_view_win.h b/chrome/browser/ui/views/apps/app_window_frame_view_win.h
similarity index 64%
rename from chrome/browser/ui/views/apps/glass_app_window_frame_view_win.h
rename to chrome/browser/ui/views/apps/app_window_frame_view_win.h
index 50299479..bd6c0be 100644
--- a/chrome/browser/ui/views/apps/glass_app_window_frame_view_win.h
+++ b/chrome/browser/ui/views/apps/app_window_frame_view_win.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 CHROME_BROWSER_UI_VIEWS_APPS_GLASS_APP_WINDOW_FRAME_VIEW_WIN_H_
-#define CHROME_BROWSER_UI_VIEWS_APPS_GLASS_APP_WINDOW_FRAME_VIEW_WIN_H_
+#ifndef CHROME_BROWSER_UI_VIEWS_APPS_APP_WINDOW_FRAME_VIEW_WIN_H_
+#define CHROME_BROWSER_UI_VIEWS_APPS_APP_WINDOW_FRAME_VIEW_WIN_H_
 
 #include "base/memory/raw_ptr.h"
 #include "ui/base/metadata/metadata_header_macros.h"
@@ -11,18 +11,17 @@
 #include "ui/views/metadata/view_factory.h"
 #include "ui/views/window/non_client_view.h"
 
-// A glass style app window frame view.
-class GlassAppWindowFrameViewWin : public views::NonClientFrameView {
+// A Windows app window frame view.
+class AppWindowFrameViewWin : public views::NonClientFrameView {
  public:
-  METADATA_HEADER(GlassAppWindowFrameViewWin);
-  explicit GlassAppWindowFrameViewWin(views::Widget* widget);
-  GlassAppWindowFrameViewWin(const GlassAppWindowFrameViewWin&) = delete;
-  GlassAppWindowFrameViewWin& operator=(const GlassAppWindowFrameViewWin&) =
-      delete;
-  ~GlassAppWindowFrameViewWin() override;
+  METADATA_HEADER(AppWindowFrameViewWin);
+  explicit AppWindowFrameViewWin(views::Widget* widget);
+  AppWindowFrameViewWin(const AppWindowFrameViewWin&) = delete;
+  AppWindowFrameViewWin& operator=(const AppWindowFrameViewWin&) = delete;
+  ~AppWindowFrameViewWin() override;
 
-  // The insets to the client area due to the glass frame.
-  gfx::Insets GetGlassInsets() const;
+  // The insets to the client area due to the frame.
+  gfx::Insets GetFrameInsets() const;
 
   // Additional insets to the client area.  |monitor| is the monitor this
   // window is on.  Normally that would be determined from the HWND, but
@@ -51,10 +50,10 @@
 };
 
 BEGIN_VIEW_BUILDER(/* no export */,
-                   GlassAppWindowFrameViewWin,
+                   AppWindowFrameViewWin,
                    views::NonClientFrameView)
 END_VIEW_BUILDER
 
-DEFINE_VIEW_BUILDER(/* no export */, GlassAppWindowFrameViewWin)
+DEFINE_VIEW_BUILDER(/* no export */, AppWindowFrameViewWin)
 
-#endif  // CHROME_BROWSER_UI_VIEWS_APPS_GLASS_APP_WINDOW_FRAME_VIEW_WIN_H_
+#endif  // CHROME_BROWSER_UI_VIEWS_APPS_APP_WINDOW_FRAME_VIEW_WIN_H_
diff --git a/chrome/browser/ui/views/apps/chrome_native_app_window_views_win.cc b/chrome/browser/ui/views/apps/chrome_native_app_window_views_win.cc
index a6e9da0..4db5703 100644
--- a/chrome/browser/ui/views/apps/chrome_native_app_window_views_win.cc
+++ b/chrome/browser/ui/views/apps/chrome_native_app_window_views_win.cc
@@ -11,7 +11,7 @@
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/shell_integration_win.h"
 #include "chrome/browser/ui/views/apps/app_window_desktop_native_widget_aura_win.h"
-#include "chrome/browser/ui/views/apps/glass_app_window_frame_view_win.h"
+#include "chrome/browser/ui/views/apps/app_window_frame_view_win.h"
 #include "chrome/browser/web_applications/extensions/web_app_extension_shortcut.h"
 #include "chrome/browser/web_applications/web_app_helpers.h"
 #include "chrome/common/chrome_switches.h"
@@ -77,10 +77,9 @@
 
 std::unique_ptr<views::NonClientFrameView>
 ChromeNativeAppWindowViewsWin::CreateStandardDesktopAppFrame() {
-  auto glass_frame_view =
-      std::make_unique<GlassAppWindowFrameViewWin>(widget());
-  glass_frame_view_ = glass_frame_view.get();
-  return glass_frame_view;
+  auto frame_view = std::make_unique<AppWindowFrameViewWin>(widget());
+  frame_view_ = frame_view.get();
+  return frame_view;
 }
 
 bool ChromeNativeAppWindowViewsWin::CanMinimize() const {
diff --git a/chrome/browser/ui/views/apps/chrome_native_app_window_views_win.h b/chrome/browser/ui/views/apps/chrome_native_app_window_views_win.h
index f6c82a03..f35610de 100644
--- a/chrome/browser/ui/views/apps/chrome_native_app_window_views_win.h
+++ b/chrome/browser/ui/views/apps/chrome_native_app_window_views_win.h
@@ -12,7 +12,7 @@
 struct ShortcutInfo;
 }
 
-class GlassAppWindowFrameViewWin;
+class AppWindowFrameViewWin;
 
 // Windows-specific parts of the views-backed native shell window implementation
 // for packaged apps.
@@ -26,9 +26,7 @@
 
   ~ChromeNativeAppWindowViewsWin() override;
 
-  GlassAppWindowFrameViewWin* glass_frame_view() {
-    return glass_frame_view_;
-  }
+  AppWindowFrameViewWin* frame_view() { return frame_view_; }
 
  private:
   void OnShortcutInfoLoaded(
@@ -53,7 +51,7 @@
   // Populated if there is a standard desktop app frame, which provides special
   // information to the native widget implementation. This will be NULL if the
   // frame is a non-standard app frame created by CreateNonStandardAppFrame.
-  raw_ptr<GlassAppWindowFrameViewWin> glass_frame_view_ = nullptr;
+  raw_ptr<AppWindowFrameViewWin> frame_view_ = nullptr;
 
   // The Windows Application User Model ID identifying the app.
   std::wstring app_model_id_;
diff --git a/chrome/browser/ui/views/extensions/extensions_toolbar_controls_unittest.cc b/chrome/browser/ui/views/extensions/extensions_toolbar_controls_unittest.cc
index d04f4e1..c464a99 100644
--- a/chrome/browser/ui/views/extensions/extensions_toolbar_controls_unittest.cc
+++ b/chrome/browser/ui/views/extensions/extensions_toolbar_controls_unittest.cc
@@ -238,15 +238,6 @@
   EXPECT_TRUE(IsRequestAccessButtonVisible());
 
   auto* manager = extensions::PermissionsManager::Get(profile());
-  {
-    // Request access button is not visible in permitted sites.
-    extensions::PermissionsManagerWaiter manager_waiter(
-        extensions::PermissionsManager::Get(profile()));
-    manager->AddUserPermittedSite(url_origin);
-    manager_waiter.WaitForUserPermissionsSettingsChange();
-    WaitForAnimation();
-    EXPECT_FALSE(IsRequestAccessButtonVisible());
-  }
 
   {
     // Request access button is not visible in restricted sites.
@@ -259,7 +250,7 @@
   }
 
   {
-    // Request acesss button is visible if site is not permitted or restricted,
+    // Request acesss button is visible if site is not restricted,
     // and at least one extension is requesting access.
     extensions::PermissionsManagerWaiter manager_waiter(
         extensions::PermissionsManager::Get(profile()));
@@ -309,3 +300,59 @@
   EXPECT_EQ(permissions.GetSiteAccess(*extension, url),
             extensions::SitePermissionsHelper::SiteAccess::kOnClick);
 }
+
+class ExtensionsToolbarControlsWithPermittedSitesUnitTest
+    : public ExtensionsToolbarControlsUnitTest {
+ public:
+  ExtensionsToolbarControlsWithPermittedSitesUnitTest() {
+    std::vector<base::test::FeatureRef> enabled_features = {
+        extensions_features::kExtensionsMenuAccessControl,
+        extensions_features::kExtensionsMenuAccessControlWithPermittedSites};
+    std::vector<base::test::FeatureRef> disabled_features;
+    feature_list_.InitWithFeatures(enabled_features, disabled_features);
+  }
+  ExtensionsToolbarControlsWithPermittedSitesUnitTest(
+      const ExtensionsToolbarControlsWithPermittedSitesUnitTest&) = delete;
+  const ExtensionsToolbarControlsWithPermittedSitesUnitTest& operator=(
+      const ExtensionsToolbarControlsWithPermittedSitesUnitTest&) = delete;
+  ~ExtensionsToolbarControlsWithPermittedSitesUnitTest() override = default;
+
+ private:
+  base::test::ScopedFeatureList feature_list_;
+};
+
+// Test that request access button is visible based on the user site setting
+// selected.
+TEST_F(ExtensionsToolbarControlsWithPermittedSitesUnitTest,
+       RequestAccessButtonVisibilityOnPermittedSites) {
+  content::WebContentsTester* web_contents_tester =
+      AddWebContentsAndGetTester();
+  const GURL url("http://www.url.com");
+  auto url_origin = url::Origin::Create(url);
+
+  // Install an extension and withhold permissions so request access button can
+  // be visible.
+  auto extension =
+      InstallExtensionWithHostPermissions("Extension", {"<all_urls>"});
+  WithholdHostPermissions(extension.get());
+
+  web_contents_tester->NavigateAndCommit(url);
+  WaitForAnimation();
+
+  // A site has "customize by extensions" site setting by default,
+  ASSERT_EQ(
+      GetUserSiteSetting(url),
+      extensions::PermissionsManager::UserSiteSetting::kCustomizeByExtension);
+  EXPECT_TRUE(IsRequestAccessButtonVisible());
+
+  // Request access button is not visible in permitted sites.
+  auto* manager = extensions::PermissionsManager::Get(profile());
+  extensions::PermissionsManagerWaiter waiter(manager);
+  manager->AddUserPermittedSite(url_origin);
+  waiter.WaitForUserPermissionsSettingsChange();
+  WaitForAnimation();
+
+  // Request access button visibility is the same for other site settings, which
+  // is already tested, regardless of whether permitted sites are supported or
+  // not.
+}
diff --git a/chrome/browser/ui/views/web_apps/web_app_integration_test_driver.cc b/chrome/browser/ui/views/web_apps/web_app_integration_test_driver.cc
index 5e47e60..4ef9a04 100644
--- a/chrome/browser/ui/views/web_apps/web_app_integration_test_driver.cc
+++ b/chrome/browser/ui/views/web_apps/web_app_integration_test_driver.cc
@@ -483,10 +483,12 @@
   for (auto it = browser_list->begin_browsers_ordered_by_activation();
        it != browser_list->end_browsers_ordered_by_activation(); ++it) {
     Browser* browser = *it;
-    if (browser->profile() != profile)
+    if (browser->profile() != profile) {
       continue;
-    if (AppBrowserController::IsForWebApp(browser, app_id))
+    }
+    if (AppBrowserController::IsForWebApp(browser, app_id)) {
       return browser;
+    }
   }
   return nullptr;
 }
@@ -527,17 +529,20 @@
 
   // WebAppInstallManagerObserver
   void OnWebAppUninstalled(const AppId& app_id) override {
-    if (app_id != app_id_)
+    if (app_id != app_id_) {
       return;
+    }
     uninstall_complete_ = true;
     MaybeFinishWaiting();
   }
 
   void MaybeFinishWaiting() {
-    if (!uninstall_complete_)
+    if (!uninstall_complete_) {
       return;
-    if (AreAppBrowsersOpen(profile_, app_id_))
+    }
+    if (AreAppBrowsersOpen(profile_, app_id_)) {
       return;
+    }
 
     BrowserList::RemoveObserver(this);
     observation_.Reset();
@@ -587,8 +592,9 @@
     for (int size_px : sizes_px) {
       SkColor icon_pixel_color =
           IconManagerReadAppIconPixel(icon_manager, app_id, size_px, 0, 0);
-      if (icon_pixel_color != expected_icon_pixel_color)
+      if (icon_pixel_color != expected_icon_pixel_color) {
         return false;
+      }
     }
     return true;
   }
@@ -866,12 +872,14 @@
   in_tear_down_ = true;
   LOG(INFO) << "TearDownOnMainThread: Start.";
   observation_.Reset();
-  if (delegate_->IsSyncTest())
+  if (delegate_->IsSyncTest()) {
     SyncTurnOff();
+  }
   for (auto* profile : GetAllProfiles()) {
     auto* provider = GetProviderForProfile(profile);
-    if (!provider)
+    if (!provider) {
       continue;
+    }
     std::vector<AppId> app_ids = provider->registrar_unsafe().GetAppIds();
     for (auto& app_id : app_ids) {
       LOG(INFO) << "TearDownOnMainThread: Uninstalling " << app_id << ".";
@@ -883,8 +891,9 @@
                   << " was already removed.";
         continue;
       }
-      if (app->IsPolicyInstalledApp())
+      if (app->IsPolicyInstalledApp()) {
         UninstallPolicyAppById(app_id);
+      }
       if (provider->registrar_unsafe().IsInstalled(app_id)) {
         DCHECK(app->CanUserUninstallWebApp());
         UninstallCompleteWaiter uninstall_waiter(profile, app_id);
@@ -959,8 +968,9 @@
 }
 
 void WebAppIntegrationTestDriver::AwaitManifestUpdate(Site site) {
-  if (!BeforeStateChangeAction(__FUNCTION__))
+  if (!BeforeStateChangeAction(__FUNCTION__)) {
     return;
+  }
   AppId app_id = GetAppIdBySiteMode(site);
   ASSERT_TRUE(provider()->registrar_unsafe().GetAppById(app_id));
   if (!previous_manifest_updates_.contains(app_id)) {
@@ -993,8 +1003,9 @@
 }
 
 void WebAppIntegrationTestDriver::CloseCustomToolbar() {
-  if (!BeforeStateChangeAction(__FUNCTION__))
+  if (!BeforeStateChangeAction(__FUNCTION__)) {
     return;
+  }
   ASSERT_TRUE(app_browser());
   BrowserView* app_view = BrowserView::GetBrowserViewForBrowser(app_browser());
   content::WebContents* web_contents = app_view->GetActiveWebContents();
@@ -1035,37 +1046,42 @@
 }
 
 void WebAppIntegrationTestDriver::DisableRunOnOsLogin(Site site) {
-  if (!BeforeStateChangeAction(__FUNCTION__))
+  if (!BeforeStateChangeAction(__FUNCTION__)) {
     return;
+  }
   SetRunOnOsLoginMode(site, apps::RunOnOsLoginMode::kNotRun);
   AfterStateChangeAction();
 }
 
 void WebAppIntegrationTestDriver::EnableRunOnOsLogin(Site site) {
-  if (!BeforeStateChangeAction(__FUNCTION__))
+  if (!BeforeStateChangeAction(__FUNCTION__)) {
     return;
+  }
   SetRunOnOsLoginMode(site, apps::RunOnOsLoginMode::kWindowed);
   AfterStateChangeAction();
 }
 
 void WebAppIntegrationTestDriver::DisableFileHandling(Site site) {
-  if (!BeforeStateChangeAction(__FUNCTION__))
+  if (!BeforeStateChangeAction(__FUNCTION__)) {
     return;
+  }
   SetFileHandlingEnabled(site, false);
   AfterStateChangeAction();
 }
 
 void WebAppIntegrationTestDriver::EnableFileHandling(Site site) {
-  if (!BeforeStateChangeAction(__FUNCTION__))
+  if (!BeforeStateChangeAction(__FUNCTION__)) {
     return;
+  }
   SetFileHandlingEnabled(site, true);
   AfterStateChangeAction();
 }
 
 void WebAppIntegrationTestDriver::CreateShortcut(Site site,
                                                  WindowOptions options) {
-  if (!BeforeStateChangeAction(__FUNCTION__))
+  if (!BeforeStateChangeAction(__FUNCTION__)) {
     return;
+  }
   MaybeNavigateTabbedBrowserInScope(site);
   bool open_in_window = options == WindowOptions::kWindowed;
   chrome::SetAutoAcceptWebAppDialogForTesting(
@@ -1087,8 +1103,9 @@
 }
 
 void WebAppIntegrationTestDriver::InstallMenuOption(InstallableSite site) {
-  if (!BeforeStateChangeAction(__FUNCTION__))
+  if (!BeforeStateChangeAction(__FUNCTION__)) {
     return;
+  }
   MaybeNavigateTabbedBrowserInScope(InstallableSiteToSite(site));
   BrowserAddedWaiter browser_added_waiter;
   WebAppTestInstallWithOsHooksObserver install_observer(profile());
@@ -1112,8 +1129,9 @@
 
 #if !BUILDFLAG(IS_CHROMEOS)
 void WebAppIntegrationTestDriver::InstallLocally(Site site) {
-  if (!BeforeStateChangeAction(__FUNCTION__))
+  if (!BeforeStateChangeAction(__FUNCTION__)) {
     return;
+  }
   AppId app_id = GetAppIdBySiteMode(site);
   ASSERT_TRUE(provider()->registrar_unsafe().GetAppById(app_id))
       << "No app installed for site: " << static_cast<int>(site);
@@ -1137,8 +1155,9 @@
 #endif
 
 void WebAppIntegrationTestDriver::InstallOmniboxIcon(InstallableSite site) {
-  if (!BeforeStateChangeAction(__FUNCTION__))
+  if (!BeforeStateChangeAction(__FUNCTION__)) {
     return;
+  }
   MaybeNavigateTabbedBrowserInScope(InstallableSiteToSite(site));
 
   auto* app_banner_manager =
@@ -1178,8 +1197,9 @@
                                                    ShortcutOptions shortcut,
                                                    WindowOptions window,
                                                    InstallMode mode) {
-  if (!BeforeStateChangeAction(__FUNCTION__))
+  if (!BeforeStateChangeAction(__FUNCTION__)) {
     return;
+  }
   base::Value container = base::Value(window == WindowOptions::kWindowed
                                           ? kDefaultLaunchContainerWindowValue
                                           : kDefaultLaunchContainerTabValue);
@@ -1243,8 +1263,9 @@
 }
 
 void WebAppIntegrationTestDriver::EnableWindowControlsOverlay(Site site) {
-  if (!BeforeStateChangeAction(__FUNCTION__))
+  if (!BeforeStateChangeAction(__FUNCTION__)) {
     return;
+  }
   ASSERT_TRUE(app_browser());
   BrowserView* app_view = BrowserView::GetBrowserViewForBrowser(app_browser());
 
@@ -1261,8 +1282,9 @@
 }
 
 void WebAppIntegrationTestDriver::DisableWindowControlsOverlay(Site site) {
-  if (!BeforeStateChangeAction(__FUNCTION__))
+  if (!BeforeStateChangeAction(__FUNCTION__)) {
     return;
+  }
   ASSERT_TRUE(app_browser());
   BrowserView* app_view = BrowserView::GetBrowserViewForBrowser(app_browser());
 
@@ -1279,30 +1301,34 @@
 }
 
 void WebAppIntegrationTestDriver::ApplyRunOnOsLoginPolicyAllowed(Site site) {
-  if (!BeforeStateChangeAction(__FUNCTION__))
+  if (!BeforeStateChangeAction(__FUNCTION__)) {
     return;
+  }
   ApplyRunOnOsLoginPolicy(site, kAllowed);
   AfterStateChangeAction();
 }
 
 void WebAppIntegrationTestDriver::ApplyRunOnOsLoginPolicyBlocked(Site site) {
-  if (!BeforeStateChangeAction(__FUNCTION__))
+  if (!BeforeStateChangeAction(__FUNCTION__)) {
     return;
+  }
   ApplyRunOnOsLoginPolicy(site, kBlocked);
   AfterStateChangeAction();
 }
 
 void WebAppIntegrationTestDriver::ApplyRunOnOsLoginPolicyRunWindowed(
     Site site) {
-  if (!BeforeStateChangeAction(__FUNCTION__))
+  if (!BeforeStateChangeAction(__FUNCTION__)) {
     return;
+  }
   ApplyRunOnOsLoginPolicy(site, kRunWindowed);
   AfterStateChangeAction();
 }
 
 void WebAppIntegrationTestDriver::RemoveRunOnOsLoginPolicy(Site site) {
-  if (!BeforeStateChangeAction(__FUNCTION__))
+  if (!BeforeStateChangeAction(__FUNCTION__)) {
     return;
+  }
   GURL url = GetUrlForSite(site);
   {
     ScopedListPrefUpdate update_list(profile()->GetPrefs(),
@@ -1406,8 +1432,9 @@
 }
 
 void WebAppIntegrationTestDriver::LaunchFromChromeApps(Site site) {
-  if (!BeforeStateChangeAction(__FUNCTION__))
+  if (!BeforeStateChangeAction(__FUNCTION__)) {
     return;
+  }
   AppId app_id = GetAppIdBySiteMode(site);
   ASSERT_TRUE(provider()->registrar_unsafe().GetAppById(app_id))
       << "No app installed for site: " << static_cast<int>(site);
@@ -1428,8 +1455,9 @@
 }
 
 void WebAppIntegrationTestDriver::LaunchFromLaunchIcon(Site site) {
-  if (!BeforeStateChangeAction(__FUNCTION__))
+  if (!BeforeStateChangeAction(__FUNCTION__)) {
     return;
+  }
   base::AutoReset<bool> intent_picker_bubble_scope =
       IntentPickerBubbleView::SetAutoAcceptIntentPickerBubbleForTesting();
   AppId app_id = GetAppIdBySiteMode(site);
@@ -1463,8 +1491,9 @@
 }
 
 void WebAppIntegrationTestDriver::LaunchFromMenuOption(Site site) {
-  if (!BeforeStateChangeAction(__FUNCTION__))
+  if (!BeforeStateChangeAction(__FUNCTION__)) {
     return;
+  }
   AppId app_id = GetAppIdBySiteMode(site);
   ASSERT_TRUE(provider()->registrar_unsafe().GetAppById(app_id))
       << "No app installed for site: " << static_cast<int>(site);
@@ -1485,8 +1514,9 @@
 
 void WebAppIntegrationTestDriver::LaunchFromPlatformShortcut(Site site) {
 #if !BUILDFLAG(IS_CHROMEOS)
-  if (!BeforeStateChangeAction(__FUNCTION__))
+  if (!BeforeStateChangeAction(__FUNCTION__)) {
     return;
+  }
   AppId app_id = GetAppIdBySiteMode(site);
   ASSERT_TRUE(provider()->registrar_unsafe().GetAppById(app_id))
       << "No app installed for site: " << static_cast<int>(site);
@@ -1533,8 +1563,9 @@
 
 #if BUILDFLAG(IS_MAC)
 void WebAppIntegrationTestDriver::LaunchFromAppShimFallback(Site site) {
-  if (!BeforeStateChangeAction(__FUNCTION__))
+  if (!BeforeStateChangeAction(__FUNCTION__)) {
     return;
+  }
 
   AppId app_id = GetAppIdBySiteMode(site);
   ASSERT_TRUE(provider()->registrar_unsafe().GetAppById(app_id))
@@ -1574,8 +1605,9 @@
 
 void WebAppIntegrationTestDriver::OpenAppSettingsFromAppMenu(Site site) {
 #if !BUILDFLAG(IS_CHROMEOS)
-  if (!BeforeStateChangeAction(__FUNCTION__))
+  if (!BeforeStateChangeAction(__FUNCTION__)) {
     return;
+  }
   Browser* app_browser = GetAppBrowserForSite(site);
   ASSERT_TRUE(app_browser);
 
@@ -1607,8 +1639,9 @@
 
 void WebAppIntegrationTestDriver::OpenAppSettingsFromChromeApps(Site site) {
 #if !BUILDFLAG(IS_CHROMEOS)
-  if (!BeforeStateChangeAction(__FUNCTION__))
+  if (!BeforeStateChangeAction(__FUNCTION__)) {
     return;
+  }
   AppId app_id = GetAppIdBySiteMode(site);
   ASSERT_TRUE(provider()->registrar_unsafe().GetAppById(app_id))
       << "No app installed for site: " << static_cast<int>(site);
@@ -1635,8 +1668,9 @@
 
 void WebAppIntegrationTestDriver::CreateShortcutsFromList(Site site) {
 #if !BUILDFLAG(IS_CHROMEOS)
-  if (!BeforeStateChangeAction(__FUNCTION__))
+  if (!BeforeStateChangeAction(__FUNCTION__)) {
     return;
+  }
   AppId app_id = GetAppIdBySiteMode(site);
   ASSERT_TRUE(provider()->registrar_unsafe().GetAppById(app_id))
       << "No app installed for site: " << static_cast<int>(site);
@@ -1669,10 +1703,12 @@
 }
 
 void WebAppIntegrationTestDriver::DeletePlatformShortcut(Site site) {
-  if (!before_state_change_action_state_ && !after_state_change_action_state_)
+  if (!before_state_change_action_state_ && !after_state_change_action_state_) {
     return;
-  if (!BeforeStateChangeAction(__FUNCTION__))
+  }
+  if (!BeforeStateChangeAction(__FUNCTION__)) {
     return;
+  }
   base::ScopedAllowBlockingForTesting allow_blocking;
   AppId app_id = GetAppIdBySiteMode(site);
   std::string app_name = provider()->registrar_unsafe().GetAppShortName(app_id);
@@ -1715,13 +1751,15 @@
 base::FilePath WebAppIntegrationTestDriver::GetResourceFile(
     base::FilePath::StringPieceType relative_path) {
   base::FilePath base_dir;
-  if (!base::PathService::Get(chrome::DIR_TEST_DATA, &base_dir))
+  if (!base::PathService::Get(chrome::DIR_TEST_DATA, &base_dir)) {
     return base::FilePath();
+  }
   base::FilePath full_path = base_dir.Append(relative_path);
   {
     base::ScopedAllowBlockingForTesting scoped_allow_blocking;
-    if (!PathExists(full_path))
+    if (!PathExists(full_path)) {
       return base::FilePath();
+    }
   }
   return full_path;
 }
@@ -1757,15 +1795,17 @@
 }
 
 void WebAppIntegrationTestDriver::NavigateBrowser(Site site) {
-  if (!BeforeStateChangeAction(__FUNCTION__))
+  if (!BeforeStateChangeAction(__FUNCTION__)) {
     return;
+  }
   NavigateTabbedBrowserToSite(GetInScopeURL(site), NavigationMode::kCurrentTab);
   AfterStateChangeAction();
 }
 
 void WebAppIntegrationTestDriver::NavigatePwa(Site pwa, Site to) {
-  if (!BeforeStateChangeAction(__FUNCTION__))
+  if (!BeforeStateChangeAction(__FUNCTION__)) {
     return;
+  }
   app_browser_ = GetAppBrowserForSite(pwa);
 
   content::TestNavigationObserver url_observer(GetUrlForSite(to));
@@ -1777,8 +1817,9 @@
 }
 
 void WebAppIntegrationTestDriver::NavigateNotfoundUrl() {
-  if (!BeforeStateChangeAction(__FUNCTION__))
+  if (!BeforeStateChangeAction(__FUNCTION__)) {
     return;
+  }
   NavigateTabbedBrowserToSite(
       delegate_->EmbeddedTestServer()->GetURL("/non-existant/index.html"),
       NavigationMode::kCurrentTab);
@@ -1788,8 +1829,9 @@
 void WebAppIntegrationTestDriver::ManifestUpdateIcon(
     Site site,
     UpdateDialogResponse response) {
-  if (!BeforeStateChangeAction(__FUNCTION__))
+  if (!BeforeStateChangeAction(__FUNCTION__)) {
     return;
+  }
   ASSERT_EQ(Site::kStandalone, site)
       << "Only site mode of 'Standalone' is supported";
 
@@ -1818,8 +1860,9 @@
     Site site,
     Title title,
     UpdateDialogResponse response) {
-  if (!BeforeStateChangeAction(__FUNCTION__))
+  if (!BeforeStateChangeAction(__FUNCTION__)) {
     return;
+  }
   ASSERT_EQ(Site::kStandalone, site)
       << "Only site mode of 'Standalone' is supported";
   ASSERT_EQ(Title::kStandaloneUpdated, title)
@@ -1840,8 +1883,9 @@
 
 void WebAppIntegrationTestDriver::ManifestUpdateDisplay(Site site,
                                                         Display display) {
-  if (!BeforeStateChangeAction(__FUNCTION__))
+  if (!BeforeStateChangeAction(__FUNCTION__)) {
     return;
+  }
 
   std::string relative_url_path = GetSiteConfiguration(site).relative_url;
   std::string manifest_url_param =
@@ -1854,8 +1898,9 @@
 }
 
 void WebAppIntegrationTestDriver::ManifestUpdateScopeTo(Site app, Site scope) {
-  if (!BeforeStateChangeAction(__FUNCTION__))
+  if (!BeforeStateChangeAction(__FUNCTION__)) {
     return;
+  }
   // The `scope_mode` would be changing the scope set in the manifest file. For
   // simplicity, right now only Standalone is supported, so that is just
   // hardcoded in manifest_scope_Standalone.json, which is specified in the URL.
@@ -1868,8 +1913,9 @@
 }
 
 void WebAppIntegrationTestDriver::OpenInChrome() {
-  if (!BeforeStateChangeAction(__FUNCTION__))
+  if (!BeforeStateChangeAction(__FUNCTION__)) {
     return;
+  }
   ASSERT_TRUE(IsBrowserOpen(app_browser())) << "No current app browser.";
   AppId app_id = app_browser()->app_controller()->app_id();
   GURL app_url = GetCurrentTab(app_browser())->GetURL();
@@ -1883,8 +1929,9 @@
 }
 
 void WebAppIntegrationTestDriver::SetOpenInTab(Site site) {
-  if (!BeforeStateChangeAction(__FUNCTION__))
+  if (!BeforeStateChangeAction(__FUNCTION__)) {
     return;
+  }
   AppId app_id = GetAppIdBySiteMode(site);
   ASSERT_TRUE(provider()->registrar_unsafe().GetAppById(app_id))
       << "No app installed for site: " << static_cast<int>(site);
@@ -1904,8 +1951,9 @@
 }
 
 void WebAppIntegrationTestDriver::SetOpenInWindow(Site site) {
-  if (!BeforeStateChangeAction(__FUNCTION__))
+  if (!BeforeStateChangeAction(__FUNCTION__)) {
     return;
+  }
   AppId app_id = GetAppIdBySiteMode(site);
   ASSERT_TRUE(provider()->registrar_unsafe().GetAppById(app_id))
       << "No app installed for site: " << static_cast<int>(site);
@@ -1939,8 +1987,9 @@
 }
 
 void WebAppIntegrationTestDriver::SwitchProfileClients(ProfileClient client) {
-  if (!BeforeStateChangeAction(__FUNCTION__))
+  if (!BeforeStateChangeAction(__FUNCTION__)) {
     return;
+  }
   DCHECK(active_profile_);
   active_profile_ = delegate_->GetProfileClient(client);
   DCHECK(active_profile_)
@@ -1951,8 +2000,9 @@
 
 void WebAppIntegrationTestDriver::SwitchActiveProfile(
     ProfileName profile_name) {
-  if (!BeforeStateChangeAction(__FUNCTION__))
+  if (!BeforeStateChangeAction(__FUNCTION__)) {
     return;
+  }
   const char* profile_name_str = nullptr;
   switch (profile_name) {
     case ProfileName::kDefault:
@@ -1978,22 +2028,25 @@
 }
 
 void WebAppIntegrationTestDriver::SyncTurnOff() {
-  if (!BeforeStateChangeAction(__FUNCTION__))
+  if (!BeforeStateChangeAction(__FUNCTION__)) {
     return;
+  }
   delegate_->SyncTurnOff();
   AfterStateChangeAction();
 }
 
 void WebAppIntegrationTestDriver::SyncTurnOn() {
-  if (!BeforeStateChangeAction(__FUNCTION__))
+  if (!BeforeStateChangeAction(__FUNCTION__)) {
     return;
+  }
   delegate_->SyncTurnOn();
   AfterStateChangeAction();
 }
 
 void WebAppIntegrationTestDriver::UninstallFromList(Site site) {
-  if (!BeforeStateChangeAction(__FUNCTION__))
+  if (!BeforeStateChangeAction(__FUNCTION__)) {
     return;
+  }
   AppId app_id = GetAppIdBySiteMode(site);
   ASSERT_TRUE(provider()->registrar_unsafe().GetAppById(app_id))
       << "No app installed for site: " << static_cast<int>(site);
@@ -2040,8 +2093,9 @@
 
 void WebAppIntegrationTestDriver::UninstallFromAppSettings(Site site) {
 #if !BUILDFLAG(IS_CHROMEOS)
-  if (!BeforeStateChangeAction(__FUNCTION__))
+  if (!BeforeStateChangeAction(__FUNCTION__)) {
     return;
+  }
   AppId app_id = GetAppIdBySiteMode(site);
   ASSERT_TRUE(provider()->registrar_unsafe().GetAppById(app_id))
       << "No app installed for site: " << static_cast<int>(site);
@@ -2077,8 +2131,9 @@
 }
 
 void WebAppIntegrationTestDriver::UninstallFromMenu(Site site) {
-  if (!BeforeStateChangeAction(__FUNCTION__))
+  if (!BeforeStateChangeAction(__FUNCTION__)) {
     return;
+  }
   AppId app_id = GetAppIdBySiteMode(site);
   ASSERT_TRUE(provider()->registrar_unsafe().GetAppById(app_id))
       << "No app installed for site: " << static_cast<int>(site);
@@ -2111,8 +2166,9 @@
 }
 
 void WebAppIntegrationTestDriver::UninstallPolicyApp(Site site) {
-  if (!BeforeStateChangeAction(__FUNCTION__))
+  if (!BeforeStateChangeAction(__FUNCTION__)) {
     return;
+  }
   GURL url = GetUrlForSite(site);
   auto policy_app = GetAppBySiteMode(before_state_change_action_state_.get(),
                                      profile(), site);
@@ -2131,8 +2187,9 @@
   // so this will listen for the removal of the policy install source.
   provider()->install_finalizer().SetRemoveManagementTypeCallbackForTesting(
       base::BindLambdaForTesting([&](const AppId& app_id) {
-        if (policy_app->id == app_id)
+        if (policy_app->id == app_id) {
           run_loop.Quit();
+        }
       }));
   {
     ScopedListPrefUpdate update(profile()->GetPrefs(),
@@ -2147,16 +2204,18 @@
   const WebApp* app = provider()->registrar_unsafe().GetAppById(policy_app->id);
   // If the app was fully uninstalled, wait for the change to propagate through
   // App Service.
-  if (app == nullptr)
+  if (app == nullptr) {
     uninstall_waiter.Wait();
+  }
   site_remember_deny_open_file_.erase(site);
   AfterStateChangeAction();
 }
 
 void WebAppIntegrationTestDriver::UninstallFromOs(Site site) {
 #if BUILDFLAG(IS_WIN)
-  if (!BeforeStateChangeAction(__FUNCTION__))
+  if (!BeforeStateChangeAction(__FUNCTION__)) {
     return;
+  }
   AppId app_id = GetAppIdBySiteMode(site);
   ASSERT_TRUE(provider()->registrar_unsafe().GetAppById(app_id))
       << "No app installed for site: " << static_cast<int>(site);
@@ -2182,8 +2241,9 @@
 
 #if BUILDFLAG(IS_MAC)
 void WebAppIntegrationTestDriver::CorruptAppShim(Site site) {
-  if (!BeforeStateChangeAction(__FUNCTION__))
+  if (!BeforeStateChangeAction(__FUNCTION__)) {
     return;
+  }
   base::ScopedAllowBlockingForTesting allow_blocking;
   AppId app_id = GetAppIdBySiteMode(site);
   std::string app_name = GetSiteConfiguration(site).app_name;
@@ -2199,8 +2259,9 @@
 #endif
 
 void WebAppIntegrationTestDriver::CheckAppListEmpty() {
-  if (!BeforeStateCheckAction(__FUNCTION__))
+  if (!BeforeStateCheckAction(__FUNCTION__)) {
     return;
+  }
   absl::optional<ProfileState> state =
       GetStateForProfile(after_state_change_action_state_.get(), profile());
   ASSERT_TRUE(state.has_value());
@@ -2244,8 +2305,9 @@
 }
 
 void WebAppIntegrationTestDriver::CheckAppInListNotLocallyInstalled(Site site) {
-  if (!BeforeStateCheckAction(__FUNCTION__))
+  if (!BeforeStateCheckAction(__FUNCTION__)) {
     return;
+  }
   // Note: This is a partially supported action.
   absl::optional<AppState> app_state =
       GetAppBySiteMode(after_state_change_action_state_.get(), profile(), site);
@@ -2255,8 +2317,9 @@
 }
 
 void WebAppIntegrationTestDriver::CheckAppInListTabbed(Site site) {
-  if (!BeforeStateCheckAction(__FUNCTION__))
+  if (!BeforeStateCheckAction(__FUNCTION__)) {
     return;
+  }
   // Note: This is a partially supported action.
   absl::optional<AppState> app_state =
       GetAppBySiteMode(after_state_change_action_state_.get(), profile(), site);
@@ -2266,8 +2329,9 @@
 }
 
 void WebAppIntegrationTestDriver::CheckAppInListWindowed(Site site) {
-  if (!BeforeStateCheckAction(__FUNCTION__))
+  if (!BeforeStateCheckAction(__FUNCTION__)) {
     return;
+  }
   // Note: This is a partially supported action.
   absl::optional<AppState> app_state =
       GetAppBySiteMode(after_state_change_action_state_.get(), profile(), site);
@@ -2277,8 +2341,9 @@
 }
 
 void WebAppIntegrationTestDriver::CheckAppNavigation(Site site) {
-  if (!BeforeStateCheckAction(__FUNCTION__))
+  if (!BeforeStateCheckAction(__FUNCTION__)) {
     return;
+  }
   ASSERT_TRUE(app_browser());
   GURL url =
       app_browser()->tab_strip_model()->GetActiveWebContents()->GetVisibleURL();
@@ -2287,8 +2352,9 @@
 }
 
 void WebAppIntegrationTestDriver::CheckAppNavigationIsStartUrl() {
-  if (!BeforeStateCheckAction(__FUNCTION__))
+  if (!BeforeStateCheckAction(__FUNCTION__)) {
     return;
+  }
   ASSERT_FALSE(active_app_id_.empty());
   ASSERT_TRUE(app_browser());
   GURL url =
@@ -2298,8 +2364,9 @@
 }
 
 void WebAppIntegrationTestDriver::CheckBrowserNavigation(Site site) {
-  if (!BeforeStateCheckAction(__FUNCTION__))
+  if (!BeforeStateCheckAction(__FUNCTION__)) {
     return;
+  }
   ASSERT_TRUE(browser());
   GURL url = browser()->tab_strip_model()->GetActiveWebContents()->GetURL();
   EXPECT_EQ(url, GetUrlForSite(site));
@@ -2309,8 +2376,9 @@
 void WebAppIntegrationTestDriver::CheckBrowserNavigationIsAppSettings(
     Site site) {
 #if !BUILDFLAG(IS_CHROMEOS)
-  if (!BeforeStateCheckAction(__FUNCTION__))
+  if (!BeforeStateCheckAction(__FUNCTION__)) {
     return;
+  }
   AppId app_id = GetAppIdBySiteMode(site);
   ASSERT_TRUE(provider()->registrar_unsafe().GetAppById(app_id))
       << "No app installed for site: " << static_cast<int>(site);
@@ -2326,8 +2394,9 @@
 }
 
 void WebAppIntegrationTestDriver::CheckAppNotInList(Site site) {
-  if (!BeforeStateCheckAction(__FUNCTION__))
+  if (!BeforeStateCheckAction(__FUNCTION__)) {
     return;
+  }
   absl::optional<AppState> app_state =
       GetAppBySiteMode(after_state_change_action_state_.get(), profile(), site);
   EXPECT_FALSE(app_state.has_value());
@@ -2335,8 +2404,9 @@
 }
 
 void WebAppIntegrationTestDriver::CheckPlatformShortcutAndIcon(Site site) {
-  if (!BeforeStateCheckAction(__FUNCTION__))
+  if (!BeforeStateCheckAction(__FUNCTION__)) {
     return;
+  }
   absl::optional<AppState> app_state =
       GetAppBySiteMode(after_state_change_action_state_.get(), profile(), site);
   ASSERT_TRUE(app_state);
@@ -2347,10 +2417,12 @@
 void WebAppIntegrationTestDriver::CheckPlatformShortcutNotExists(Site site) {
   // This is to handle if the check happens at the very beginning of the test,
   // when no web app is installed (or any other action has happened yet).
-  if (!before_state_change_action_state_ && !after_state_change_action_state_)
+  if (!before_state_change_action_state_ && !after_state_change_action_state_) {
     return;
-  if (!BeforeStateCheckAction(__FUNCTION__))
+  }
+  if (!BeforeStateCheckAction(__FUNCTION__)) {
     return;
+  }
   absl::optional<AppState> app_state =
       GetAppBySiteMode(after_state_change_action_state_.get(), profile(), site);
   if (!app_state) {
@@ -2372,8 +2444,9 @@
 }
 
 void WebAppIntegrationTestDriver::CheckAppIcon(Site site, Color color) {
-  if (!BeforeStateCheckAction(__FUNCTION__))
+  if (!BeforeStateCheckAction(__FUNCTION__)) {
     return;
+  }
   absl::optional<AppState> app_state =
       GetAppBySiteMode(after_state_change_action_state_.get(), profile(), site);
   ASSERT_TRUE(app_state);
@@ -2430,8 +2503,9 @@
 }
 
 void WebAppIntegrationTestDriver::CheckAppTitle(Site site, Title title) {
-  if (!BeforeStateCheckAction(__FUNCTION__))
+  if (!BeforeStateCheckAction(__FUNCTION__)) {
     return;
+  }
   absl::optional<AppState> app_state =
       GetAppBySiteMode(after_state_change_action_state_.get(), profile(), site);
   ASSERT_TRUE(app_state);
@@ -2539,8 +2613,9 @@
 void WebAppIntegrationTestDriver::CheckInstallIconShown() {
   // Currently this function does not support tests that check install icons
   // for sites that have a manifest but no service worker.
-  if (!BeforeStateCheckAction(__FUNCTION__))
+  if (!BeforeStateCheckAction(__FUNCTION__)) {
     return;
+  }
   auto* app_banner_manager =
       webapps::TestAppBannerManagerDesktop::FromWebContents(
           GetCurrentTab(browser()));
@@ -2552,8 +2627,9 @@
 void WebAppIntegrationTestDriver::CheckInstallIconNotShown() {
   // Currently this function does not support tests that check install icons
   // for sites that have a manifest but no service worker.
-  if (!BeforeStateCheckAction(__FUNCTION__))
+  if (!BeforeStateCheckAction(__FUNCTION__)) {
     return;
+  }
   auto* app_banner_manager =
       webapps::TestAppBannerManagerDesktop::FromWebContents(
           GetCurrentTab(browser()));
@@ -2563,8 +2639,9 @@
 }
 
 void WebAppIntegrationTestDriver::CheckLaunchIconShown() {
-  if (!BeforeStateCheckAction(__FUNCTION__))
+  if (!BeforeStateCheckAction(__FUNCTION__)) {
     return;
+  }
   absl::optional<BrowserState> browser_state = GetStateForBrowser(
       after_state_change_action_state_.get(), profile(), browser());
   ASSERT_TRUE(browser_state.has_value());
@@ -2573,8 +2650,9 @@
 }
 
 void WebAppIntegrationTestDriver::CheckLaunchIconNotShown() {
-  if (!BeforeStateCheckAction(__FUNCTION__))
+  if (!BeforeStateCheckAction(__FUNCTION__)) {
     return;
+  }
   absl::optional<BrowserState> browser_state = GetStateForBrowser(
       after_state_change_action_state_.get(), profile(), browser());
   ASSERT_TRUE(browser_state.has_value());
@@ -2583,8 +2661,9 @@
 }
 
 void WebAppIntegrationTestDriver::CheckTabCreated() {
-  if (!BeforeStateCheckAction(__FUNCTION__))
+  if (!BeforeStateCheckAction(__FUNCTION__)) {
     return;
+  }
   DCHECK(before_state_change_action_state_);
   absl::optional<BrowserState> most_recent_browser_state = GetStateForBrowser(
       after_state_change_action_state_.get(), profile(), browser());
@@ -2611,8 +2690,9 @@
 }
 
 void WebAppIntegrationTestDriver::CheckTabNotCreated() {
-  if (!BeforeStateCheckAction(__FUNCTION__))
+  if (!BeforeStateCheckAction(__FUNCTION__)) {
     return;
+  }
   DCHECK(before_state_change_action_state_);
   absl::optional<BrowserState> most_recent_browser_state = GetStateForBrowser(
       after_state_change_action_state_.get(), profile(), browser());
@@ -2626,8 +2706,9 @@
 }
 
 void WebAppIntegrationTestDriver::CheckCustomToolbar() {
-  if (!BeforeStateCheckAction(__FUNCTION__))
+  if (!BeforeStateCheckAction(__FUNCTION__)) {
     return;
+  }
   ASSERT_TRUE(app_browser());
   EXPECT_TRUE(app_browser()->app_controller()->ShouldShowCustomTabBar());
   BrowserView* app_view = BrowserView::GetBrowserViewForBrowser(app_browser());
@@ -2639,8 +2720,9 @@
 }
 
 void WebAppIntegrationTestDriver::CheckNoToolbar() {
-  if (!BeforeStateCheckAction(__FUNCTION__))
+  if (!BeforeStateCheckAction(__FUNCTION__)) {
     return;
+  }
   ASSERT_TRUE(app_browser());
   EXPECT_FALSE(app_browser()->app_controller()->ShouldShowCustomTabBar());
   BrowserView* app_view = BrowserView::GetBrowserViewForBrowser(app_browser());
@@ -2649,8 +2731,9 @@
 }
 
 void WebAppIntegrationTestDriver::CheckRunOnOsLoginEnabled(Site site) {
-  if (!BeforeStateCheckAction(__FUNCTION__))
+  if (!BeforeStateCheckAction(__FUNCTION__)) {
     return;
+  }
   absl::optional<AppState> app_state =
       GetAppBySiteMode(after_state_change_action_state_.get(), profile(), site);
   ASSERT_TRUE(app_state);
@@ -2674,8 +2757,9 @@
 }
 
 void WebAppIntegrationTestDriver::CheckRunOnOsLoginDisabled(Site site) {
-  if (!BeforeStateCheckAction(__FUNCTION__))
+  if (!BeforeStateCheckAction(__FUNCTION__)) {
     return;
+  }
   absl::optional<AppState> app_state =
       GetAppBySiteMode(after_state_change_action_state_.get(), profile(), site);
   ASSERT_TRUE(app_state);
@@ -2691,8 +2775,9 @@
     Site site,
     FileExtension file_extension) {
 #if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC) || BUILDFLAG(IS_LINUX)
-  if (!BeforeStateCheckAction(__FUNCTION__))
+  if (!BeforeStateCheckAction(__FUNCTION__)) {
     return;
+  }
   ASSERT_TRUE(IsFileHandledBySite(site, file_extension));
   AfterStateCheckAction();
 #endif
@@ -2702,8 +2787,9 @@
     Site site,
     FileExtension file_extension) {
 #if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC) || BUILDFLAG(IS_LINUX)
-  if (!BeforeStateCheckAction(__FUNCTION__))
+  if (!BeforeStateCheckAction(__FUNCTION__)) {
     return;
+  }
   ASSERT_FALSE(IsFileHandledBySite(site, file_extension));
   AfterStateCheckAction();
 #endif
@@ -2711,8 +2797,9 @@
 
 void WebAppIntegrationTestDriver::CheckUserCannotSetRunOnOsLogin(Site site) {
 #if !BUILDFLAG(IS_CHROMEOS)
-  if (!BeforeStateCheckAction(__FUNCTION__))
+  if (!BeforeStateCheckAction(__FUNCTION__)) {
     return;
+  }
   absl::optional<AppState> app_state =
       GetAppBySiteMode(after_state_change_action_state_.get(), profile(), site);
   ASSERT_TRUE(app_state);
@@ -2742,8 +2829,9 @@
 
 void WebAppIntegrationTestDriver::CheckUserDisplayModeInternal(
     mojom::UserDisplayMode user_display_mode) {
-  if (!BeforeStateCheckAction(__FUNCTION__))
+  if (!BeforeStateCheckAction(__FUNCTION__)) {
     return;
+  }
   absl::optional<AppState> app_state = GetStateForAppId(
       after_state_change_action_state_.get(), profile(), active_app_id_);
   ASSERT_TRUE(app_state.has_value());
@@ -2752,8 +2840,9 @@
 }
 
 void WebAppIntegrationTestDriver::CheckWindowClosed() {
-  if (!BeforeStateCheckAction(__FUNCTION__))
+  if (!BeforeStateCheckAction(__FUNCTION__)) {
     return;
+  }
   DCHECK(before_state_change_action_state_);
   absl::optional<ProfileState> after_action_profile =
       GetStateForProfile(after_state_change_action_state_.get(), profile());
@@ -2767,8 +2856,9 @@
 }
 
 void WebAppIntegrationTestDriver::CheckWindowCreated() {
-  if (!BeforeStateCheckAction(__FUNCTION__))
+  if (!BeforeStateCheckAction(__FUNCTION__)) {
     return;
+  }
   DCHECK(before_state_change_action_state_);
   absl::optional<ProfileState> after_action_profile =
       GetStateForProfile(after_state_change_action_state_.get(), profile());
@@ -2785,8 +2875,9 @@
 }
 
 void WebAppIntegrationTestDriver::CheckWindowNotCreated() {
-  if (!BeforeStateCheckAction(__FUNCTION__))
+  if (!BeforeStateCheckAction(__FUNCTION__)) {
     return;
+  }
   DCHECK(before_state_change_action_state_);
   absl::optional<ProfileState> after_action_profile =
       GetStateForProfile(after_state_change_action_state_.get(), profile());
@@ -2805,8 +2896,9 @@
 void WebAppIntegrationTestDriver::CheckWindowControlsOverlayToggle(
     Site site,
     IsShown is_shown) {
-  if (!BeforeStateCheckAction(__FUNCTION__))
+  if (!BeforeStateCheckAction(__FUNCTION__)) {
     return;
+  }
   ASSERT_TRUE(app_browser());
   EXPECT_TRUE(AppBrowserController::IsForWebApp(app_browser(),
                                                 GetAppIdBySiteMode(site)));
@@ -2826,8 +2918,9 @@
 
 void WebAppIntegrationTestDriver::CheckWindowControlsOverlay(Site site,
                                                              IsOn is_on) {
-  if (!BeforeStateCheckAction(__FUNCTION__))
+  if (!BeforeStateCheckAction(__FUNCTION__)) {
     return;
+  }
   ASSERT_TRUE(app_browser());
   EXPECT_TRUE(AppBrowserController::IsForWebApp(app_browser(),
                                                 GetAppIdBySiteMode(site)));
@@ -2837,8 +2930,9 @@
 }
 
 void WebAppIntegrationTestDriver::CheckWindowDisplayMinimal() {
-  if (!BeforeStateCheckAction(__FUNCTION__))
+  if (!BeforeStateCheckAction(__FUNCTION__)) {
     return;
+  }
   DCHECK(app_browser());
   web_app::AppBrowserController* app_controller =
       app_browser()->app_controller();
@@ -2891,8 +2985,9 @@
 }
 
 void WebAppIntegrationTestDriver::CheckWindowDisplayStandalone() {
-  if (!BeforeStateCheckAction(__FUNCTION__))
+  if (!BeforeStateCheckAction(__FUNCTION__)) {
     return;
+  }
   DCHECK(app_browser());
 
   web_app::AppBrowserController* app_controller =
@@ -2989,16 +3084,18 @@
     // state. This is great, except for the manifest update edge case, which can
     // happen asynchronously outside of actions. In this case, re-grab the
     // snapshot after the update.
-    if (executing_action_level_ == 0 && after_state_change_action_state_)
+    if (executing_action_level_ == 0 && after_state_change_action_state_) {
       after_state_change_action_state_ = ConstructStateSnapshot();
+    }
   }
 }
 
 bool WebAppIntegrationTestDriver::BeforeStateChangeAction(
     const char* function) {
   DCHECK(!base::StartsWith(function, "Check"));
-  if (testing::Test::HasFatalFailure() && !in_tear_down_)
+  if (testing::Test::HasFatalFailure() && !in_tear_down_) {
     return false;
+  }
   LOG(INFO) << "BeforeStateChangeAction: "
             << std::string(executing_action_level_, ' ') << function;
   ++executing_action_level_;
@@ -3021,8 +3118,9 @@
   std::map<AppId, size_t> open_browsers_per_app;
   for (auto* profile : GetAllProfiles()) {
     auto* provider = GetProviderForProfile(profile);
-    if (!provider)
+    if (!provider) {
       continue;
+    }
     std::vector<AppId> app_ids = provider->registrar_unsafe().GetAppIds();
     for (auto& app_id : app_ids) {
       // Wait for any shims to finish connecting.
@@ -3042,8 +3140,9 @@
     }
   }
   for (const auto& [app_id, open_browsers] : open_browsers_per_app) {
-    if (open_browsers != 0)
+    if (open_browsers != 0) {
       continue;
+    }
     std::string app_name =
         provider()->registrar_unsafe().GetAppShortName(app_id);
     base::FilePath app_path = GetShortcutPath(
@@ -3052,8 +3151,9 @@
     WaitForShimToQuitForTesting(app_path, app_id);
   }
 #endif
-  if (delegate_->IsSyncTest())
+  if (delegate_->IsSyncTest()) {
     delegate_->AwaitWebAppQuiescence();
+  }
   FlushShortcutTasks();
   provider()->command_manager().AwaitAllCommandsCompleteForTesting();
   AwaitManifestSystemIdle();
@@ -3073,8 +3173,9 @@
 
 bool WebAppIntegrationTestDriver::BeforeStateCheckAction(const char* function) {
   DCHECK(base::StartsWith(function, "Check"));
-  if (testing::Test::HasFatalFailure() && !in_tear_down_)
+  if (testing::Test::HasFatalFailure() && !in_tear_down_) {
     return false;
+  }
   ++executing_action_level_;
   provider()->command_manager().AwaitAllCommandsCompleteForTesting();
   LOG(INFO) << "BeforeStateCheckAction: "
@@ -3086,14 +3187,16 @@
 void WebAppIntegrationTestDriver::AfterStateCheckAction() {
   DCHECK(executing_action_level_ > 0);
   --executing_action_level_;
-  if (!after_state_change_action_state_)
+  if (!after_state_change_action_state_) {
     return;
+  }
   DCHECK_EQ(*after_state_change_action_state_, *ConstructStateSnapshot());
 }
 
 void WebAppIntegrationTestDriver::AwaitManifestSystemIdle() {
-  if (!is_performing_manifest_update_)
+  if (!is_performing_manifest_update_) {
     return;
+  }
 
   // Wait till pending manifest update processes have finished loading the page
   // to start the manifest update.
@@ -3197,8 +3300,9 @@
         launch_icon_shown = intent_picker_view()->GetVisible();
       }
       AppId app_id;
-      if (AppBrowserController::IsWebApp(browser))
+      if (AppBrowserController::IsWebApp(browser)) {
         app_id = browser->app_controller()->app_id();
+      }
 
       browser_state.emplace(
           browser, BrowserState(browser, tab_state_map, active_tab, app_id,
@@ -3231,11 +3335,11 @@
             IsShortcutAndIconCreated(profile, registrar.GetAppShortName(app_id),
                                      app_id));
 #if !BUILDFLAG(IS_CHROMEOS)
-      if (registrar.IsLocallyInstalled(app_id)) {
-        CheckAppSettingsAppState(profile->GetOriginalProfile(), state);
-      }
+        if (registrar.IsLocallyInstalled(app_id)) {
+          CheckAppSettingsAppState(profile->GetOriginalProfile(), state);
+        }
 #endif
-      app_state.emplace(app_id, state);
+        app_state.emplace(app_id, state);
       }
     }
 
@@ -3321,8 +3425,9 @@
   // so this will listen for the removal of the policy install source.
   provider()->install_finalizer().SetRemoveManagementTypeCallbackForTesting(
       base::BindLambdaForTesting([&](const AppId& app_id) {
-        if (id == app_id)
+        if (id == app_id) {
           run_loop.Quit();
+        }
       }));
 
   const WebApp* web_app = provider()->registrar_unsafe().GetAppById(id);
@@ -3350,10 +3455,12 @@
   const WebApp* app = provider()->registrar_unsafe().GetAppById(id);
   // If the app was fully uninstalled, wait for the change to propagate through
   // App Service.
-  if (app == nullptr)
+  if (app == nullptr) {
     app_registration_waiter.Await();
-  if (app == nullptr && active_app_id_ == id)
+  }
+  if (app == nullptr && active_app_id_ == id) {
     active_app_id_.clear();
+  }
 }
 
 void WebAppIntegrationTestDriver::ForceUpdateManifestContents(
@@ -3411,11 +3518,13 @@
   auto profile_state = GetStateForProfile(state, profile());
   DCHECK(profile_state);
   for (const auto& browser_state_pair : profile_state->browsers) {
-    if (browser_state_pair.second.app_id == app_state->id)
+    if (browser_state_pair.second.app_id == app_state->id) {
       return browser_state_pair.second.browser;
+    }
   }
-  if (!launch_if_not_open)
+  if (!launch_if_not_open) {
     return nullptr;
+  }
   Browser* browser = LaunchWebAppBrowserAndWait(profile(), app_state->id);
   provider()->manifest_update_manager().ResetManifestThrottleForTesting(
       GetAppIdBySiteMode(site));
@@ -3635,8 +3744,9 @@
 }
 
 Profile* WebAppIntegrationTestDriver::profile() {
-  if (!active_profile_)
+  if (!active_profile_) {
     active_profile_ = delegate_->GetDefaultProfile();
+  }
   return active_profile_;
 }
 
diff --git a/chrome/browser/ui/web_applications/web_app_browsertest.cc b/chrome/browser/ui/web_applications/web_app_browsertest.cc
index 6760434..04c902e0 100644
--- a/chrome/browser/ui/web_applications/web_app_browsertest.cc
+++ b/chrome/browser/ui/web_applications/web_app_browsertest.cc
@@ -58,10 +58,12 @@
 #include "chrome/browser/ui/web_applications/web_app_menu_model.h"
 #include "chrome/browser/ui/web_applications/web_app_ui_utils.h"
 #include "chrome/browser/ui/window_sizer/window_sizer.h"
+#include "chrome/browser/web_applications/commands/run_on_os_login_command.h"
 #include "chrome/browser/web_applications/external_install_options.h"
 #include "chrome/browser/web_applications/mojom/user_display_mode.mojom.h"
 #include "chrome/browser/web_applications/os_integration/os_integration_test_override.h"
 #include "chrome/browser/web_applications/os_integration/web_app_shortcut.h"
+#include "chrome/browser/web_applications/test/web_app_install_test_utils.h"
 #include "chrome/browser/web_applications/test/web_app_test_observers.h"
 #include "chrome/browser/web_applications/test/web_app_test_utils.h"
 #include "chrome/browser/web_applications/web_app.h"
@@ -1583,7 +1585,41 @@
           provider->registrar_unsafe().GetAppShortName(app_id));
   EXPECT_FALSE(base::PathExists(desktop_shortcut_path));
 #endif
-}  // namespace web_app
+}
+
+IN_PROC_BROWSER_TEST_F(WebAppBrowserTest, RunOnOsLoginMetrics) {
+  os_hooks_suppress_.reset();
+  GURL pwa_url("https://test-app.com");
+
+  base::ScopedAllowBlockingForTesting allow_blocking;
+
+  std::unique_ptr<OsIntegrationTestOverride::BlockingRegistration>
+      registration = OsIntegrationTestOverride::OverrideForTesting();
+
+  auto* provider = WebAppProvider::GetForTest(profile());
+  const AppId& app_id = InstallPWA(pwa_url);
+
+  ASSERT_TRUE(provider->registrar_unsafe().IsInstalled(app_id));
+
+  base::HistogramTester tester;
+  base::RunLoop run_loop;
+  provider->scheduler().SetRunOnOsLoginMode(
+      app_id, RunOnOsLoginMode::kWindowed, base::BindLambdaForTesting([&]() {
+        EXPECT_THAT(
+            tester.GetAllSamples("WebApp.RunOnOsLogin.Registration.Result"),
+            BucketsAre(base::Bucket(true, 1)));
+        run_loop.Quit();
+      }));
+  run_loop.Run();
+  EXPECT_TRUE(GetOsIntegrationTestOverride()->IsRunOnOsLoginEnabled(
+      profile(), app_id, provider->registrar_unsafe().GetAppShortName(app_id)));
+
+  test::UninstallAllWebApps(profile());
+  EXPECT_FALSE(GetOsIntegrationTestOverride()->IsRunOnOsLoginEnabled(
+      profile(), app_id, provider->registrar_unsafe().GetAppShortName(app_id)));
+  EXPECT_THAT(tester.GetAllSamples("WebApp.RunOnOsLogin.Unregistration.Result"),
+              BucketsAre(base::Bucket(true, 1)));
+}
 #endif
 
 // Tests that reparenting the last browser tab doesn't close the browser window.
diff --git a/chrome/browser/ui/webui/history_clusters/history_clusters_handler.cc b/chrome/browser/ui/webui/history_clusters/history_clusters_handler.cc
index fe58ec21f..38894cc 100644
--- a/chrome/browser/ui/webui/history_clusters/history_clusters_handler.cc
+++ b/chrome/browser/ui/webui/history_clusters/history_clusters_handler.cc
@@ -28,13 +28,13 @@
 #include "chrome/browser/ui/browser.h"
 #include "chrome/browser/ui/browser_commands.h"
 #include "chrome/browser/ui/browser_finder.h"
-#include "chrome/browser/ui/webui/history_clusters/history_cluster_type_utils.h"
 #include "chrome/common/pref_names.h"
 #include "components/history/core/browser/history_service.h"
 #include "components/history/core/browser/history_types.h"
 #include "components/history_clusters/core/cluster_metrics_utils.h"
 #include "components/history_clusters/core/config.h"
 #include "components/history_clusters/core/features.h"
+#include "components/history_clusters/core/history_cluster_type_utils.h"
 #include "components/history_clusters/core/history_clusters_prefs.h"
 #include "components/history_clusters/ui/query_clusters_state.h"
 #include "components/image_service/image_service.h"
@@ -144,8 +144,10 @@
     bool can_load_more,
     bool is_continuation) {
   std::vector<mojom::ClusterPtr> cluster_mojoms;
+  const TemplateURLService* template_url_service =
+      TemplateURLServiceFactory::GetForProfile(profile);
   for (const auto& cluster : clusters_batch) {
-    auto cluster_mojom = ClusterToMojom(profile, cluster);
+    auto cluster_mojom = ClusterToMojom(template_url_service, cluster);
     cluster_mojoms.emplace_back(std::move(cluster_mojom));
   }
 
diff --git a/chrome/browser/ui/webui/settings/ash/device_section.cc b/chrome/browser/ui/webui/settings/ash/device_section.cc
index 4ebc91a7..1cc87e5 100644
--- a/chrome/browser/ui/webui/settings/ash/device_section.cc
+++ b/chrome/browser/ui/webui/settings/ash/device_section.cc
@@ -834,6 +834,12 @@
       {"audioDeviceUsbLabel", IDS_SETTINGS_AUDIO_DEVICE_USB_LABEL},
       {"audioInputDeviceTitle", IDS_SETTINGS_AUDIO_INPUT_DEVICE_TITLE},
       {"audioInputGainTitle", IDS_SETTINGS_AUDIO_INPUT_GAIN_TITLE},
+      {"audioInputMuteButtonAriaLabelMuted",
+       IDS_SETTINGS_AUDIO_INPUT_MUTE_BUTTON_ARIA_LABEL_MUTED},
+      {"audioInputMuteButtonAriaLabelMutedByHardwareSwitch",
+       IDS_SETTINGS_AUDIO_INPUT_MUTE_BUTTON_ARIA_LABEL_MUTED_BY_HARDWARE_SWITCH},
+      {"audioInputMuteButtonAriaLabelNotMuted",
+       IDS_SETTINGS_AUDIO_INPUT_MUTE_BUTTON_ARIA_LABEL_NOT_MUTED},
       {"audioInputNoiseCancellationTitle",
        IDS_SETTINGS_AUDIO_INPUT_NOISE_CANCELLATION_TITLE},
       {"audioInputTitle", IDS_SETTINGS_AUDIO_INPUT_TITLE},
@@ -842,6 +848,10 @@
        IDS_SETTINGS_AUDIO_MUTED_EXTERNALLY_TOOLTIP},
       {"audioOutputDeviceTitle", IDS_SETTINGS_AUDIO_OUTPUT_DEVICE_TITLE},
       {"audioOutputTitle", IDS_SETTINGS_AUDIO_OUTPUT_TITLE},
+      {"audioOutputMuteButtonAriaLabelMuted",
+       IDS_SETTINGS_AUDIO_OUTPUT_MUTE_BUTTON_ARIA_LABEL_MUTED},
+      {"audioOutputMuteButtonAriaLabelNotMuted",
+       IDS_SETTINGS_AUDIO_OUTPUT_MUTE_BUTTON_ARIA_LABEL_NOT_MUTED},
       {"audioTitle", IDS_SETTINGS_AUDIO_TITLE},
       {"audioToggleToMuteTooltip", IDS_SETTINGS_AUDIO_TOGGLE_TO_MUTE_TOOLTIP},
       {"audioToggleToUnmuteTooltip",
diff --git a/chrome/browser/usb/usb_chooser_context_unittest.cc b/chrome/browser/usb/usb_chooser_context_unittest.cc
index 1a62387..8d0479e 100644
--- a/chrome/browser/usb/usb_chooser_context_unittest.cc
+++ b/chrome/browser/usb/usb_chooser_context_unittest.cc
@@ -6,12 +6,12 @@
 
 #include "base/containers/flat_map.h"
 #include "base/functional/callback_helpers.h"
-#include "base/json/json_reader.h"
 #include "base/no_destructor.h"
 #include "base/run_loop.h"
 #include "base/strings/utf_string_conversions.h"
 #include "base/test/bind.h"
 #include "base/test/test_future.h"
+#include "base/test/values_test_util.h"
 #include "base/values.h"
 #include "build/chromeos_buildflags.h"
 #include "chrome/browser/content_settings/host_content_settings_map_factory.h"
@@ -493,7 +493,7 @@
   prefs->SetManagedPref(prefs::kManagedDefaultWebUsbGuardSetting,
                         std::make_unique<base::Value>(CONTENT_SETTING_BLOCK));
   prefs->SetManagedPref(prefs::kManagedWebUsbAskForUrls,
-                        base::JSONReader::ReadDeprecated(R"(
+                        base::test::ParseJsonList(R"(
     [ "https://foo.origin" ]
   )"));
 
@@ -525,7 +525,7 @@
 
   auto* prefs = profile()->GetTestingPrefService();
   prefs->SetManagedPref(prefs::kManagedWebUsbBlockedForUrls,
-                        base::JSONReader::ReadDeprecated(R"(
+                        base::test::ParseJsonList(R"(
     [ "https://foo.origin" ]
   )"));
 
@@ -618,8 +618,8 @@
 
   ExpectNoPermissions(store, *specific_device_info);
 
-  profile()->GetPrefs()->Set(prefs::kManagedWebUsbAllowDevicesForUrls,
-                             *base::JSONReader::ReadDeprecated(kPolicySetting));
+  profile()->GetPrefs()->SetList(prefs::kManagedWebUsbAllowDevicesForUrls,
+                                 base::test::ParseJsonList(kPolicySetting));
 
   ExpectCorrectPermissions(store, kValidOrigins, kInvalidOrigins,
                            *specific_device_info);
@@ -640,8 +640,8 @@
 
   ExpectNoPermissions(store, *vendor_related_device_info);
 
-  profile()->GetPrefs()->Set(prefs::kManagedWebUsbAllowDevicesForUrls,
-                             *base::JSONReader::ReadDeprecated(kPolicySetting));
+  profile()->GetPrefs()->SetList(prefs::kManagedWebUsbAllowDevicesForUrls,
+                                 base::test::ParseJsonList(kPolicySetting));
 
   ExpectCorrectPermissions(store, kValidOrigins, kInvalidOrigins,
                            *vendor_related_device_info);
@@ -661,8 +661,8 @@
 
   ExpectNoPermissions(store, *unrelated_device_info);
 
-  profile()->GetPrefs()->Set(prefs::kManagedWebUsbAllowDevicesForUrls,
-                             *base::JSONReader::ReadDeprecated(kPolicySetting));
+  profile()->GetPrefs()->SetList(prefs::kManagedWebUsbAllowDevicesForUrls,
+                                 base::test::ParseJsonList(kPolicySetting));
 
   EXPECT_TRUE(store->HasDevicePermission(kCoolOrigin, *unrelated_device_info));
   for (const auto& origin_url : PolicyOrigins()) {
@@ -706,8 +706,8 @@
   EXPECT_FALSE(store->HasDevicePermission(kCoolOrigin, *specific_device_info));
   EXPECT_FALSE(store->HasDevicePermission(kCoolOrigin, *unrelated_device_info));
 
-  profile()->GetPrefs()->Set(prefs::kManagedWebUsbAllowDevicesForUrls,
-                             *base::JSONReader::ReadDeprecated(kPolicySetting));
+  profile()->GetPrefs()->SetList(prefs::kManagedWebUsbAllowDevicesForUrls,
+                                 base::test::ParseJsonList(kPolicySetting));
 
   EXPECT_TRUE(
       store->HasDevicePermission(kProductVendorOrigin, *specific_device_info));
@@ -753,9 +753,8 @@
   ExpectNoPermissions(user_store, *specific_device_info);
   ExpectNoPermissions(signin_store, *specific_device_info);
 
-  user_profile->GetPrefs()->Set(
-      prefs::kManagedWebUsbAllowDevicesForUrls,
-      *base::JSONReader::ReadDeprecated(kPolicySetting));
+  user_profile->GetPrefs()->SetList(prefs::kManagedWebUsbAllowDevicesForUrls,
+                                    base::test::ParseJsonList(kPolicySetting));
 
   ExpectCorrectPermissions(user_store, kValidOrigins, kInvalidOrigins,
                            *specific_device_info);
@@ -780,9 +779,9 @@
   ExpectNoPermissions(user_store, *specific_device_info);
   ExpectNoPermissions(signin_store, *specific_device_info);
 
-  signin_profile->GetPrefs()->Set(
+  signin_profile->GetPrefs()->SetList(
       prefs::kManagedWebUsbAllowDevicesForUrls,
-      *base::JSONReader::ReadDeprecated(kPolicySetting));
+      base::test::ParseJsonList(kPolicySetting));
 
   ExpectNoPermissions(user_store, *specific_device_info);
   ExpectCorrectPermissions(signin_store, kValidOrigins, kInvalidOrigins,
@@ -829,8 +828,8 @@
 
 TEST_F(UsbChooserContextTest, GetGrantedObjectsWithOnlyPolicyAllowedDevices) {
   auto* store = GetChooserContext(profile());
-  profile()->GetPrefs()->Set(prefs::kManagedWebUsbAllowDevicesForUrls,
-                             *base::JSONReader::ReadDeprecated(kPolicySetting));
+  profile()->GetPrefs()->SetList(prefs::kManagedWebUsbAllowDevicesForUrls,
+                                 base::test::ParseJsonList(kPolicySetting));
 
   const auto kVendorOrigin = url::Origin::Create(GURL(kVendorUrl));
   auto objects = store->GetGrantedObjects(kVendorOrigin);
@@ -847,8 +846,8 @@
 
 TEST_F(UsbChooserContextTest,
        GetGrantedObjectsWithUserAndPolicyAllowedDevices) {
-  profile()->GetPrefs()->Set(prefs::kManagedWebUsbAllowDevicesForUrls,
-                             *base::JSONReader::ReadDeprecated(kPolicySetting));
+  profile()->GetPrefs()->SetList(prefs::kManagedWebUsbAllowDevicesForUrls,
+                                 base::test::ParseJsonList(kPolicySetting));
 
   UsbDeviceInfoPtr persistent_device_info =
       device_manager_.CreateAndAddDevice(1000, 1, "Google", "Gizmo", "123ABC");
@@ -895,8 +894,8 @@
 
 TEST_F(UsbChooserContextTest,
        GetGrantedObjectsWithUserGrantedDeviceAllowedBySpecificDevicePolicy) {
-  profile()->GetPrefs()->Set(prefs::kManagedWebUsbAllowDevicesForUrls,
-                             *base::JSONReader::ReadDeprecated(kPolicySetting));
+  profile()->GetPrefs()->SetList(prefs::kManagedWebUsbAllowDevicesForUrls,
+                                 base::test::ParseJsonList(kPolicySetting));
 
   UsbDeviceInfoPtr persistent_device_info = device_manager_.CreateAndAddDevice(
       6353, 5678, "Google", "Gizmo", "123ABC");
@@ -927,8 +926,8 @@
       6353, 1000, "Vendor", "Product", "123ABC");
 
   auto* store = GetChooserContext(profile());
-  profile()->GetPrefs()->Set(prefs::kManagedWebUsbAllowDevicesForUrls,
-                             *base::JSONReader::ReadDeprecated(kPolicySetting));
+  profile()->GetPrefs()->SetList(prefs::kManagedWebUsbAllowDevicesForUrls,
+                                 base::test::ParseJsonList(kPolicySetting));
 
   const auto kVendorOrigin = url::Origin::Create(GURL(kVendorUrl));
   store->GrantDevicePermission(kVendorOrigin, *persistent_device_info);
@@ -953,8 +952,8 @@
       1123, 5813, "Some", "Product", "123ABC");
 
   auto* store = GetChooserContext(profile());
-  profile()->GetPrefs()->Set(prefs::kManagedWebUsbAllowDevicesForUrls,
-                             *base::JSONReader::ReadDeprecated(kPolicySetting));
+  profile()->GetPrefs()->SetList(prefs::kManagedWebUsbAllowDevicesForUrls,
+                                 base::test::ParseJsonList(kPolicySetting));
 
   const auto kAnyDeviceOrigin = url::Origin::Create(GURL(kAnyDeviceUrl));
   store->GrantDevicePermission(kAnyDeviceOrigin, *persistent_device_info);
@@ -976,8 +975,8 @@
 TEST_F(UsbChooserContextTest,
        GetAllGrantedObjectsWithOnlyPolicyAllowedDevices) {
   auto* store = GetChooserContext(profile());
-  profile()->GetPrefs()->Set(prefs::kManagedWebUsbAllowDevicesForUrls,
-                             *base::JSONReader::ReadDeprecated(kPolicySetting));
+  profile()->GetPrefs()->SetList(prefs::kManagedWebUsbAllowDevicesForUrls,
+                                 base::test::ParseJsonList(kPolicySetting));
 
   auto objects = store->GetAllGrantedObjects();
   ASSERT_EQ(objects.size(), 4u);
@@ -1017,8 +1016,8 @@
 
 TEST_F(UsbChooserContextTest,
        GetAllGrantedObjectsWithUserAndPolicyAllowedDevices) {
-  profile()->GetPrefs()->Set(prefs::kManagedWebUsbAllowDevicesForUrls,
-                             *base::JSONReader::ReadDeprecated(kPolicySetting));
+  profile()->GetPrefs()->SetList(prefs::kManagedWebUsbAllowDevicesForUrls,
+                                 base::test::ParseJsonList(kPolicySetting));
 
   const GURL kGoogleUrl("https://www.google.com");
   const auto kGoogleOrigin = url::Origin::Create(kGoogleUrl);
@@ -1094,8 +1093,8 @@
 TEST_F(UsbChooserContextTest,
        GetAllGrantedObjectsWithSpecificPolicyAndUserGrantedDevice) {
   auto* store = GetChooserContext(profile());
-  profile()->GetPrefs()->Set(prefs::kManagedWebUsbAllowDevicesForUrls,
-                             *base::JSONReader::ReadDeprecated(kPolicySetting));
+  profile()->GetPrefs()->SetList(prefs::kManagedWebUsbAllowDevicesForUrls,
+                                 base::test::ParseJsonList(kPolicySetting));
 
   UsbDeviceInfoPtr persistent_device_info = device_manager_.CreateAndAddDevice(
       6353, 5678, "Specific", "Product", "123ABC");
@@ -1154,8 +1153,8 @@
       6353, 1000, "Vendor", "Product", "123ABC");
 
   auto* store = GetChooserContext(profile());
-  profile()->GetPrefs()->Set(prefs::kManagedWebUsbAllowDevicesForUrls,
-                             *base::JSONReader::ReadDeprecated(kPolicySetting));
+  profile()->GetPrefs()->SetList(prefs::kManagedWebUsbAllowDevicesForUrls,
+                                 base::test::ParseJsonList(kPolicySetting));
 
   EXPECT_CALL(*mock_permission_observers_[profile()],
               OnObjectPermissionChanged(
@@ -1207,8 +1206,8 @@
       1123, 5813, "Some", "Product", "123ABC");
 
   auto* store = GetChooserContext(profile());
-  profile()->GetPrefs()->Set(prefs::kManagedWebUsbAllowDevicesForUrls,
-                             *base::JSONReader::ReadDeprecated(kPolicySetting));
+  profile()->GetPrefs()->SetList(prefs::kManagedWebUsbAllowDevicesForUrls,
+                                 base::test::ParseJsonList(kPolicySetting));
 
   EXPECT_CALL(*mock_permission_observers_[profile()],
               OnObjectPermissionChanged(
diff --git a/chrome/browser/web_applications/BUILD.gn b/chrome/browser/web_applications/BUILD.gn
index 620c7ae..622e692 100644
--- a/chrome/browser/web_applications/BUILD.gn
+++ b/chrome/browser/web_applications/BUILD.gn
@@ -133,8 +133,6 @@
     "os_integration/uninstallation_via_os_settings_sub_manager.h",
     "os_integration/url_handler_manager.cc",
     "os_integration/url_handler_manager.h",
-    "os_integration/url_handling_sub_manager.cc",
-    "os_integration/url_handling_sub_manager.h",
     "os_integration/web_app_file_handler_manager.cc",
     "os_integration/web_app_file_handler_manager.h",
     "os_integration/web_app_file_handler_registration.cc",
@@ -626,7 +624,6 @@
     "os_integration/run_on_os_login_sub_manager_unittest.cc",
     "os_integration/shortcut_menu_handling_sub_manager_unittest.cc",
     "os_integration/uninstallation_via_os_settings_sub_manager_unittest.cc",
-    "os_integration/url_handling_sub_manager_unittest.cc",
     "os_integration/web_app_file_handler_manager_unittest.cc",
     "os_integration/web_app_protocol_handler_manager_unittest.cc",
     "os_integration/web_app_shortcut_unittest.cc",
diff --git a/chrome/browser/web_applications/os_integration/os_integration_manager.cc b/chrome/browser/web_applications/os_integration/os_integration_manager.cc
index 0bcee616..44e289d 100644
--- a/chrome/browser/web_applications/os_integration/os_integration_manager.cc
+++ b/chrome/browser/web_applications/os_integration/os_integration_manager.cc
@@ -31,7 +31,6 @@
 #include "chrome/browser/web_applications/os_integration/shortcut_menu_handling_sub_manager.h"
 #include "chrome/browser/web_applications/os_integration/shortcut_sub_manager.h"
 #include "chrome/browser/web_applications/os_integration/uninstallation_via_os_settings_sub_manager.h"
-#include "chrome/browser/web_applications/os_integration/url_handling_sub_manager.h"
 #include "chrome/browser/web_applications/os_integration/web_app_shortcut.h"
 #include "chrome/browser/web_applications/os_integration/web_app_uninstallation_via_os_settings_registration.h"
 #include "chrome/browser/web_applications/proto/web_app_os_integration_state.pb.h"
@@ -190,8 +189,6 @@
       *profile_, *registrar, *sync_bridge);
   auto protocol_handling_sub_manager =
       std::make_unique<ProtocolHandlingSubManager>(profile_, *registrar);
-  auto url_handling_sub_manager =
-      std::make_unique<UrlHandlingSubManager>(*registrar);
   auto shortcut_menu_handling_sub_manager =
       std::make_unique<ShortcutMenuHandlingSubManager>(
           profile_->GetPath(), *icon_manager, *registrar);
@@ -202,7 +199,6 @@
   sub_managers_.push_back(std::move(shortcut_sub_manager));
   sub_managers_.push_back(std::move(file_handling_sub_manager));
   sub_managers_.push_back(std::move(protocol_handling_sub_manager));
-  sub_managers_.push_back(std::move(url_handling_sub_manager));
   sub_managers_.push_back(std::move(shortcut_menu_handling_sub_manager));
   sub_managers_.push_back(std::move(run_on_os_login_sub_manager));
   sub_managers_.push_back(
@@ -234,6 +230,11 @@
     return;
   }
 
+  if (sub_managers_.empty()) {
+    std::move(callback).Run();
+    return;
+  }
+
   if (!registrar_->GetAppById(app_id)) {
     std::move(callback).Run();
     return;
@@ -592,11 +593,18 @@
 
 void OsIntegrationManager::RegisterRunOnOsLogin(const AppId& app_id,
                                                 ResultCallback callback) {
+  ResultCallback metrics_callback =
+      base::BindOnce([](Result result) {
+        base::UmaHistogramBoolean("WebApp.RunOnOsLogin.Registration.Result",
+                                  (result == Result::kOk));
+        return result;
+      }).Then(std::move(callback));
+
   GetShortcutInfoForApp(
       app_id,
       base::BindOnce(
           &OsIntegrationManager::OnShortcutInfoRetrievedRegisterRunOnOsLogin,
-          weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
+          weak_ptr_factory_.GetWeakPtr(), std::move(metrics_callback)));
 }
 
 void OsIntegrationManager::MacAppShimOnAppInstalledForProfile(
@@ -641,10 +649,17 @@
 
 void OsIntegrationManager::UnregisterRunOnOsLogin(const AppId& app_id,
                                                   ResultCallback callback) {
+  ResultCallback metrics_callback =
+      base::BindOnce([](Result result) {
+        base::UmaHistogramBoolean("WebApp.RunOnOsLogin.Unregistration.Result",
+                                  (result == Result::kOk));
+        return result;
+      }).Then(std::move(callback));
+
   ScheduleUnregisterRunOnOsLogin(
       sync_bridge_, app_id, profile_->GetPath(),
       base::UTF8ToUTF16(registrar_->GetAppShortName(app_id)),
-      std::move(callback));
+      std::move(metrics_callback));
 }
 
 void OsIntegrationManager::DeleteShortcuts(
diff --git a/chrome/browser/web_applications/os_integration/url_handling_sub_manager.cc b/chrome/browser/web_applications/os_integration/url_handling_sub_manager.cc
deleted file mode 100644
index c042d58..0000000
--- a/chrome/browser/web_applications/os_integration/url_handling_sub_manager.cc
+++ /dev/null
@@ -1,65 +0,0 @@
-// Copyright 2023 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "chrome/browser/web_applications/os_integration/url_handling_sub_manager.h"
-
-#include <utility>
-
-#include "chrome/browser/web_applications/proto/web_app_os_integration_state.pb.h"
-#include "chrome/browser/web_applications/web_app.h"
-#include "chrome/browser/web_applications/web_app_registrar.h"
-
-namespace web_app {
-
-UrlHandlingSubManager::UrlHandlingSubManager(WebAppRegistrar& registrar)
-    : registrar_(registrar) {}
-
-UrlHandlingSubManager::~UrlHandlingSubManager() = default;
-
-void UrlHandlingSubManager::Configure(
-    const AppId& app_id,
-    proto::WebAppOsIntegrationState& desired_state,
-    base::OnceClosure configure_done) {
-  DCHECK(!desired_state.has_url_handling());
-
-  if (!registrar_->IsLocallyInstalled(app_id)) {
-    std::move(configure_done).Run();
-    return;
-  }
-
-  const WebApp* web_app = registrar_->GetAppById(app_id);
-
-  proto::UrlHandling* url_handling_proto = desired_state.mutable_url_handling();
-  for (const auto& url_handler : web_app->url_handlers()) {
-    proto::UrlHandling::UrlHandler* url_handler_proto =
-        url_handling_proto->add_url_handlers();
-    url_handler_proto->set_origin(url_handler.origin.Serialize());
-    url_handler_proto->set_has_origin_wildcard(url_handler.has_origin_wildcard);
-
-    for (const std::string& path : url_handler.paths) {
-      url_handler_proto->add_paths(path);
-    }
-    for (const std::string& exclude_path : url_handler.exclude_paths) {
-      url_handler_proto->add_exclude_paths(exclude_path);
-    }
-  }
-
-  std::move(configure_done).Run();
-}
-
-void UrlHandlingSubManager::Start() {}
-
-void UrlHandlingSubManager::Shutdown() {}
-
-void UrlHandlingSubManager::Execute(
-    const AppId& app_id,
-    const absl::optional<SynchronizeOsOptions>& synchronize_options,
-    const proto::WebAppOsIntegrationState& desired_state,
-    const proto::WebAppOsIntegrationState& current_state,
-    base::OnceClosure callback) {
-  // Not implemented yet.
-  std::move(callback).Run();
-}
-
-}  // namespace web_app
diff --git a/chrome/browser/web_applications/os_integration/url_handling_sub_manager.h b/chrome/browser/web_applications/os_integration/url_handling_sub_manager.h
deleted file mode 100644
index ae972ad9..0000000
--- a/chrome/browser/web_applications/os_integration/url_handling_sub_manager.h
+++ /dev/null
@@ -1,42 +0,0 @@
-// Copyright 2023 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef CHROME_BROWSER_WEB_APPLICATIONS_OS_INTEGRATION_URL_HANDLING_SUB_MANAGER_H_
-#define CHROME_BROWSER_WEB_APPLICATIONS_OS_INTEGRATION_URL_HANDLING_SUB_MANAGER_H_
-
-#include "base/functional/callback_forward.h"
-#include "base/memory/raw_ref.h"
-#include "chrome/browser/web_applications/os_integration/os_integration_sub_manager.h"
-#include "chrome/browser/web_applications/proto/web_app_os_integration_state.pb.h"
-#include "chrome/browser/web_applications/web_app_id.h"
-#include "third_party/abseil-cpp/absl/types/optional.h"
-
-namespace web_app {
-
-class WebAppRegistrar;
-
-// Used to track updates to the URL handlers for a web app.
-class UrlHandlingSubManager : public OsIntegrationSubManager {
- public:
-  explicit UrlHandlingSubManager(WebAppRegistrar& registrar);
-  ~UrlHandlingSubManager() override;
-  void Start() override;
-  void Shutdown() override;
-
-  void Configure(const AppId& app_id,
-                 proto::WebAppOsIntegrationState& desired_state,
-                 base::OnceClosure configure_done) override;
-  void Execute(const AppId& app_id,
-               const absl::optional<SynchronizeOsOptions>& synchronize_options,
-               const proto::WebAppOsIntegrationState& desired_state,
-               const proto::WebAppOsIntegrationState& current_state,
-               base::OnceClosure callback) override;
-
- private:
-  const raw_ref<WebAppRegistrar> registrar_;
-};
-
-}  // namespace web_app
-
-#endif  // CHROME_BROWSER_WEB_APPLICATIONS_OS_INTEGRATION_URL_HANDLING_SUB_MANAGER_H_
diff --git a/chrome/browser/web_applications/os_integration/url_handling_sub_manager_unittest.cc b/chrome/browser/web_applications/os_integration/url_handling_sub_manager_unittest.cc
deleted file mode 100644
index 1abaa20..0000000
--- a/chrome/browser/web_applications/os_integration/url_handling_sub_manager_unittest.cc
+++ /dev/null
@@ -1,201 +0,0 @@
-// Copyright 2023 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include <memory>
-#include <utility>
-#include <vector>
-
-#include "base/files/file_util.h"
-#include "base/test/scoped_feature_list.h"
-#include "base/test/test_future.h"
-#include "chrome/browser/profiles/profile.h"
-#include "chrome/browser/web_applications/os_integration/os_integration_manager.h"
-#include "chrome/browser/web_applications/os_integration/os_integration_test_override.h"
-#include "chrome/browser/web_applications/proto/web_app_os_integration_state.pb.h"
-#include "chrome/browser/web_applications/test/fake_web_app_provider.h"
-#include "chrome/browser/web_applications/test/web_app_install_test_utils.h"
-#include "chrome/browser/web_applications/test/web_app_test.h"
-#include "chrome/browser/web_applications/test/web_app_test_utils.h"
-#include "chrome/browser/web_applications/web_app_command_scheduler.h"
-#include "chrome/browser/web_applications/web_app_install_info.h"
-#include "chrome/browser/web_applications/web_app_install_params.h"
-#include "chrome/browser/web_applications/web_app_provider.h"
-#include "chrome/common/chrome_features.h"
-#include "components/services/app_service/public/cpp/url_handler_info.h"
-#include "components/webapps/browser/install_result_code.h"
-#include "testing/gtest/include/gtest/gtest.h"
-#include "url/gurl.h"
-
-namespace web_app {
-
-namespace {
-
-class UrlHandlingSubManagerTest
-    : public WebAppTest,
-      public ::testing::WithParamInterface<OsIntegrationSubManagersState> {
- public:
-  const GURL kWebAppUrl = GURL("https://example.com/path/index.html");
-
-  UrlHandlingSubManagerTest() = default;
-  ~UrlHandlingSubManagerTest() override = default;
-
-  void SetUp() override {
-    WebAppTest::SetUp();
-    {
-      base::ScopedAllowBlockingForTesting allow_blocking;
-      test_override_ =
-          OsIntegrationTestOverride::OverrideForTesting(base::GetHomeDir());
-    }
-    if (GetParam() == OsIntegrationSubManagersState::kSaveStateToDB) {
-      scoped_feature_list_.InitAndEnableFeatureWithParameters(
-          features::kOsIntegrationSubManagers, {{"stage", "write_config"}});
-    } else {
-      scoped_feature_list_.InitWithFeatures(
-          /*enabled_features=*/{},
-          /*disabled_features=*/{features::kOsIntegrationSubManagers});
-    }
-
-    provider_ = FakeWebAppProvider::Get(profile());
-
-    auto file_handler_manager =
-        std::make_unique<WebAppFileHandlerManager>(profile());
-    auto protocol_handler_manager =
-        std::make_unique<WebAppProtocolHandlerManager>(profile());
-    auto shortcut_manager = std::make_unique<WebAppShortcutManager>(
-        profile(), /*icon_manager=*/nullptr, file_handler_manager.get(),
-        protocol_handler_manager.get());
-    auto os_integration_manager = std::make_unique<OsIntegrationManager>(
-        profile(), std::move(shortcut_manager), std::move(file_handler_manager),
-        std::move(protocol_handler_manager), /*url_handler_manager=*/nullptr);
-
-    provider_->SetOsIntegrationManager(std::move(os_integration_manager));
-    test::AwaitStartWebAppProviderAndSubsystems(profile());
-  }
-
-  void TearDown() override {
-    // Blocking required due to file operations in the shortcut override
-    // destructor.
-    test::UninstallAllWebApps(profile());
-    {
-      base::ScopedAllowBlockingForTesting allow_blocking;
-      test_override_.reset();
-    }
-    WebAppTest::TearDown();
-  }
-
-  web_app::AppId InstallWebApp(apps::UrlHandlers url_handlers) {
-    std::unique_ptr<WebAppInstallInfo> info =
-        std::make_unique<WebAppInstallInfo>();
-    info->start_url = kWebAppUrl;
-    info->title = u"Test App";
-    info->user_display_mode = web_app::mojom::UserDisplayMode::kStandalone;
-    info->url_handlers = url_handlers;
-    base::test::TestFuture<const AppId&, webapps::InstallResultCode> result;
-    // InstallFromInfoWithParams is used instead of InstallFromInfo, because
-    // InstallFromInfo doesn't register OS integration.
-    provider().scheduler().InstallFromInfoWithParams(
-        std::move(info), /*overwrite_existing_manifest_fields=*/true,
-        webapps::WebappInstallSource::OMNIBOX_INSTALL_ICON,
-        result.GetCallback(), WebAppInstallParams());
-    bool success = result.Wait();
-    EXPECT_TRUE(success);
-    if (!success) {
-      return AppId();
-    }
-    EXPECT_EQ(result.Get<webapps::InstallResultCode>(),
-              webapps::InstallResultCode::kSuccessNewInstall);
-    return result.Get<AppId>();
-  }
-
- protected:
-  WebAppProvider& provider() { return *provider_; }
-
- private:
-  raw_ptr<FakeWebAppProvider> provider_;
-  base::test::ScopedFeatureList scoped_feature_list_;
-  std::unique_ptr<OsIntegrationTestOverride::BlockingRegistration>
-      test_override_;
-};
-
-TEST_P(UrlHandlingSubManagerTest, TestConfig) {
-  apps::UrlHandlers url_handlers;
-  url::Origin foo_origin = url::Origin::Create(GURL("https://foo.com"));
-  url::Origin bar_origin = url::Origin::Create(GURL("https://bar.com"));
-  url_handlers.push_back(apps::UrlHandlerInfo(foo_origin, true,
-                                              /*paths*/ {"/include"},
-                                              /*exclude_paths*/ {"/exclude"}));
-  url_handlers.push_back(apps::UrlHandlerInfo(bar_origin, false, /*paths*/ {},
-                                              /*exclude_paths*/ {}));
-
-  const AppId& app_id = InstallWebApp(url_handlers);
-
-  auto state =
-      provider().registrar_unsafe().GetAppCurrentOsIntegrationState(app_id);
-  ASSERT_TRUE(state.has_value());
-  const proto::WebAppOsIntegrationState& os_integration_state = state.value();
-  if (AreOsIntegrationSubManagersEnabled()) {
-    EXPECT_TRUE(os_integration_state.has_url_handling());
-    EXPECT_EQ(os_integration_state.url_handling().url_handlers_size(), 2);
-
-    EXPECT_EQ(os_integration_state.url_handling().url_handlers(0).origin(),
-              "https://foo.com");
-    EXPECT_TRUE(os_integration_state.url_handling()
-                    .url_handlers(0)
-                    .has_origin_wildcard());
-    EXPECT_EQ(os_integration_state.url_handling().url_handlers(0).paths_size(),
-              1);
-    EXPECT_EQ(os_integration_state.url_handling().url_handlers(0).paths()[0],
-              "/include");
-    EXPECT_EQ(os_integration_state.url_handling()
-                  .url_handlers(0)
-                  .exclude_paths_size(),
-              1);
-    EXPECT_EQ(
-        os_integration_state.url_handling().url_handlers(0).exclude_paths()[0],
-        "/exclude");
-
-    EXPECT_EQ(os_integration_state.url_handling().url_handlers(1).origin(),
-              "https://bar.com");
-    EXPECT_FALSE(os_integration_state.url_handling()
-                     .url_handlers(1)
-                     .has_origin_wildcard());
-    EXPECT_EQ(os_integration_state.url_handling().url_handlers(1).paths_size(),
-              0);
-    EXPECT_EQ(os_integration_state.url_handling()
-                  .url_handlers(1)
-                  .exclude_paths_size(),
-              0);
-  } else {
-    ASSERT_FALSE(os_integration_state.has_url_handling());
-  }
-}
-
-TEST_P(UrlHandlingSubManagerTest, TestUninstall) {
-  apps::UrlHandlers url_handlers;
-  url::Origin foo_origin = url::Origin::Create(GURL("https://foo.com"));
-  url::Origin bar_origin = url::Origin::Create(GURL("https://bar.com"));
-  url_handlers.push_back(apps::UrlHandlerInfo(foo_origin, true,
-                                              /*paths*/ {"/include"},
-                                              /*exclude_paths*/ {"/exclude"}));
-  url_handlers.push_back(apps::UrlHandlerInfo(bar_origin, false, /*paths*/ {},
-                                              /*exclude_paths*/ {}));
-
-  const AppId& app_id = InstallWebApp(url_handlers);
-  test::UninstallAllWebApps(profile());
-
-  auto state =
-      provider().registrar_unsafe().GetAppCurrentOsIntegrationState(app_id);
-  ASSERT_FALSE(state.has_value());
-}
-
-INSTANTIATE_TEST_SUITE_P(
-    All,
-    UrlHandlingSubManagerTest,
-    ::testing::Values(OsIntegrationSubManagersState::kSaveStateToDB,
-                      OsIntegrationSubManagersState::kDisabled),
-    test::GetOsIntegrationSubManagersTestName);
-
-}  // namespace
-
-}  // namespace web_app
diff --git a/chrome/browser/web_applications/proto/web_app_os_integration_state.proto b/chrome/browser/web_applications/proto/web_app_os_integration_state.proto
index 5f6ecaf..11e3178 100644
--- a/chrome/browser/web_applications/proto/web_app_os_integration_state.proto
+++ b/chrome/browser/web_applications/proto/web_app_os_integration_state.proto
@@ -76,27 +76,16 @@
   repeated FileHandler file_handlers = 1;
 }
 
-message UrlHandling {
-  message UrlHandler {
-    optional string origin = 1;
-    optional bool has_origin_wildcard = 2;
-    repeated string paths = 3;
-    repeated string exclude_paths = 4;
-  }
-  repeated UrlHandler url_handlers = 1;
-}
-
 // Represents all the common OS Integration states to be stored in the web_app
 // DB that matches the values in the OS.
 message WebAppOsIntegrationState {
-  reserved 2;
+  reserved 2, 8;
   optional ShortcutDescription shortcut = 1;
   optional ProtocolsHandled protocols_handled = 3;
   optional RunOnOsLogin run_on_os_login = 4;
   optional OsUninstallRegistration uninstall_registration = 5;
   optional ShortcutMenus shortcut_menus = 6;
   optional FileHandling file_handling = 7;
-  optional UrlHandling url_handling = 8;
   // Add data states for other OS integration hooks here.
   // New fields added to this message must also be added to:
   // OsStatesDebugValue()
diff --git a/chrome/browser/web_applications/web_app.cc b/chrome/browser/web_applications/web_app.cc
index b443c08..7461deb 100644
--- a/chrome/browser/web_applications/web_app.cc
+++ b/chrome/browser/web_applications/web_app.cc
@@ -182,31 +182,6 @@
     debug_dict.Set("file_handling", std::move(file_handlers_list));
   }
 
-  if (current_states.has_url_handling()) {
-    base::Value::List url_handlers;
-    for (const auto& url_handler :
-         current_states.url_handling().url_handlers()) {
-      base::Value::Dict url_handler_dict;
-      url_handler_dict.Set("origin", url_handler.origin());
-      url_handler_dict.Set("has_origin_wildcard",
-                           url_handler.has_origin_wildcard());
-      base::Value::List paths;
-      for (auto path : url_handler.paths()) {
-        paths.Append(path);
-      }
-      url_handler_dict.Set("paths", std::move(paths));
-      base::Value::List exclude_paths;
-      for (auto path : url_handler.exclude_paths()) {
-        exclude_paths.Append(path);
-      }
-      url_handler_dict.Set("exclude_paths", std::move(exclude_paths));
-      url_handlers.Append(std::move(url_handler_dict));
-    }
-    base::Value::Dict url_handling;
-    url_handling.Set("url_handlers", std::move(url_handlers));
-    debug_dict.Set("url_handling", std::move(url_handling));
-  }
-
   return base::Value(std::move(debug_dict));
 }
 
diff --git a/chrome/browser/webid/federated_identity_auto_reauthn_permission_context.cc b/chrome/browser/webid/federated_identity_auto_reauthn_permission_context.cc
index a998049..4d18eed 100644
--- a/chrome/browser/webid/federated_identity_auto_reauthn_permission_context.cc
+++ b/chrome/browser/webid/federated_identity_auto_reauthn_permission_context.cc
@@ -24,20 +24,26 @@
 FederatedIdentityAutoReauthnPermissionContext::
     ~FederatedIdentityAutoReauthnPermissionContext() = default;
 
-bool FederatedIdentityAutoReauthnPermissionContext::HasAutoReauthnPermission(
+bool FederatedIdentityAutoReauthnPermissionContext::
+    HasAutoReauthnContentSetting() {
+  return host_content_settings_map_->GetDefaultContentSetting(
+             ContentSettingsType::FEDERATED_IDENTITY_AUTO_REAUTHN_PERMISSION,
+             /*provider_id=*/nullptr) != ContentSetting::CONTENT_SETTING_BLOCK;
+}
+
+bool FederatedIdentityAutoReauthnPermissionContext::IsAutoReauthnEmbargoed(
     const url::Origin& relying_party_embedder) {
-  bool is_content_setting_allowed =
-      host_content_settings_map_->GetDefaultContentSetting(
-          ContentSettingsType::FEDERATED_IDENTITY_AUTO_REAUTHN_PERMISSION,
-          /*provider_id=*/nullptr) != ContentSetting::CONTENT_SETTING_BLOCK;
-  UMA_HISTOGRAM_BOOLEAN("Blink.FedCm.AutoReauthn.BlockedByContentSettings",
-                        !is_content_setting_allowed);
-  bool is_embargoed = permission_autoblocker_->IsEmbargoed(
+  return permission_autoblocker_->IsEmbargoed(
       relying_party_embedder.GetURL(),
       ContentSettingsType::FEDERATED_IDENTITY_AUTO_REAUTHN_PERMISSION);
-  UMA_HISTOGRAM_BOOLEAN("Blink.FedCm.AutoReauthn.BlockedByEmbargo",
-                        is_embargoed);
-  return is_content_setting_allowed && !is_embargoed;
+}
+
+base::Time
+FederatedIdentityAutoReauthnPermissionContext::GetAutoReauthnEmbargoStartTime(
+    const url::Origin& relying_party_embedder) {
+  return permission_autoblocker_->GetEmbargoStartTime(
+      relying_party_embedder.GetURL(),
+      ContentSettingsType::FEDERATED_IDENTITY_AUTO_REAUTHN_PERMISSION);
 }
 
 void FederatedIdentityAutoReauthnPermissionContext::RecordDisplayAndEmbargo(
diff --git a/chrome/browser/webid/federated_identity_auto_reauthn_permission_context.h b/chrome/browser/webid/federated_identity_auto_reauthn_permission_context.h
index 73bc152..c6d163d 100644
--- a/chrome/browser/webid/federated_identity_auto_reauthn_permission_context.h
+++ b/chrome/browser/webid/federated_identity_auto_reauthn_permission_context.h
@@ -34,7 +34,10 @@
       const FederatedIdentityAutoReauthnPermissionContext&) = delete;
 
   // content::FederatedIdentityAutoReauthnPermissionContextDelegate:
-  bool HasAutoReauthnPermission(
+  bool HasAutoReauthnContentSetting() override;
+  bool IsAutoReauthnEmbargoed(
+      const url::Origin& relying_party_embedder) override;
+  base::Time GetAutoReauthnEmbargoStartTime(
       const url::Origin& relying_party_embedder) override;
   void RecordDisplayAndEmbargo(
       const url::Origin& relying_party_embedder) override;
diff --git a/chrome/browser/webid/federated_identity_auto_reauthn_permission_context_unittest.cc b/chrome/browser/webid/federated_identity_auto_reauthn_permission_context_unittest.cc
index c0a7983..a053280 100644
--- a/chrome/browser/webid/federated_identity_auto_reauthn_permission_context_unittest.cc
+++ b/chrome/browser/webid/federated_identity_auto_reauthn_permission_context_unittest.cc
@@ -52,8 +52,8 @@
 TEST_F(FederatedIdentityAutoReauthnPermissionContextTest,
        AutoReauthnEnabledByDefault) {
   GURL rp_url("https://rp.com");
-  EXPECT_EQ(true,
-            context_->HasAutoReauthnPermission(url::Origin::Create(rp_url)));
+  EXPECT_TRUE(context_->HasAutoReauthnContentSetting());
+  EXPECT_FALSE(context_->IsAutoReauthnEmbargoed(url::Origin::Create(rp_url)));
 }
 
 // Test that
diff --git a/chrome/build/linux.pgo.txt b/chrome/build/linux.pgo.txt
index 8d4d383c..670c1bc 100644
--- a/chrome/build/linux.pgo.txt
+++ b/chrome/build/linux.pgo.txt
@@ -1 +1 @@
-chrome-linux-main-1676980367-ad7da12a7ad797e7820024f7fb3ecaafe86744f1.profdata
+chrome-linux-main-1677002389-5e8b493cc771b62b8c22038ecef911a650be0be4.profdata
diff --git a/chrome/build/mac-arm.pgo.txt b/chrome/build/mac-arm.pgo.txt
index 83334bd0b..637638c 100644
--- a/chrome/build/mac-arm.pgo.txt
+++ b/chrome/build/mac-arm.pgo.txt
@@ -1 +1 @@
-chrome-mac-arm-main-1676980367-21616960f10b88358395bf2911e6f3a88c674438.profdata
+chrome-mac-arm-main-1677009458-7346edb3be6d91898fc7dc9ade06bb4e244fb600.profdata
diff --git a/chrome/build/mac.pgo.txt b/chrome/build/mac.pgo.txt
index 3b57420..fc13fb7 100644
--- a/chrome/build/mac.pgo.txt
+++ b/chrome/build/mac.pgo.txt
@@ -1 +1 @@
-chrome-mac-main-1676958321-3a15a6ea4903056f4c2154ff19b6ada3fe3697d8.profdata
+chrome-mac-main-1677002389-c085779fc8798025d46f40a44faf7db0ca954d78.profdata
diff --git a/chrome/build/win32.pgo.txt b/chrome/build/win32.pgo.txt
index 2ae7799..cf4498e8 100644
--- a/chrome/build/win32.pgo.txt
+++ b/chrome/build/win32.pgo.txt
@@ -1 +1 @@
-chrome-win32-main-1676980367-5eeafc6931e2661f9793227292162a6a9894a260.profdata
+chrome-win32-main-1677002389-aae805505c34a85fb8c880aed9e730c525c06114.profdata
diff --git a/chrome/build/win64.pgo.txt b/chrome/build/win64.pgo.txt
index 50103367..e3c8f9f 100644
--- a/chrome/build/win64.pgo.txt
+++ b/chrome/build/win64.pgo.txt
@@ -1 +1 @@
-chrome-win64-main-1676980367-f4f6f10748cab154e595d9943c12247067360c1a.profdata
+chrome-win64-main-1677002389-9855de2516e5ab7c0d9b964eb2b999368b94955a.profdata
diff --git a/chrome/common/extensions/api/pdf_viewer_private.idl b/chrome/common/extensions/api/pdf_viewer_private.idl
index 215fbb81..67eaab1 100644
--- a/chrome/common/extensions/api/pdf_viewer_private.idl
+++ b/chrome/common/extensions/api/pdf_viewer_private.idl
@@ -30,4 +30,10 @@
     [supportsPromises] static void setPdfOcrPref(
         boolean value, OnPdfOcrPrefSetCallback callback);
   };
+
+  interface Events {
+    // Fired when a pref value for PDF OCR has changed.
+    // |value| The pref value that changed.
+    static void onPdfOcrPrefChanged(boolean value);
+  };
 };
diff --git a/chrome/test/base/extension_js_browser_test.cc b/chrome/test/base/extension_js_browser_test.cc
index c00abad..15378717 100644
--- a/chrome/test/base/extension_js_browser_test.cc
+++ b/chrome/test/base/extension_js_browser_test.cc
@@ -5,6 +5,7 @@
 #include "chrome/test/base/extension_js_browser_test.h"
 
 #include <memory>
+#include <vector>
 
 #include "base/functional/callback.h"
 #include "base/json/json_reader.h"
@@ -14,6 +15,7 @@
 #include "base/values.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/ui/browser.h"
+#include "chrome/common/extensions/extension_constants.h"
 #include "chrome/test/base/javascript_browser_test.h"
 #include "content/public/browser/web_contents.h"
 #include "content/public/test/browser_test_utils.h"
@@ -22,6 +24,23 @@
 #include "extensions/browser/extension_host.h"
 #include "extensions/browser/extension_host_test_helper.h"
 #include "test_switches.h"
+#include "ui/base/ime/ash/extension_ime_util.h"
+
+namespace {
+
+const std::vector<std::string>& GetExtensionIdsToCollectCoverage() {
+  static const std::vector<std::string> extensions_for_coverage = {
+      extension_misc::kChromeVoxExtensionId,
+      extension_misc::kSelectToSpeakExtensionId,
+      extension_misc::kSwitchAccessExtensionId,
+      extension_misc::kAccessibilityCommonExtensionId,
+      extension_misc::kEnhancedNetworkTtsExtensionId,
+      ash::extension_ime_util::kBrailleImeExtensionId,
+  };
+  return extensions_for_coverage;
+}
+
+}  // namespace
 
 ExtensionJSBrowserTest::ExtensionJSBrowserTest() = default;
 
@@ -37,8 +56,14 @@
         command_line->GetSwitchValuePath(switches::kDevtoolsCodeCoverage);
     ShouldInspectDevToolsAgentHostCallback callback =
         base::BindRepeating([](content::DevToolsAgentHost* host) {
-          return base::StartsWith(host->GetTitle(), "ChromeVox") &&
-                 host->GetType() == "background_page";
+          const auto& ext_ids = GetExtensionIdsToCollectCoverage();
+          for (const auto& ext_id : ext_ids) {
+            if (base::Contains(host->GetURL().path(), ext_id) &&
+                host->GetType() == "background_page") {
+              return true;
+            }
+          }
+          return false;
         });
     coverage_handler_ = std::make_unique<DevToolsAgentCoverageObserver>(
         devtools_code_coverage_dir, std::move(callback));
diff --git a/chrome/test/data/extensions/api_test/pdf_viewer_private/background.js b/chrome/test/data/extensions/api_test/pdf_viewer_private/background.js
index d62f16e8..163533b0 100644
--- a/chrome/test/data/extensions/api_test/pdf_viewer_private/background.js
+++ b/chrome/test/data/extensions/api_test/pdf_viewer_private/background.js
@@ -76,5 +76,23 @@
         await chrome.pdfViewerPrivate.isPdfOcrAlwaysActive();
     chrome.test.assertFalse(isPdfOcrAlwaysActive);
     chrome.test.succeed();
+  },
+  /**
+   * Test that the onPdfOcrPrefChanged function can monitor changes in the PDF
+   * OCR pref correctly. It monitors the pref defined with the following name:
+   * `settings.a11y.pdf_ocr_always_active`
+   */
+  async function testOnPdfOcrPrefChanged() {
+    const result = await chrome.pdfViewerPrivate.setPdfOcrPref(false);
+    chrome.test.assertTrue(result);
+
+    chrome.pdfViewerPrivate.onPdfOcrPrefChanged.addListener(function local(
+        isPdfOcrAlwaysActive) {
+      chrome.pdfViewerPrivate.onPdfOcrPrefChanged.removeListener(local);
+      chrome.test.assertTrue(isPdfOcrAlwaysActive);
+      chrome.test.succeed();
+    });
+
+    chrome.pdfViewerPrivate.setPdfOcrPref(true);
   }
 ]);
diff --git a/chrome/test/data/webui/settings/chromeos/device_page_tests.js b/chrome/test/data/webui/settings/chromeos/device_page_tests.js
index 3644c9c..dfff6036 100644
--- a/chrome/test/data/webui/settings/chromeos/device_page_tests.js
+++ b/chrome/test/data/webui/settings/chromeos/device_page_tests.js
@@ -1469,6 +1469,67 @@
           loadTimeData.getString('audioMutedExternallyTooltip'),
           inputMuteTooltip.textContent.trim());
     });
+
+    test(
+        'mute state updates button aria-description and aria-pressed',
+        async function() {
+          const outputMuteButton =
+              audioPage.shadowRoot.querySelector('#audioOutputMuteButton');
+          const inputMuteButton =
+              audioPage.shadowRoot.querySelector('#audioInputGainMuteButton');
+
+          // Default state should be unmuted so show the toggle mute tooltip.
+          assertEquals(
+              loadTimeData.getString('audioOutputMuteButtonAriaLabelNotMuted'),
+              outputMuteButton.ariaDescription.trim());
+          assertEquals(
+              loadTimeData.getString('audioInputMuteButtonAriaLabelNotMuted'),
+              inputMuteButton.ariaDescription.trim());
+          const ariaNotPressedValue = 'false';
+          assertEquals(ariaNotPressedValue, outputMuteButton.ariaPressed);
+          assertEquals(ariaNotPressedValue, inputMuteButton.ariaPressed);
+
+          // Test muted by user case.
+          crosAudioConfig.setAudioSystemProperties(
+              mutedByUserFakeAudioSystemProperties);
+          await flushTasks();
+          assertEquals(
+              loadTimeData.getString('audioOutputMuteButtonAriaLabelMuted'),
+              outputMuteButton.ariaDescription.trim());
+          assertEquals(
+              loadTimeData.getString('audioInputMuteButtonAriaLabelMuted'),
+              inputMuteButton.ariaDescription.trim());
+          const ariaPressedValue = 'true';
+          assertEquals(ariaPressedValue, outputMuteButton.ariaPressed);
+          assertEquals(ariaPressedValue, inputMuteButton.ariaPressed);
+
+          // Test muted by policy case.
+          crosAudioConfig.setAudioSystemProperties(
+              mutedByPolicyFakeAudioSystemProperties);
+          await flushTasks();
+          assertEquals(
+              loadTimeData.getString('audioOutputMuteButtonAriaLabelMuted'),
+              outputMuteButton.ariaDescription.trim());
+          assertEquals(
+              loadTimeData.getString('audioInputMuteButtonAriaLabelMuted'),
+              inputMuteButton.ariaDescription.trim());
+          assertEquals(ariaPressedValue, outputMuteButton.ariaPressed);
+          assertEquals(ariaPressedValue, inputMuteButton.ariaPressed);
+
+          // Test muted externally case.
+          crosAudioConfig.setAudioSystemProperties(
+              mutedExternallyFakeAudioSystemProperties);
+          await flushTasks();
+          assertEquals(
+              loadTimeData.getString('audioOutputMuteButtonAriaLabelMuted'),
+              outputMuteButton.ariaDescription.trim());
+          assertEquals(
+              loadTimeData.getString(
+                  'audioInputMuteButtonAriaLabelMutedByHardwareSwitch'),
+              inputMuteButton.ariaDescription.trim());
+          assertEquals(ariaPressedValue, outputMuteButton.ariaPressed);
+          assertEquals(ariaPressedValue, inputMuteButton.ariaPressed);
+        });
   });
 
   suite(assert(TestNames.PerDeviceMouse), function() {
diff --git a/chrome/updater/test/integration_tests.cc b/chrome/updater/test/integration_tests.cc
index 26ed94b..ea09749 100644
--- a/chrome/updater/test/integration_tests.cc
+++ b/chrome/updater/test/integration_tests.cc
@@ -95,13 +95,19 @@
     ASSERT_NO_FATAL_FAILURE(EnterTestMode(GURL("http://localhost:1234")));
 
 #if BUILDFLAG(IS_LINUX)
-    // On LUCI the XDG_RUNTIME_DIR environment variable may not be set. This is
-    // required for systemctl to connect to its bus in user mode.
+    // On LUCI the XDG_RUNTIME_DIR and DBUS_SESSION_BUS_ADDRESS environment
+    // variables may not be set. These are required for systemctl to connect to
+    // its bus in user mode.
     std::unique_ptr<base::Environment> env = base::Environment::Create();
+    const std::string xdg_runtime_dir =
+        base::StrCat({"/run/user/", base::NumberToString(getuid())});
     if (!env->HasVar("XDG_RUNTIME_DIR")) {
-      ASSERT_TRUE(env->SetVar(
-          "XDG_RUNTIME_DIR",
-          base::StrCat({"/run/user/", base::NumberToString(getuid())})));
+      ASSERT_TRUE(env->SetVar("XDG_RUNTIME_DIR", xdg_runtime_dir));
+    }
+    if (!env->HasVar("DBUS_SESSION_BUS_ADDRESS")) {
+      ASSERT_TRUE(
+          env->SetVar("DBUS_SESSION_BUS_ADDRESS",
+                      base::StrCat({"unix:path=", xdg_runtime_dir, "/bus"})));
     }
 #endif
   }
diff --git a/chromeos/ash/components/dbus/arc/fake_arcvm_data_migrator_client.cc b/chromeos/ash/components/dbus/arc/fake_arcvm_data_migrator_client.cc
index da0398a..dc0c8b8 100644
--- a/chromeos/ash/components/dbus/arc/fake_arcvm_data_migrator_client.cc
+++ b/chromeos/ash/components/dbus/arc/fake_arcvm_data_migrator_client.cc
@@ -36,7 +36,7 @@
     const arc::data_migrator::HasDataToMigrateRequest& request,
     chromeos::DBusMethodCallback<bool> callback) {
   base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
-      FROM_HERE, base::BindOnce(std::move(callback), true));
+      FROM_HERE, base::BindOnce(std::move(callback), has_data_to_migrate_));
 }
 
 void FakeArcVmDataMigratorClient::StartMigration(
diff --git a/chromeos/ash/components/dbus/arc/fake_arcvm_data_migrator_client.h b/chromeos/ash/components/dbus/arc/fake_arcvm_data_migrator_client.h
index 778b7bd..1213435 100644
--- a/chromeos/ash/components/dbus/arc/fake_arcvm_data_migrator_client.h
+++ b/chromeos/ash/components/dbus/arc/fake_arcvm_data_migrator_client.h
@@ -7,6 +7,7 @@
 
 #include "chromeos/ash/components/dbus/arc/arcvm_data_migrator_client.h"
 #include "chromeos/ash/components/dbus/arcvm_data_migrator/arcvm_data_migrator.pb.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
 
 namespace ash {
 
@@ -30,11 +31,18 @@
   FakeArcVmDataMigratorClient& operator=(const FakeArcVmDataMigratorClient&) =
       delete;
 
+  void set_has_data_to_migrate(absl::optional<bool> has_data_to_migrate) {
+    has_data_to_migrate_ = has_data_to_migrate;
+  }
+
  protected:
   friend class ArcVmDataMigratorClient;
 
   FakeArcVmDataMigratorClient();
   ~FakeArcVmDataMigratorClient() override;
+
+ private:
+  absl::optional<bool> has_data_to_migrate_ = true;
 };
 
 }  // namespace ash
diff --git a/chromeos/ash/components/device_activity/churn_cohort_use_case_impl_unittest.cc b/chromeos/ash/components/device_activity/churn_cohort_use_case_impl_unittest.cc
index 4af28c7..062741f7 100644
--- a/chromeos/ash/components/device_activity/churn_cohort_use_case_impl_unittest.cc
+++ b/chromeos/ash/components/device_activity/churn_cohort_use_case_impl_unittest.cc
@@ -36,14 +36,14 @@
 // https://crsrc.org/o/src/third_party/chromiumos-overlay/chromeos-base/chromeos-activate-date/files/activate_date;l=67
 const char kFakeFirstActivateDate[] = "2022-50";
 
-// The decimal representation of the bit string `100010001100000000000001101`
+// The decimal representation of the bit string `0100010001 000000000000001101`
 // The first 10 bits represent the number of months since 2000 is 275, which
 // represents the 2022-12.
 // The right 18 bits represent the churn cohort active status for past 18
 // months. The right most bit represents the status of previous active mont,
 // in this case, it represent 2022-12. And the second right most bit
 // represents 2022-11, etc.
-const int kFakeChurnActiveStatus = 109450913;
+const int kFakeChurnActiveStatus = 71565325;
 
 constexpr ChromeDeviceMetadataParameters kFakeChromeParameters = {
     version_info::Channel::STABLE /* chromeos_channel */,
diff --git a/chromeos/ash/components/device_activity/device_activity_client.cc b/chromeos/ash/components/device_activity/device_activity_client.cc
index 773cfcc..2d90e3c 100644
--- a/chromeos/ash/components/device_activity/device_activity_client.cc
+++ b/chromeos/ash/components/device_activity/device_activity_client.cc
@@ -1157,6 +1157,17 @@
     // Update local state pref to record reporting device active.
     current_use_case->SetLastKnownPingTimestamp(
         last_transition_out_of_idle_time_);
+
+    // Update the churn active status after churn cohort ping.
+    if (current_use_case->GetPsmUseCase() ==
+        private_membership::rlwe::RlweUseCase::
+            CROS_FRESNEL_CHURN_MONTHLY_COHORT) {
+      churn_active_status_ptr_->UpdateValue(last_transition_out_of_idle_time_);
+      // Update the active value in local_state_ as well.
+      int active_value = churn_active_status_ptr_->GetValueAsInt();
+      local_state_->SetInteger(prefs::kDeviceActiveLastKnownChurnActiveStatus,
+                               active_value);
+    }
   }
 
   RecordDurationStateMetric(state_, state_timer_.Elapsed());
diff --git a/chromeos/ash/components/device_activity/device_activity_client_unittest.cc b/chromeos/ash/components/device_activity/device_activity_client_unittest.cc
index 3381a88..fb058bc 100644
--- a/chromeos/ash/components/device_activity/device_activity_client_unittest.cc
+++ b/chromeos/ash/components/device_activity/device_activity_client_unittest.cc
@@ -50,7 +50,7 @@
 
 // Set the current time to the following string.
 // Note that we use midnight PST (UTC-8) for the unit tests.
-const char kFakeNowTimeString[] = "2000-01-01 08:00:00 GMT";
+const char kFakeNowTimeString[] = "2023-01-01 08:00:00 GMT";
 
 // This value represents the UTC based activate date of the device formatted
 // YYYY-WW to reduce privacy granularity.
@@ -1569,4 +1569,54 @@
                                       use_cases.size());
 }
 
+TEST_F(DeviceActivityClientTest,
+       UpdateChurnActiveStatusAfterChurnCohortCheckIn) {
+  // The decimal representation of the bit string `100010001000000000000001101`
+  // The first 10 bits represent the number of months since 2000 is 273, which
+  // represents the 2022-10.
+  // The right 18 bits represent the churn cohort active status for past 18
+  // months. The right most bit represents the status of previous active mont,
+  // in this case, it represent 2022-10. And the second right most bit
+  // represents 2022-09, etc.
+  int kFakeBeforeChurnActiveStatus = 71565325;
+  int kFakeAfterChurnActvieStatus = 72351849;
+
+  // Set the past ping month to 2022-10.
+  base::Time new_daily_ts = base::Time::Now() - base::Days(70);
+  for (auto* use_case : device_activity_client_->GetUseCases()) {
+    use_case->SetLastKnownPingTimestamp(new_daily_ts);
+  }
+
+  // Initialize the churn_active_value to kFakeBeforeChurnActiveStatus.
+  churn_active_status_->InitializeValue(kFakeBeforeChurnActiveStatus);
+
+  // Last Churn Cohort month is: 2022-10, months is 273
+  // Current Churn Cohort month is: 2023-01, months is 276
+  // 273->276:   0100010001->0100010100
+  // 2022-10 active value: 71565325 -> 0100010001 000000000000001101
+  // 2023-01 active value: 72351849 -> 0100010100 000000000001101001
+
+  SetWifiNetworkState(shill::kStateOnline);
+
+  for (auto* use_case : device_activity_client_->GetUseCases()) {
+    SCOPED_TRACE(testing::Message()
+                 << "PSM use case: "
+                 << psm_rlwe::RlweUseCase_Name(use_case->GetPsmUseCase()));
+
+    EXPECT_TRUE(use_case->IsLastKnownPingTimestampSet());
+
+    EXPECT_EQ(device_activity_client_->GetState(),
+              DeviceActivityClient::State::kCheckingIn);
+
+    SimulateImportResponse(std::string(), net::HTTP_OK);
+    task_environment_.RunUntilIdle();
+
+    EXPECT_GE(use_case->GetLastKnownPingTimestamp(), new_daily_ts);
+  }
+
+  // Check the new churn active status after ping.
+  int updated_active_status_value = churn_active_status_->GetValueAsInt();
+  EXPECT_EQ(updated_active_status_value, kFakeAfterChurnActvieStatus);
+}
+
 }  // namespace ash::device_activity
diff --git a/chromeos/ui/frame/multitask_menu/multitask_menu_nudge_controller.cc b/chromeos/ui/frame/multitask_menu/multitask_menu_nudge_controller.cc
index dfc8b36f..ae670c0 100644
--- a/chromeos/ui/frame/multitask_menu/multitask_menu_nudge_controller.cc
+++ b/chromeos/ui/frame/multitask_menu/multitask_menu_nudge_controller.cc
@@ -387,14 +387,36 @@
   }
 
   // The nudge is placed right below the anchor.
-  // TODO(crbug.com/1329233): Determine what to do if the nudge is offscreen.
   const gfx::Rect anchor_bounds_in_screen = anchor_view_->GetBoundsInScreen();
-  const gfx::Rect bounds_in_screen(
+  gfx::Rect bounds_in_screen(
       anchor_bounds_in_screen.CenterPoint().x() - size.width() / 2,
       anchor_bounds_in_screen.bottom() + kNudgeDistanceFromAnchor, size.width(),
       size.height());
+  bool adjust_to_fit = false;
+  const display::Display display =
+      display::Screen::GetScreen()->GetDisplayNearestView(window_);
+#if BUILDFLAG(IS_CHROMEOS_LACROS)
+  // Lacros always needs adjustment since the child cannot go outside the
+  // parents bounds currently. See https://crbug.com/1416919.
+  adjust_to_fit = true;
+#elif BUILDFLAG(IS_CHROMEOS_ASH)
+  // If the nudge is going to be offscreen, make sure it is within the window
+  // bounds.
+  adjust_to_fit = !display.work_area().Contains(bounds_in_screen);
+#endif
+  if (adjust_to_fit) {
+    // The nudge should be within the window bounds.
+    bounds_in_screen.AdjustToFit(window_->GetBoundsInScreen());
+  }
   nudge_widget_->SetBounds(bounds_in_screen);
 
+  // If setting bounds on the nudge causes it to move to another display (this
+  // can happen while dragging across displays), dismiss the nudge.
+  if (nudge_widget_->GetNativeWindow()->parent() != window_->parent()) {
+    DismissNudge();
+    return;
+  }
+
   // The circular pulse should be a square that matches the smaller dimension of
   // `anchor_view_`. We use rounded corners to make it look like a circle.
   gfx::Rect pulse_layer_bounds = anchor_bounds_in_screen;
diff --git a/components/browser_ui/widget/android/java/src/org/chromium/components/browser_ui/widget/textbubble/TextBubble.java b/components/browser_ui/widget/android/java/src/org/chromium/components/browser_ui/widget/textbubble/TextBubble.java
index 5872bd2..3b77bb6 100644
--- a/components/browser_ui/widget/android/java/src/org/chromium/components/browser_ui/widget/textbubble/TextBubble.java
+++ b/components/browser_ui/widget/android/java/src/org/chromium/components/browser_ui/widget/textbubble/TextBubble.java
@@ -540,7 +540,15 @@
      */
     private void setText(TextView view) {
         view.setText(mIsAccessibilityEnabled ? mAccessibilityString : mString);
-        if (mInverseColor) {
+        updateTextStyle(view, mInverseColor);
+    }
+
+    /**
+     * @param isInverse Whether the color scheme is inversed or not.
+     * @param view The {@link TextView} to update the style for.
+     */
+    protected void updateTextStyle(TextView view, boolean isInverse) {
+        if (isInverse) {
             ApiCompatibilityUtils.setTextAppearance(
                     view, R.style.TextAppearance_TextMediumThick_Accent1);
         }
diff --git a/components/history_clusters/core/BUILD.gn b/components/history_clusters/core/BUILD.gn
index 1af94ad..b2072d3a 100644
--- a/components/history_clusters/core/BUILD.gn
+++ b/components/history_clusters/core/BUILD.gn
@@ -29,6 +29,8 @@
     "file_clustering_backend.h",
     "full_membership_cluster_processor.cc",
     "full_membership_cluster_processor.h",
+    "history_cluster_type_utils.cc",
+    "history_cluster_type_utils.h",
     "history_clusters_db_tasks.cc",
     "history_clusters_db_tasks.h",
     "history_clusters_debug_jsons.cc",
@@ -75,6 +77,7 @@
   deps = [
     "//base",
     "//components/history/core/browser",
+    "//components/history_clusters/public/mojom:mojo_bindings",
     "//components/keyed_service/core",
     "//components/optimization_guide/core",
     "//components/optimization_guide/core:entities",
diff --git a/chrome/browser/ui/webui/history_clusters/history_cluster_type_utils.cc b/components/history_clusters/core/history_cluster_type_utils.cc
similarity index 87%
rename from chrome/browser/ui/webui/history_clusters/history_cluster_type_utils.cc
rename to components/history_clusters/core/history_cluster_type_utils.cc
index 9ed0daf..229d5af 100644
--- a/chrome/browser/ui/webui/history_clusters/history_cluster_type_utils.cc
+++ b/components/history_clusters/core/history_cluster_type_utils.cc
@@ -2,24 +2,23 @@
 // 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/history_clusters/history_cluster_type_utils.h"
+#include "components/history_clusters/core/history_cluster_type_utils.h"
 
 #include "base/strings/string_number_conversions.h"
 #include "base/strings/utf_string_conversions.h"
 #include "base/time/time.h"
 #include "base/time/time_to_iso8601.h"
-#include "chrome/browser/profiles/profile.h"
-#include "chrome/browser/search_engines/template_url_service_factory.h"
 #include "components/history/core/browser/history_types.h"
 #include "components/history_clusters/core/config.h"
+#include "components/history_clusters/public/mojom/history_cluster_types.mojom.h"
+#include "components/search_engines/template_url.h"
 #include "components/search_engines/template_url_service.h"
 #include "ui/base/l10n/time_format.h"
-#include "ui/webui/resources/cr_components/history_clusters/history_clusters.mojom.h"
 
 namespace history_clusters {
 
 // Creates a `mojom::VisitPtr` from a `history_clusters::Visit`.
-mojom::URLVisitPtr VisitToMojom(Profile* profile,
+mojom::URLVisitPtr VisitToMojom(const TemplateURLService* template_url_service,
                                 const history::ClusterVisit& visit) {
   auto visit_mojom = mojom::URLVisit::New();
   visit_mojom->visit_id = visit.annotated_visit.visit_row.visit_id;
@@ -68,8 +67,6 @@
     visit_mojom->annotations.push_back(mojom::Annotation::kBookmarked);
   }
 
-  const TemplateURLService* template_url_service =
-      TemplateURLServiceFactory::GetForProfile(profile);
   const TemplateURL* default_search_provider =
       template_url_service ? template_url_service->GetDefaultSearchProvider()
                            : nullptr;
@@ -97,10 +94,8 @@
 
 // Creates a `mojom::SearchQueryPtr` from the given search query, if possible.
 absl::optional<mojom::SearchQueryPtr> SearchQueryToMojom(
-    Profile* profile,
+    const TemplateURLService* template_url_service,
     const std::string& search_query) {
-  const TemplateURLService* template_url_service =
-      TemplateURLServiceFactory::GetForProfile(profile);
   const TemplateURL* default_search_provider =
       template_url_service ? template_url_service->GetDefaultSearchProvider()
                            : nullptr;
@@ -121,7 +116,7 @@
   return search_query_mojom;
 }
 
-mojom::ClusterPtr ClusterToMojom(Profile* profile,
+mojom::ClusterPtr ClusterToMojom(const TemplateURLService* template_url_service,
                                  const history::Cluster cluster) {
   auto cluster_mojom = mojom::Cluster::New();
   cluster_mojom->id = cluster.cluster_id;
@@ -143,11 +138,12 @@
   }
 
   for (const auto& visit : cluster.visits) {
-    cluster_mojom->visits.push_back(VisitToMojom(profile, visit));
+    cluster_mojom->visits.push_back(VisitToMojom(template_url_service, visit));
   }
 
   for (const auto& related_search : cluster.related_searches) {
-    auto search_query_mojom = SearchQueryToMojom(profile, related_search);
+    auto search_query_mojom =
+        SearchQueryToMojom(template_url_service, related_search);
     if (search_query_mojom) {
       cluster_mojom->related_searches.emplace_back(
           std::move(*search_query_mojom));
diff --git a/chrome/browser/ui/webui/history_clusters/history_cluster_type_utils.h b/components/history_clusters/core/history_cluster_type_utils.h
similarity index 60%
rename from chrome/browser/ui/webui/history_clusters/history_cluster_type_utils.h
rename to components/history_clusters/core/history_cluster_type_utils.h
index 57d428b8..bd1f85f 100644
--- a/chrome/browser/ui/webui/history_clusters/history_cluster_type_utils.h
+++ b/components/history_clusters/core/history_cluster_type_utils.h
@@ -2,12 +2,12 @@
 // 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_HISTORY_CLUSTERS_HISTORY_CLUSTER_TYPE_UTILS_H_
-#define CHROME_BROWSER_UI_WEBUI_HISTORY_CLUSTERS_HISTORY_CLUSTER_TYPE_UTILS_H_
+#ifndef COMPONENTS_HISTORY_CLUSTERS_CORE_HISTORY_CLUSTER_TYPE_UTILS_H_
+#define COMPONENTS_HISTORY_CLUSTERS_CORE_HISTORY_CLUSTER_TYPE_UTILS_H_
 
 #include "components/history_clusters/public/mojom/history_cluster_types.mojom-forward.h"
 
-class Profile;
+class TemplateURLService;
 
 namespace history {
 struct Cluster;
@@ -16,9 +16,9 @@
 namespace history_clusters {
 
 // Creates a `mojom::Cluster` from a `history_clusters::Cluster`.
-mojom::ClusterPtr ClusterToMojom(Profile* profile,
+mojom::ClusterPtr ClusterToMojom(const TemplateURLService* template_url_service,
                                  const history::Cluster cluster);
 
 }  // namespace history_clusters
 
-#endif  // CHROME_BROWSER_UI_WEBUI_HISTORY_CLUSTERS_HISTORY_CLUSTER_TYPE_UTILS_H_
+#endif  // COMPONENTS_HISTORY_CLUSTERS_CORE_HISTORY_CLUSTER_TYPE_UTILS_H_
diff --git a/components/media_router/browser/BUILD.gn b/components/media_router/browser/BUILD.gn
index b4ae651..4db9ab0 100644
--- a/components/media_router/browser/BUILD.gn
+++ b/components/media_router/browser/BUILD.gn
@@ -7,8 +7,6 @@
     "media_router.h",
     "media_router_base.cc",
     "media_router_base.h",
-    "media_router_debugger.cc",
-    "media_router_debugger.h",
     "media_router_dialog_controller.cc",
     "media_router_dialog_controller.h",
     "media_router_factory.cc",
@@ -88,6 +86,8 @@
       "log_util.h",
       "logger_impl.cc",
       "logger_impl.h",
+      "media_router_debugger.cc",
+      "media_router_debugger.h",
       "presentation/presentation_navigation_policy.cc",
       "presentation/presentation_navigation_policy.h",
     ]
@@ -117,7 +117,6 @@
 
   sources = [
     "media_router_base_unittest.cc",
-    "media_router_debugger_unittest.cc",
     "media_router_dialog_controller_unittest.cc",
     "media_router_factory_unittest.cc",
     "media_router_metrics_unittest.cc",
@@ -145,6 +144,7 @@
     sources += [
       "issue_manager_unittest.cc",
       "logger_impl_unittest.cc",
+      "media_router_debugger_unittest.cc",
     ]
   }
 }
diff --git a/components/media_router/browser/media_router_debugger.cc b/components/media_router/browser/media_router_debugger.cc
index 2fc535d..a2f2481 100644
--- a/components/media_router/browser/media_router_debugger.cc
+++ b/components/media_router/browser/media_router_debugger.cc
@@ -4,6 +4,10 @@
 
 #include "components/media_router/browser/media_router_debugger.h"
 
+#include "components/media_router/browser/media_router.h"
+#include "components/media_router/browser/media_router_factory.h"
+#include "content/public/browser/browser_thread.h"
+#include "content/public/browser/web_contents.h"
 #include "media/base/media_switches.h"
 
 namespace media_router {
@@ -11,6 +15,22 @@
 MediaRouterDebugger::MediaRouterDebugger() = default;
 MediaRouterDebugger::~MediaRouterDebugger() = default;
 
+// static.
+MediaRouterDebugger* MediaRouterDebugger::GetForFrameTreeNode(
+    int frame_tree_node_id) {
+  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+  auto* web_contents =
+      content::WebContents::FromFrameTreeNodeId(frame_tree_node_id);
+  if (!web_contents) {
+    return nullptr;
+  }
+
+  auto* media_router = MediaRouterFactory::GetApiForBrowserContextIfExists(
+      web_contents->GetBrowserContext());
+
+  return media_router ? &media_router->GetDebugger() : nullptr;
+}
+
 void MediaRouterDebugger::EnableRtcpReports() {
   is_rtcp_reports_enabled_ = true;
 }
diff --git a/components/media_router/browser/media_router_debugger.h b/components/media_router/browser/media_router_debugger.h
index b878705..cb14499 100644
--- a/components/media_router/browser/media_router_debugger.h
+++ b/components/media_router/browser/media_router_debugger.h
@@ -10,6 +10,10 @@
 // An interface for media router debugging and feedback.
 class MediaRouterDebugger {
  public:
+  // Fetches the MediaRouterDebugger from the media router fetched from the
+  // |frame_tree_node_id|. Must be called on the UiThread. May return a nullptr.
+  static MediaRouterDebugger* GetForFrameTreeNode(int frame_tree_node_id);
+
   MediaRouterDebugger();
 
   MediaRouterDebugger(const MediaRouterDebugger&) = delete;
diff --git a/components/metrics/metrics_log.cc b/components/metrics/metrics_log.cc
index a5710fd..6daa396 100644
--- a/components/metrics/metrics_log.cc
+++ b/components/metrics/metrics_log.cc
@@ -493,9 +493,11 @@
   // call RecordEnvironment() in order to persist the system profile in the
   // persistent histograms .pma file.
   if (has_environment_) {
+    std::string client_uuid = uma_proto_.system_profile().client_uuid();
     uma_proto_.clear_system_profile();
     MetricsLog::RecordCoreSystemProfile(client_,
                                         uma_proto_.mutable_system_profile());
+    uma_proto_.mutable_system_profile()->set_client_uuid(client_uuid);
   }
 
   has_environment_ = true;
diff --git a/components/metrics/metrics_log_unittest.cc b/components/metrics/metrics_log_unittest.cc
index 423bfdca..7320f3a 100644
--- a/components/metrics/metrics_log_unittest.cc
+++ b/components/metrics/metrics_log_unittest.cc
@@ -124,6 +124,7 @@
     EXPECT_TRUE(system_profile.has_channel());
     EXPECT_FALSE(system_profile.has_is_extended_stable_channel());
     EXPECT_TRUE(system_profile.has_application_locale());
+    EXPECT_TRUE(system_profile.has_client_uuid());
 
     const SystemProfileProto::OS& os = system_profile.os();
     EXPECT_TRUE(os.has_name());
diff --git a/components/mirroring/mojom/session_parameters.mojom b/components/mirroring/mojom/session_parameters.mojom
index d9ef4b4..815d1055 100644
--- a/components/mirroring/mojom/session_parameters.mojom
+++ b/components/mirroring/mojom/session_parameters.mojom
@@ -58,4 +58,8 @@
   //
   // TODO(crbug.com/1363512): Remove support for sender side letterboxing.
   bool force_letterboxing;
+
+  // If this is set to true, the Mirroring Service will fetch, analyze, and
+  // store information on the quality of the session using RTCP logs.
+  bool enable_rtcp_reporting;
 };
diff --git a/components/mirroring/service/BUILD.gn b/components/mirroring/service/BUILD.gn
index c6f3d16ef..84d8a65 100644
--- a/components/mirroring/service/BUILD.gn
+++ b/components/mirroring/service/BUILD.gn
@@ -36,6 +36,8 @@
     "rtp_stream.h",
     "session.cc",
     "session.h",
+    "session_logger.cc",
+    "session_logger.h",
     "udp_socket_client.cc",
     "udp_socket_client.h",
     "value_util.cc",
@@ -102,6 +104,7 @@
     "receiver_response_unittest.cc",
     "remoting_sender_unittest.cc",
     "rtp_stream_unittest.cc",
+    "session_logger_unittest.cc",
     "session_unittest.cc",
     "udp_socket_client_unittest.cc",
     "video_capture_client_unittest.cc",
@@ -115,6 +118,7 @@
     "//components/mirroring/mojom:service",
     "//components/openscreen_platform",
     "//media",
+    "//media:test_support",
     "//media/capture/mojom:video_capture",
     "//media/cast:common",
     "//media/cast:net",
diff --git a/components/mirroring/service/session.cc b/components/mirroring/service/session.cc
index 2e4d77b..c70b3e9 100644
--- a/components/mirroring/service/session.cc
+++ b/components/mirroring/service/session.cc
@@ -466,6 +466,7 @@
     audio_input_device_ = nullptr;
   }
   audio_capturing_callback_.reset();
+  session_logger_.reset();
   audio_stream_.reset();
   video_stream_.reset();
   cast_transport_.reset();
@@ -723,6 +724,10 @@
       std::make_unique<TransportClient>(this), std::move(udp_client),
       base::SingleThreadTaskRunner::GetCurrentDefault());
 
+  if (session_params_.enable_rtcp_reporting) {
+    session_logger_ = std::make_unique<SessionLogger>(cast_environment_);
+  }
+
   if (state_ == REMOTING) {
     DCHECK(media_remoter_);
     DCHECK(!audio_config ||
diff --git a/components/mirroring/service/session.h b/components/mirroring/service/session.h
index 2294d426..91ab702 100644
--- a/components/mirroring/service/session.h
+++ b/components/mirroring/service/session.h
@@ -17,6 +17,7 @@
 #include "components/mirroring/service/mirror_settings.h"
 #include "components/mirroring/service/rpc_dispatcher_impl.h"
 #include "components/mirroring/service/rtp_stream.h"
+#include "components/mirroring/service/session_logger.h"
 #include "gpu/config/gpu_info.h"
 #include "media/capture/video/video_capture_feedback.h"
 #include "media/cast/cast_environment.h"
@@ -206,6 +207,7 @@
   std::unique_ptr<viz::Gpu> gpu_;
   SupportedProfiles supported_profiles_;
   mojo::Remote<media::mojom::VideoEncodeAcceleratorProvider> vea_provider_;
+  std::unique_ptr<SessionLogger> session_logger_;
 
   // A callback to call after initialization is completed
   AsyncInitializeDoneCB init_done_cb_;
diff --git a/components/mirroring/service/session_logger.cc b/components/mirroring/service/session_logger.cc
new file mode 100644
index 0000000..ec983fe
--- /dev/null
+++ b/components/mirroring/service/session_logger.cc
@@ -0,0 +1,77 @@
+// Copyright 2023 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/mirroring/service/session_logger.h"
+
+#include "media/cast/logging/receiver_time_offset_estimator_impl.h"
+
+namespace mirroring {
+
+SessionLogger::SessionLogger(
+    scoped_refptr<media::cast::CastEnvironment> cast_environment)
+    : SessionLogger(
+          cast_environment,
+          std::make_unique<media::cast::ReceiverTimeOffsetEstimatorImpl>()) {}
+
+SessionLogger::SessionLogger(
+    scoped_refptr<media::cast::CastEnvironment> cast_environment,
+    std::unique_ptr<media::cast::ReceiverTimeOffsetEstimator> offset_estimator)
+    : cast_environment_(cast_environment),
+      offset_estimator_(std::move(offset_estimator)),
+      video_stats_subscriber_(media::cast::VIDEO_EVENT,
+                              cast_environment->Clock(),
+                              offset_estimator_.get()),
+      audio_stats_subscriber_(media::cast::AUDIO_EVENT,
+                              cast_environment->Clock(),
+                              offset_estimator_.get()) {
+  auto* logger = cast_environment_->logger();
+  if (logger) {
+    SubscribeToLoggingEvents(*logger);
+  }
+}
+
+SessionLogger::~SessionLogger() {
+  auto* logger = cast_environment_->logger();
+  if (logger) {
+    UnsubscribeFromLoggingEvents(*logger);
+  }
+}
+
+base::Value::Dict SessionLogger::GetStats() const {
+  base::Value::Dict video_dict = video_stats_subscriber_.GetStats();
+  base::Value::Dict audio_dict = audio_stats_subscriber_.GetStats();
+
+  base::Value::Dict combined_stats;
+  base::Value::Dict* audio_dict_value = audio_dict.FindDict(
+      media::cast::StatsEventSubscriber::kAudioStatsDictKey);
+  if (audio_dict_value) {
+    combined_stats.Set(media::cast::StatsEventSubscriber::kAudioStatsDictKey,
+                       std::move(*audio_dict_value));
+  }
+
+  base::Value::Dict* video_dict_value = video_dict.FindDict(
+      media::cast::StatsEventSubscriber::kVideoStatsDictKey);
+  if (video_dict_value) {
+    combined_stats.Set(media::cast::StatsEventSubscriber::kVideoStatsDictKey,
+                       std::move(*video_dict_value));
+  }
+
+  return combined_stats;
+}
+
+void SessionLogger::SubscribeToLoggingEvents(
+    media::cast::LogEventDispatcher& logger) {
+  logger.Subscribe(offset_estimator_.get());
+  logger.Subscribe(&video_stats_subscriber_);
+  logger.Subscribe(&audio_stats_subscriber_);
+}
+
+void SessionLogger::UnsubscribeFromLoggingEvents(
+    media::cast::LogEventDispatcher& logger) {
+  logger.Unsubscribe(&video_stats_subscriber_);
+  logger.Unsubscribe(&audio_stats_subscriber_);
+  logger.Unsubscribe(offset_estimator_.get());
+}
+
+}  // namespace mirroring
diff --git a/components/mirroring/service/session_logger.h b/components/mirroring/service/session_logger.h
new file mode 100644
index 0000000..148cc64d
--- /dev/null
+++ b/components/mirroring/service/session_logger.h
@@ -0,0 +1,57 @@
+// Copyright 2023 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_MIRRORING_SERVICE_SESSION_LOGGER_H_
+#define COMPONENTS_MIRRORING_SERVICE_SESSION_LOGGER_H_
+
+#include "base/component_export.h"
+#include "base/memory/raw_ptr.h"
+#include "base/values.h"
+#include "media/cast/cast_environment.h"
+#include "media/cast/logging/log_event_dispatcher.h"
+#include "media/cast/logging/receiver_time_offset_estimator.h"
+#include "media/cast/logging/stats_event_subscriber.h"
+
+class LogEventDispatcher;
+
+namespace mirroring {
+
+// Handles logging and statistics of a mirroring session.
+class COMPONENT_EXPORT(MIRRORING_SERVICE) SessionLogger {
+ public:
+  explicit SessionLogger(
+      scoped_refptr<media::cast::CastEnvironment> cast_environment);
+
+  // Constructor used for testing.
+  SessionLogger(scoped_refptr<media::cast::CastEnvironment> cast_environment,
+                std::unique_ptr<media::cast::ReceiverTimeOffsetEstimator>
+                    offset_estimator);
+
+  SessionLogger(const SessionLogger&) = delete;
+  SessionLogger& operator=(const SessionLogger&) = delete;
+
+  virtual ~SessionLogger();
+
+  // Returns a dictionary containing statistics for the current session. The
+  // dictionary contains two entries - "audio" or "video" pointing to an inner
+  // dictionary. The inner dictionary consists of string - double entries, where
+  // the string describes the name of the stat, and the double describes the
+  // value of the stat. See CastStat and StatsMap of the StatsEventSubscriber
+  // object for more details.
+  base::Value::Dict GetStats() const;
+
+ protected:
+  void SubscribeToLoggingEvents(media::cast::LogEventDispatcher& logger);
+  void UnsubscribeFromLoggingEvents(media::cast::LogEventDispatcher& logger);
+
+  scoped_refptr<media::cast::CastEnvironment> cast_environment_;
+
+  std::unique_ptr<media::cast::ReceiverTimeOffsetEstimator> offset_estimator_;
+  media::cast::StatsEventSubscriber video_stats_subscriber_;
+  media::cast::StatsEventSubscriber audio_stats_subscriber_;
+};
+
+}  // namespace mirroring
+
+#endif  // COMPONENTS_MIRRORING_SERVICE_SESSION_LOGGER_H_
diff --git a/components/mirroring/service/session_logger_unittest.cc b/components/mirroring/service/session_logger_unittest.cc
new file mode 100644
index 0000000..c304021
--- /dev/null
+++ b/components/mirroring/service/session_logger_unittest.cc
@@ -0,0 +1,205 @@
+// Copyright 2023 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/mirroring/service/session_logger.h"
+
+#include "base/base64.h"
+#include "base/functional/bind.h"
+#include "base/functional/callback.h"
+#include "base/json/json_reader.h"
+#include "base/logging.h"
+#include "base/test/mock_callback.h"
+#include "base/values.h"
+#include "components/mirroring/service/value_util.h"
+#include "media/base/fake_single_thread_task_runner.h"
+#include "media/cast/cast_environment.h"
+#include "media/cast/common/frame_id.h"
+#include "media/cast/common/rtp_time.h"
+#include "media/cast/logging/logging_defines.h"
+#include "media/cast/logging/stats_event_subscriber.h"
+#include "media/cast/test/fake_receiver_time_offset_estimator.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using ::media::FakeSingleThreadTaskRunner;
+using ::media::cast::AUDIO_EVENT;
+using ::media::cast::CastEnvironment;
+using ::media::cast::EventMediaType;
+using ::media::cast::FRAME_CAPTURE_BEGIN;
+using ::media::cast::FRAME_CAPTURE_END;
+using ::media::cast::FRAME_ENCODED;
+using ::media::cast::FrameEvent;
+using ::media::cast::FrameId;
+using ::media::cast::RtpTimeDelta;
+using ::media::cast::RtpTimeTicks;
+using ::media::cast::StatsEventSubscriber;
+using ::media::cast::VIDEO_EVENT;
+using ::media::cast::test::FakeReceiverTimeOffsetEstimator;
+
+namespace mirroring {
+
+namespace {
+const int kReceiverOffsetSecs = 100;
+const size_t kMaxFrameInfoMapSize = 100UL;
+}  // namespace
+
+class SessionLoggerTest : public ::testing::Test {
+ public:
+  SessionLoggerTest()
+      : task_runner_(new FakeSingleThreadTaskRunner(&sender_clock_)),
+        cast_environment_(new CastEnvironment(&sender_clock_,
+                                              task_runner_,
+                                              task_runner_,
+                                              task_runner_)),
+        fake_offset_estimator_(
+            std::make_unique<FakeReceiverTimeOffsetEstimator>(
+                base::Seconds(kReceiverOffsetSecs))) {
+    receiver_clock_.Advance(base::Seconds(kReceiverOffsetSecs));
+  }
+
+  SessionLoggerTest(const SessionLoggerTest&) = delete;
+  SessionLoggerTest& operator=(const SessionLoggerTest&) = delete;
+
+  ~SessionLoggerTest() override = default;
+
+ protected:
+  void AdvanceClocks(base::TimeDelta delta) {
+    task_runner_->Sleep(delta);
+    receiver_clock_.Advance(delta);
+  }
+
+  void Init() {
+    DCHECK(!session_logger_.get());
+    session_logger_ = std::make_unique<SessionLogger>(
+        cast_environment_, std::move(fake_offset_estimator_));
+  }
+
+  void DispatchFrameEvent(std::unique_ptr<FrameEvent> frame_event) {
+    cast_environment_->logger()->DispatchFrameEvent(std::move(frame_event));
+  }
+
+  std::unique_ptr<FrameEvent> ConstructCaptureBeginEvent(
+      EventMediaType event_media_type,
+      RtpTimeTicks rtp_timestamp) {
+    std::unique_ptr<FrameEvent> capture_begin_event =
+        std::make_unique<FrameEvent>();
+    capture_begin_event->timestamp = sender_clock_.NowTicks();
+    capture_begin_event->type = FRAME_CAPTURE_BEGIN;
+    capture_begin_event->media_type = event_media_type;
+    capture_begin_event->rtp_timestamp = rtp_timestamp;
+
+    return capture_begin_event;
+  }
+
+  std::unique_ptr<FrameEvent> ConstructCaptureEndEvent(
+      EventMediaType event_media_type,
+      RtpTimeTicks rtp_timestamp) {
+    std::unique_ptr<FrameEvent> capture_end_event =
+        std::make_unique<FrameEvent>();
+    capture_end_event->timestamp = sender_clock_.NowTicks();
+    capture_end_event->type = FRAME_CAPTURE_END;
+    capture_end_event->media_type = event_media_type;
+    capture_end_event->rtp_timestamp = rtp_timestamp;
+
+    return capture_end_event;
+  }
+
+  std::unique_ptr<FrameEvent> ConstructEncodeEvent(
+      EventMediaType event_media_type,
+      RtpTimeTicks rtp_timestamp,
+      FrameId frame_id) {
+    std::unique_ptr<FrameEvent> encode_event = std::make_unique<FrameEvent>();
+    encode_event->timestamp = sender_clock_.NowTicks();
+    encode_event->type = FRAME_ENCODED;
+    encode_event->media_type = event_media_type;
+    encode_event->rtp_timestamp = rtp_timestamp;
+    encode_event->frame_id = frame_id;
+    encode_event->size = 1024;
+    encode_event->key_frame = true;
+    encode_event->target_bitrate = 5678;
+    encode_event->encoder_cpu_utilization = 9.10;
+    encode_event->idealized_bitrate_utilization = 11.12;
+
+    return encode_event;
+  }
+
+  base::SimpleTestTickClock sender_clock_;
+  base::SimpleTestTickClock receiver_clock_;
+  scoped_refptr<FakeSingleThreadTaskRunner> task_runner_;
+  scoped_refptr<CastEnvironment> cast_environment_;
+  std::unique_ptr<FakeReceiverTimeOffsetEstimator> fake_offset_estimator_;
+  std::unique_ptr<SessionLogger> session_logger_;
+};
+
+TEST_F(SessionLoggerTest, CaptureEncode) {
+  // Using RTP events, test that the StatEventSubscribers are parsing and
+  // storing statistics.
+  Init();
+
+  RtpTimeTicks rtp_timestamp;
+  FrameId frame_id = FrameId::first();
+
+  const int num_frames = kMaxFrameInfoMapSize + 50;
+
+  // Drop half the frames during the encode step.
+  for (int i = 0; i < num_frames; i++) {
+    DispatchFrameEvent(ConstructCaptureBeginEvent(VIDEO_EVENT, rtp_timestamp));
+    DispatchFrameEvent(ConstructCaptureBeginEvent(AUDIO_EVENT, rtp_timestamp));
+
+    AdvanceClocks(base::Microseconds(10));
+
+    DispatchFrameEvent(ConstructCaptureEndEvent(VIDEO_EVENT, rtp_timestamp));
+    DispatchFrameEvent(ConstructCaptureEndEvent(AUDIO_EVENT, rtp_timestamp));
+
+    if (i % 2 == 0) {
+      AdvanceClocks(base::Microseconds(10));
+
+      DispatchFrameEvent(
+          ConstructEncodeEvent(VIDEO_EVENT, rtp_timestamp, frame_id));
+      DispatchFrameEvent(
+          ConstructEncodeEvent(AUDIO_EVENT, rtp_timestamp, frame_id));
+    }
+    AdvanceClocks(base::Microseconds(34567));
+    rtp_timestamp += RtpTimeDelta::FromTicks(90);
+    frame_id++;
+  }
+
+  auto stats_dict = session_logger_->GetStats();
+
+  // Check that the GetStats() dict has been populated with audio stats.
+  const base::Value::Dict* audio_dict =
+      stats_dict.FindDict(StatsEventSubscriber::kAudioStatsDictKey);
+  EXPECT_TRUE(audio_dict);
+  EXPECT_TRUE(audio_dict->contains("CAPTURE_FPS"));
+  EXPECT_TRUE(audio_dict->FindDouble("CAPTURE_FPS").has_value());
+
+  EXPECT_TRUE(audio_dict->contains("NUM_FRAMES_CAPTURED"));
+  EXPECT_TRUE(audio_dict->FindDouble("NUM_FRAMES_CAPTURED").has_value());
+
+  EXPECT_TRUE(audio_dict->contains("NUM_FRAMES_DROPPED_BY_ENCODER"));
+  EXPECT_TRUE(
+      audio_dict->FindDouble("NUM_FRAMES_DROPPED_BY_ENCODER").has_value());
+
+  EXPECT_TRUE(audio_dict->contains("AVG_CAPTURE_LATENCY_MS"));
+  EXPECT_TRUE(audio_dict->FindDouble("AVG_CAPTURE_LATENCY_MS").has_value());
+
+  // Check that the GetStats() dict has been populated with video stats.
+  const base::Value::Dict* video_dict =
+      stats_dict.FindDict(StatsEventSubscriber::kVideoStatsDictKey);
+  EXPECT_TRUE(video_dict);
+  EXPECT_TRUE(video_dict->contains("CAPTURE_FPS"));
+  EXPECT_TRUE(video_dict->FindDouble("CAPTURE_FPS").has_value());
+
+  EXPECT_TRUE(video_dict->contains("NUM_FRAMES_CAPTURED"));
+  EXPECT_TRUE(video_dict->FindDouble("NUM_FRAMES_CAPTURED").has_value());
+
+  EXPECT_TRUE(video_dict->contains("NUM_FRAMES_DROPPED_BY_ENCODER"));
+  EXPECT_TRUE(
+      video_dict->FindDouble("NUM_FRAMES_DROPPED_BY_ENCODER").has_value());
+
+  EXPECT_TRUE(video_dict->contains("AVG_CAPTURE_LATENCY_MS"));
+  EXPECT_TRUE(video_dict->FindDouble("AVG_CAPTURE_LATENCY_MS").has_value());
+}
+
+}  // namespace mirroring
diff --git a/components/policy/core/common/features.cc b/components/policy/core/common/features.cc
index b8996a3..d02a5bf 100644
--- a/components/policy/core/common/features.cc
+++ b/components/policy/core/common/features.cc
@@ -28,7 +28,7 @@
 
 BASE_FEATURE(kActivateMetricsReportingEnabledPolicyAndroid,
              "ActivateMetricsReportingEnabledPolicyAndroid",
-             base::FEATURE_DISABLED_BY_DEFAULT);
+             base::FEATURE_ENABLED_BY_DEFAULT);
 
 BASE_FEATURE(kDmTokenDeletion,
              "DmTokenDeletion",
diff --git a/components/security_state/core/security_state.cc b/components/security_state/core/security_state.cc
index 5faa207..eb8f4e76b 100644
--- a/components/security_state/core/security_state.cc
+++ b/components/security_state/core/security_state.cc
@@ -66,18 +66,18 @@
     return DANGEROUS;
   }
 
-  // If the navigation was upgraded to HTTPS because of HTTPS-Only Mode, but did
-  // not succeed (either currently showing the HTTPS-Only Mode interstitial, or
-  // the navigation fell back to HTTP), set the security level to WARNING. The
-  // HTTPS-Only Mode interstitial warning is considered "less serious" than the
-  // general certificate error interstitials.
+  // If the navigation was upgraded to HTTPS because of HTTPS-First Mode, but
+  // did not succeed and is showing the HTTPS-First Mode interstitial, set the
+  // security level to WARNING. The HTTPS-First Mode interstitial warning is
+  // considered "less serious" than the general certificate error interstitials.
   //
   // This check must come before the checks for `connection_info_initialized`
-  // (because the HTTPS-Only Mode intersitital can trigger if the HTTPS version
-  // of the page does not commit) and certificate errors (because the HTTPS-Only
-  // Mode interstitial takes precedent if the certificate error occurred due to
-  // an upgraded main-frame navigation).
-  if (visible_security_state.is_https_only_mode_upgraded) {
+  // (because the HTTPS-First Mode intersitital can trigger if the HTTPS version
+  // of the page does not commit) and certificate errors (because the
+  // HTTPS-First Mode interstitial takes precedent if the certificate error
+  // occurred due to an upgraded main-frame navigation).
+  if (visible_security_state.is_https_only_mode_upgraded &&
+      visible_security_state.is_error_page) {
     return WARNING;
   }
 
diff --git a/components/security_state/core/security_state_unittest.cc b/components/security_state/core/security_state_unittest.cc
index 8c820cb..4c430395 100644
--- a/components/security_state/core/security_state_unittest.cc
+++ b/components/security_state/core/security_state_unittest.cc
@@ -474,6 +474,7 @@
   helper.set_cert_status(net::CERT_STATUS_SHA1_SIGNATURE_PRESENT |
                          net::CERT_STATUS_UNABLE_TO_CHECK_REVOCATION);
   EXPECT_TRUE(helper.HasMajorCertificateError());
+  helper.set_is_error_page(true);
   helper.set_is_https_only_mode_upgraded(true);
   EXPECT_EQ(SecurityLevel::WARNING, helper.GetSecurityLevel());
 }
@@ -483,6 +484,7 @@
   TestSecurityStateHelper helper;
   helper.set_malicious_content_status(
       MALICIOUS_CONTENT_STATUS_SOCIAL_ENGINEERING);
+  helper.set_is_error_page(true);
   helper.set_is_https_only_mode_upgraded(true);
   EXPECT_EQ(DANGEROUS, helper.GetSecurityLevel());
 }
diff --git a/components/signin/internal/identity_manager/DEPS b/components/signin/internal/identity_manager/DEPS
index 4374378..b2b6d8c 100644
--- a/components/signin/internal/identity_manager/DEPS
+++ b/components/signin/internal/identity_manager/DEPS
@@ -5,5 +5,6 @@
   "+components/signin/public/webdata",
   "+components/signin/public/android/jni_headers",
   "+components/signin/public/android/test_support_jni_headers",
+  "+google_apis",
   "+mojo/public",
 ]
diff --git a/components/signin/internal/identity_manager/gaia_cookie_manager_service.cc b/components/signin/internal/identity_manager/gaia_cookie_manager_service.cc
index 19a611cc..e64f7a6 100644
--- a/components/signin/internal/identity_manager/gaia_cookie_manager_service.cc
+++ b/components/signin/internal/identity_manager/gaia_cookie_manager_service.cc
@@ -31,6 +31,7 @@
 #include "components/signin/public/base/signin_metrics.h"
 #include "components/signin/public/base/signin_pref_names.h"
 #include "components/signin/public/identity_manager/set_accounts_in_cookie_result.h"
+#include "google_apis/credentials_mode.h"
 #include "google_apis/gaia/gaia_constants.h"
 #include "google_apis/gaia/gaia_urls.h"
 #include "net/base/load_flags.h"
@@ -347,7 +348,8 @@
 
   auto request = std::make_unique<network::ResourceRequest>();
   request->url = url;
-  request->credentials_mode = network::mojom::CredentialsMode::kOmit;
+  request->credentials_mode =
+      google_apis::GetOmitCredentialsModeForGaiaRequests();
 
   std::unique_ptr<network::SimpleURLLoader> loader =
       network::SimpleURLLoader::Create(std::move(request), traffic_annotation);
diff --git a/components/sync/driver/sync_stopped_reporter.cc b/components/sync/driver/sync_stopped_reporter.cc
index e388680..cebc3f35 100644
--- a/components/sync/driver/sync_stopped_reporter.cc
+++ b/components/sync/driver/sync_stopped_reporter.cc
@@ -12,6 +12,7 @@
 #include "base/metrics/histogram_functions.h"
 #include "base/strings/stringprintf.h"
 #include "components/sync/protocol/sync.pb.h"
+#include "google_apis/credentials_mode.h"
 #include "net/base/load_flags.h"
 #include "net/http/http_status_code.h"
 #include "net/traffic_annotation/network_traffic_annotation.h"
@@ -110,7 +111,8 @@
   resource_request->url = sync_event_url_;
   resource_request->load_flags =
       net::LOAD_BYPASS_CACHE | net::LOAD_DISABLE_CACHE;
-  resource_request->credentials_mode = network::mojom::CredentialsMode::kOmit;
+  resource_request->credentials_mode =
+      google_apis::GetOmitCredentialsModeForGaiaRequests();
   resource_request->method = "POST";
   resource_request->headers.SetHeader(
       net::HttpRequestHeaders::kAuthorization,
diff --git a/components/sync/driver/trusted_vault_histograms.h b/components/sync/driver/trusted_vault_histograms.h
index ba3dc6b..5a01b05 100644
--- a/components/sync/driver/trusted_vault_histograms.h
+++ b/components/sync/driver/trusted_vault_histograms.h
@@ -27,7 +27,9 @@
   kThrottledClientSide = 2,
   kAttemptingRegistrationWithNewKeyPair = 3,
   kAttemptingRegistrationWithExistingKeyPair = 4,
-  kAttemptingRegistrationWithPersistentAuthError = 5,
+  // Deprecated, replaced with more detailed
+  // TrustedVaultDeviceRegistrationOutcomeForUMA.
+  kDeprecatedAttemptingRegistrationWithPersistentAuthError = 5,
   kAlreadyRegisteredV1 = 6,
   kMaxValue = kAlreadyRegisteredV1,
 };
diff --git a/components/sync/engine/model_type_processor_metrics.cc b/components/sync/engine/model_type_processor_metrics.cc
index 3cc6add1..92dcdfbf 100644
--- a/components/sync/engine/model_type_processor_metrics.cc
+++ b/components/sync/engine/model_type_processor_metrics.cc
@@ -35,6 +35,13 @@
 
 void LogNonReflectionUpdateFreshnessToUma(ModelType type,
                                           base::Time remote_modification_time) {
+  // TODO(crbug.com/1417105): This metric is only meaningful for incremental
+  // updates, and currently ApplyUpdatesImmediatelyTypes() don't properly
+  // distinguish incremental vs initial/full updates.
+  if (ApplyUpdatesImmediatelyTypes().Has(type)) {
+    return;
+  }
+
   const base::TimeDelta freshness =
       base::Time::Now() - remote_modification_time;
 
diff --git a/components/sync/engine/net/http_bridge.cc b/components/sync/engine/net/http_bridge.cc
index 8e5c296a..387aa93 100644
--- a/components/sync/engine/net/http_bridge.cc
+++ b/components/sync/engine/net/http_bridge.cc
@@ -17,6 +17,7 @@
 #include "base/task/thread_pool.h"
 #include "base/threading/thread_restrictions.h"
 #include "components/variations/net/variations_http_headers.h"
+#include "google_apis/credentials_mode.h"
 #include "net/base/load_flags.h"
 #include "net/base/net_errors.h"
 #include "net/http/http_request_headers.h"
@@ -242,7 +243,8 @@
   resource_request->method = "POST";
   resource_request->load_flags =
       net::LOAD_BYPASS_CACHE | net::LOAD_DISABLE_CACHE;
-  resource_request->credentials_mode = network::mojom::CredentialsMode::kOmit;
+  resource_request->credentials_mode =
+      google_apis::GetOmitCredentialsModeForGaiaRequests();
 
   if (!extra_headers_.empty())
     resource_request->headers.AddHeadersFromString(extra_headers_);
diff --git a/components/sync/trusted_vault/standalone_trusted_vault_backend.cc b/components/sync/trusted_vault/standalone_trusted_vault_backend.cc
index aa45d56f..5548eb25 100644
--- a/components/sync/trusted_vault/standalone_trusted_vault_backend.cc
+++ b/components/sync/trusted_vault/standalone_trusted_vault_backend.cc
@@ -875,10 +875,6 @@
   }
 
   DCHECK(ongoing_device_registration_request_);
-  if (has_persistent_auth_error_) {
-    return TrustedVaultDeviceRegistrationStateForUMA::
-        kAttemptingRegistrationWithPersistentAuthError;
-  }
 
   return had_generated_key_pair ? TrustedVaultDeviceRegistrationStateForUMA::
                                       kAttemptingRegistrationWithExistingKeyPair
diff --git a/components/sync/trusted_vault/standalone_trusted_vault_backend_unittest.cc b/components/sync/trusted_vault/standalone_trusted_vault_backend_unittest.cc
index e0fd3a79..af1c379 100644
--- a/components/sync/trusted_vault/standalone_trusted_vault_backend_unittest.cc
+++ b/components/sync/trusted_vault/standalone_trusted_vault_backend_unittest.cc
@@ -950,7 +950,7 @@
 }
 
 TEST_F(StandaloneTrustedVaultBackendTest,
-       ShouldRecordAuthErrorAndAttemptDeviceRegistration) {
+       ShouldRetryDeviceRegistrationWhenAuthErrorResolved) {
   const CoreAccountInfo account_info = MakeAccountInfoWithGaiaId("user");
   const std::vector<uint8_t> kVaultKey = {1, 2, 3};
   const int kLastKeyVersion = 1;
@@ -970,7 +970,7 @@
       "Sync.TrustedVaultDeviceRegistrationState",
       /*sample=*/
       TrustedVaultDeviceRegistrationStateForUMA::
-          kAttemptingRegistrationWithPersistentAuthError,
+          kAttemptingRegistrationWithNewKeyPair,
       /*expected_bucket_count=*/1);
 
   Mock::VerifyAndClearExpectations(connection());
@@ -989,7 +989,7 @@
   // The second attempt should NOT have logged the histogram, following the
   // histogram's definition that it should be logged once.
   histogram_tester2.ExpectTotalCount("Sync.TrustedVaultDeviceRegistrationState",
-                                     /*count=*/0);
+                                     /*expected_count=*/0);
 }
 
 TEST_F(StandaloneTrustedVaultBackendTest,
diff --git a/components/sync/trusted_vault/trusted_vault_request.cc b/components/sync/trusted_vault/trusted_vault_request.cc
index 26611a6..b62a69d1 100644
--- a/components/sync/trusted_vault/trusted_vault_request.cc
+++ b/components/sync/trusted_vault/trusted_vault_request.cc
@@ -12,6 +12,7 @@
 #include "components/sync/driver/trusted_vault_histograms.h"
 #include "components/sync/trusted_vault/trusted_vault_access_token_fetcher.h"
 #include "components/sync/trusted_vault/trusted_vault_server_constants.h"
+#include "google_apis/credentials_mode.h"
 #include "net/base/url_util.h"
 #include "net/http/http_request_headers.h"
 #include "net/http/http_status_code.h"
@@ -194,7 +195,8 @@
       net::AppendQueryParameter(request_url_, kQueryParameterAlternateOutputKey,
                                 kQueryParameterAlternateOutputProto);
   request->method = GetHttpMethodString(http_method_);
-  request->credentials_mode = network::mojom::CredentialsMode::kOmit;
+  request->credentials_mode =
+      google_apis::GetOmitCredentialsModeForGaiaRequests();
   request->headers.SetHeader(
       kAuthorizationHeader,
       /*value=*/base::StringPrintf("Bearer %s", access_token.c_str()));
diff --git a/content/browser/attribution_reporting/attribution_host.cc b/content/browser/attribution_reporting/attribution_host.cc
index 952a56f2..c107f38e 100644
--- a/content/browser/attribution_reporting/attribution_host.cc
+++ b/content/browser/attribution_reporting/attribution_host.cc
@@ -38,6 +38,7 @@
 #include "third_party/blink/public/common/features.h"
 #include "third_party/blink/public/common/navigation/impression.h"
 #include "third_party/blink/public/mojom/conversions/attribution_data_host.mojom.h"
+#include "third_party/blink/public/mojom/permissions_policy/permissions_policy.mojom-shared.h"
 #include "url/gurl.h"
 #include "url/origin.h"
 
@@ -450,6 +451,11 @@
     return;
   }
 
+  if (!initiator_frame_host->IsFeatureEnabled(
+          blink::mojom::PermissionsPolicyFeature::kAttributionReporting)) {
+    return;
+  }
+
   AttributionManager* attribution_manager =
       AttributionManager::FromWebContents(web_contents());
   if (!attribution_manager) {
diff --git a/content/browser/attribution_reporting/attribution_host.h b/content/browser/attribution_reporting/attribution_host.h
index b0da1da..372a266 100644
--- a/content/browser/attribution_reporting/attribution_host.h
+++ b/content/browser/attribution_reporting/attribution_host.h
@@ -64,6 +64,8 @@
   // for reportEvent or for an automatic beacon. It may be cached and sent
   // later. This should be called before the navigation committed for a
   // navigation beacon.
+  // This function should only be invoked if Attribution Reporting API is
+  // enabled on the page.
   void NotifyFencedFrameReportingBeaconStarted(
       BeaconId beacon_id,
       RenderFrameHostImpl* initiator_frame_host);
diff --git a/content/browser/attribution_reporting/attribution_host_unittest.cc b/content/browser/attribution_reporting/attribution_host_unittest.cc
index 70f42ac..97fea5e 100644
--- a/content/browser/attribution_reporting/attribution_host_unittest.cc
+++ b/content/browser/attribution_reporting/attribution_host_unittest.cc
@@ -5,6 +5,7 @@
 #include "content/browser/attribution_reporting/attribution_host.h"
 
 #include <memory>
+#include <vector>
 
 #include "base/memory/raw_ptr.h"
 #include "base/test/metrics/histogram_tester.h"
@@ -31,8 +32,11 @@
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "third_party/blink/public/common/features.h"
+#include "third_party/blink/public/common/permissions_policy/origin_with_possible_wildcards.h"
+#include "third_party/blink/public/common/permissions_policy/permissions_policy_declaration.h"
 #include "third_party/blink/public/common/tokens/tokens.h"
 #include "third_party/blink/public/mojom/conversions/conversions.mojom.h"
+#include "third_party/blink/public/mojom/permissions_policy/permissions_policy.mojom-shared.h"
 #include "url/gurl.h"
 #include "url/origin.h"
 
@@ -582,7 +586,7 @@
       {kLocalHost, true},
       {"http://127.0.0.1", true},
       {"http://insecure.com", false},
-      {"https:/secore.com", true},
+      {"https:/secure.com", true},
   };
 
   NavigationBeaconId navigation_id(123);
@@ -613,5 +617,45 @@
   }
 }
 
+TEST_F(AttributionHostTest, FencedFrameReportingBeacon_FeaturePolicyChecked) {
+  contents()->NavigateAndCommit(GURL("https://secure.com"));
+
+  RenderFrameHost* fenced_frame =
+      RenderFrameHostTester::For(main_rfh())
+          ->AppendFencedFrame(blink::mojom::FencedFrameMode::kOpaqueAds);
+
+  static constexpr char kAllowedOriginUrl[] = "https://a.test";
+
+  const struct {
+    const char* fenced_frame_url;
+    bool expected;
+  } kTestCases[] = {
+      {kAllowedOriginUrl, true},
+      {"https://b.test", false},
+  };
+
+  for (const auto& test_case : kTestCases) {
+    EXPECT_CALL(*mock_data_host_manager(),
+                NotifyFencedFrameReportingBeaconStarted)
+        .Times(test_case.expected);
+
+    auto simulator = NavigationSimulatorImpl::CreateRendererInitiated(
+        GURL(test_case.fenced_frame_url), fenced_frame);
+    simulator->SetPermissionsPolicyHeader(
+        {blink::ParsedPermissionsPolicyDeclaration(
+            blink::mojom::PermissionsPolicyFeature::kAttributionReporting,
+            /*allowed_origins=*/
+            {blink::OriginWithPossibleWildcards(
+                url::Origin::Create(GURL(kAllowedOriginUrl)),
+                /*has_subdomain_wildcard=*/false)},
+            /*matches_all_origins=*/false, /*matches_opaque_src=*/false)});
+    simulator->Commit();
+    fenced_frame = simulator->GetFinalRenderFrameHost();
+
+    conversion_host()->NotifyFencedFrameReportingBeaconStarted(
+        EventBeaconId(123), static_cast<RenderFrameHostImpl*>(fenced_frame));
+  }
+}
+
 }  // namespace
 }  // namespace content
diff --git a/content/browser/child_process_launcher_helper_mac.cc b/content/browser/child_process_launcher_helper_mac.cc
index 57a5e8af..27cfd07 100644
--- a/content/browser/child_process_launcher_helper_mac.cc
+++ b/content/browser/child_process_launcher_helper_mac.cc
@@ -55,7 +55,8 @@
     return *cache;
   }
 
-  sandbox::mac::SandboxPolicy* Query(sandbox::mojom::Sandbox sandbox_type) {
+  const sandbox::mac::SandboxPolicy* Query(
+      sandbox::mojom::Sandbox sandbox_type) {
     base::AutoLock lock(lock_);
     auto it = cache_.find(sandbox_type);
     if (it == cache_.end())
@@ -138,7 +139,7 @@
     // problem.
     options->environment.insert(std::make_pair("OS_ACTIVITY_MODE", "disable"));
 
-    auto* cached_policy = SandboxProfileCache::Get().Query(sandbox_type);
+    const auto* cached_policy = SandboxProfileCache::Get().Query(sandbox_type);
     if (cached_policy) {
       policy_ = *cached_policy;
     } else {
diff --git a/content/browser/devtools/protocol/target_handler.cc b/content/browser/devtools/protocol/target_handler.cc
index 3e49127d..2a4010833 100644
--- a/content/browser/devtools/protocol/target_handler.cc
+++ b/content/browser/devtools/protocol/target_handler.cc
@@ -241,8 +241,7 @@
     base::StringPiece message_sp(reinterpret_cast<const char*>(message.data()),
                                  message.size());
     if (agent_host == page_host_.get()) {
-      std::unique_ptr<base::Value> value =
-          base::JSONReader::ReadDeprecated(message_sp);
+      absl::optional<base::Value> value = base::JSONReader::Read(message_sp);
       if (!value || !value->is_dict()) {
         return;
       }
diff --git a/content/browser/loader/navigation_early_hints_browsertest.cc b/content/browser/loader/navigation_early_hints_browsertest.cc
index 0720a87..21f4950 100644
--- a/content/browser/loader/navigation_early_hints_browsertest.cc
+++ b/content/browser/loader/navigation_early_hints_browsertest.cc
@@ -149,9 +149,8 @@
 class NavigationEarlyHintsTest : public ContentBrowserTest {
  public:
   NavigationEarlyHintsTest() {
-    feature_list_.InitWithFeatures(
-        {net::features::kSplitCacheByNetworkIsolationKey},
-        {net::features::kForceIsolationInfoFrameOriginToTopLevelFrame});
+    feature_list_.InitAndEnableFeature(
+        net::features::kSplitCacheByNetworkIsolationKey);
   }
   ~NavigationEarlyHintsTest() override = default;
 
diff --git a/content/browser/navigation_browsertest.cc b/content/browser/navigation_browsertest.cc
index 5fb1122f..11ed7e2 100644
--- a/content/browser/navigation_browsertest.cc
+++ b/content/browser/navigation_browsertest.cc
@@ -349,24 +349,6 @@
   }
 };
 
-class NetworkDoubleKeyIsolationNavigationBrowserTest
-    : public ContentBrowserTest {
- public:
-  NetworkDoubleKeyIsolationNavigationBrowserTest() {
-    scoped_feature_list_.InitWithFeatures(
-        {net::features::kForceIsolationInfoFrameOriginToTopLevelFrame}, {});
-  }
-
- protected:
-  void SetUpOnMainThread() override {
-    ASSERT_TRUE(embedded_test_server()->Start());
-    ContentBrowserTest::SetUpOnMainThread();
-  }
-
- private:
-  base::test::ScopedFeatureList scoped_feature_list_;
-};
-
 class NavigationBrowserTestReferrerPolicy
     : public NavigationBrowserTest,
       public ::testing::WithParamInterface<network::mojom::ReferrerPolicy> {
@@ -948,46 +930,6 @@
           .IsEqualForTesting(iframe_request->trusted_params->isolation_info));
 }
 
-IN_PROC_BROWSER_TEST_F(NetworkDoubleKeyIsolationNavigationBrowserTest,
-                       SubframeDoubleKeyNetworkIsolation) {
-  GURL url_top(embedded_test_server()->GetURL("/page_with_iframe.html"));
-  GURL url_iframe = embedded_test_server()->GetURL("/title1.html");
-  url::Origin origin = url::Origin::Create(url_top);
-  URLLoaderMonitor monitor({url_iframe});
-  EXPECT_TRUE(NavigateToURL(shell(), url_top));
-  monitor.WaitForUrls();
-
-  absl::optional<network::ResourceRequest> main_frame_request =
-      monitor.GetRequestInfo(url_top);
-  ASSERT_TRUE(main_frame_request.has_value());
-  ASSERT_TRUE(main_frame_request->trusted_params);
-  EXPECT_TRUE(net::IsolationInfo::Create(
-                  net::IsolationInfo::RequestType::kMainFrame, origin, origin,
-                  net::SiteForCookies::FromOrigin(origin),
-                  std::set<net::SchemefulSite>())
-                  .IsEqualForTesting(
-                      main_frame_request->trusted_params->isolation_info));
-
-  absl::optional<network::ResourceRequest> iframe_request =
-      monitor.GetRequestInfo(url_iframe);
-  ASSERT_TRUE(iframe_request->trusted_params);
-
-  // IsolationInfo and NIK of subframe should only reflect the main_frame's
-  // origin when these flags are on because double key does not include the
-  // subframe's origin.
-  EXPECT_TRUE(
-      net::IsolationInfo::Create(net::IsolationInfo::RequestType::kSubFrame,
-                                 origin, origin,
-                                 net::SiteForCookies::FromOrigin(origin),
-                                 std::set<net::SchemefulSite>())
-          .IsEqualForTesting(iframe_request->trusted_params->isolation_info));
-
-  EXPECT_EQ(
-      main_frame_request->trusted_params->isolation_info
-          .network_isolation_key(),
-      iframe_request->trusted_params->isolation_info.network_isolation_key());
-}
-
 // Tests that the initiator is not set for a browser initiated top frame
 // navigation.
 IN_PROC_BROWSER_TEST_F(NavigationBrowserTest, BrowserNavigationInitiator) {
@@ -4506,9 +4448,8 @@
     : public NavigationBaseBrowserTest {
  public:
   NetworkIsolationSplitCacheAppendIframeOrigin() {
-    feature_list_.InitWithFeatures(
-        {net::features::kSplitCacheByNetworkIsolationKey},
-        {net::features::kForceIsolationInfoFrameOriginToTopLevelFrame});
+    feature_list_.InitAndEnableFeature(
+        net::features::kSplitCacheByNetworkIsolationKey);
   }
 
  private:
diff --git a/content/browser/network/split_cache_browsertest.cc b/content/browser/network/split_cache_browsertest.cc
index 5a013e39..076cac8 100644
--- a/content/browser/network/split_cache_browsertest.cc
+++ b/content/browser/network/split_cache_browsertest.cc
@@ -391,9 +391,8 @@
     : public SplitCacheContentBrowserTest {
  public:
   SplitCacheRegistrableDomainContentBrowserTest() {
-    feature_list_.InitWithFeatures(
-        {net::features::kSplitCacheByNetworkIsolationKey},
-        {net::features::kForceIsolationInfoFrameOriginToTopLevelFrame});
+    feature_list_.InitAndEnableFeature(
+        net::features::kSplitCacheByNetworkIsolationKey);
   }
 
  private:
@@ -406,11 +405,9 @@
  public:
   SplitCacheContentBrowserTestEnabled() {
     std::vector<base::test::FeatureRef> enabled_features;
+    std::vector<base::test::FeatureRef> disabled_features;
     enabled_features.push_back(net::features::kSplitCacheByNetworkIsolationKey);
 
-    std::vector<base::test::FeatureRef> disabled_features;
-    disabled_features.push_back(
-        net::features::kForceIsolationInfoFrameOriginToTopLevelFrame);
     // When the test parameter is true, we test the split cache with
     // PlzDedicatedWorker enabled.
     if (GetParam())
diff --git a/content/browser/renderer_host/render_widget_host_view_ios.h b/content/browser/renderer_host/render_widget_host_view_ios.h
index 4dfd088..4d72f14 100644
--- a/content/browser/renderer_host/render_widget_host_view_ios.h
+++ b/content/browser/renderer_host/render_widget_host_view_ios.h
@@ -128,6 +128,10 @@
   void SetCurrentDeviceScaleFactor(float device_scale_factor) override;
   void UpdateScreenInfo() override;
   void TransformPointToRootSurface(gfx::PointF* point) override;
+  bool TransformPointToCoordSpaceForView(
+      const gfx::PointF& point,
+      RenderWidgetHostViewBase* target_view,
+      gfx::PointF* transformed_point) override;
   void ProcessAckedTouchEvent(
       const TouchEventWithLatencyInfo& touch,
       blink::mojom::InputEventResultState ack_result) override;
diff --git a/content/browser/renderer_host/render_widget_host_view_ios.mm b/content/browser/renderer_host/render_widget_host_view_ios.mm
index 93b58466..e270242 100644
--- a/content/browser/renderer_host/render_widget_host_view_ios.mm
+++ b/content/browser/renderer_host/render_widget_host_view_ios.mm
@@ -499,6 +499,19 @@
   browser_compositor_->TransformPointToRootSurface(point);
 }
 
+bool RenderWidgetHostViewIOS::TransformPointToCoordSpaceForView(
+    const gfx::PointF& point,
+    RenderWidgetHostViewBase* target_view,
+    gfx::PointF* transformed_point) {
+  if (target_view == this) {
+    *transformed_point = point;
+    return true;
+  }
+
+  return target_view->TransformPointToLocalCoordSpace(
+      point, GetCurrentSurfaceId(), transformed_point);
+}
+
 display::ScreenInfo RenderWidgetHostViewIOS::GetCurrentScreenInfo() const {
   return screen_infos_.current();
 }
diff --git a/content/browser/web_package/signed_exchange_loader.cc b/content/browser/web_package/signed_exchange_loader.cc
index a196ea04..240d70f5 100644
--- a/content/browser/web_package/signed_exchange_loader.cc
+++ b/content/browser/web_package/signed_exchange_loader.cc
@@ -50,16 +50,10 @@
   if (!outer_request.trusted_params ||
       outer_request.trusted_params->isolation_info.IsEmpty())
     return net::IsolationInfo();
-  if (net::IsolationInfo::IsFrameSiteEnabled()) {
-    return net::IsolationInfo::Create(
-        net::IsolationInfo::RequestType::kOther,
-        *outer_request.trusted_params->isolation_info.top_frame_origin(),
-        *outer_request.trusted_params->isolation_info.frame_origin(),
-        net::SiteForCookies());
-  }
-  return net::IsolationInfo::CreateDoubleKey(
+  return net::IsolationInfo::Create(
       net::IsolationInfo::RequestType::kOther,
       *outer_request.trusted_params->isolation_info.top_frame_origin(),
+      *outer_request.trusted_params->isolation_info.frame_origin(),
       net::SiteForCookies());
 }
 
diff --git a/content/browser/webid/fedcm_metrics.cc b/content/browser/webid/fedcm_metrics.cc
index 3e2dc0b..81b43c6e 100644
--- a/content/browser/webid/fedcm_metrics.cc
+++ b/content/browser/webid/fedcm_metrics.cc
@@ -214,7 +214,10 @@
 void FedCmMetrics::RecordAutoReauthnMetrics(
     bool has_single_returning_account,
     const IdentityRequestAccount* auto_signin_account,
-    bool auto_reauthn_success) {
+    bool auto_reauthn_success,
+    bool is_auto_reauthn_setting_blocked,
+    bool is_auto_reauthn_embargoed,
+    absl::optional<base::TimeDelta> time_from_embargo) {
   NumReturningAccounts num_returning_accounts = NumReturningAccounts::kZero;
   if (has_single_returning_account) {
     num_returning_accounts = NumReturningAccounts::kOne;
@@ -226,11 +229,30 @@
                                 num_returning_accounts);
   base::UmaHistogramBoolean("Blink.FedCm.AutoReauthn.Succeeded",
                             auto_reauthn_success);
-
+  base::UmaHistogramBoolean("Blink.FedCm.AutoReauthn.BlockedByContentSettings",
+                            is_auto_reauthn_setting_blocked);
+  base::UmaHistogramBoolean("Blink.FedCm.AutoReauthn.BlockedByEmbargo",
+                            is_auto_reauthn_embargoed);
   ukm::builders::Blink_FedCm ukm_builder(page_source_id_);
+  if (time_from_embargo) {
+    // Use a custom histogram with the default number of buckets so that we set
+    // the maximum to the permission embargo duration: 10 minutes. See
+    // `kFederatedIdentityAutoReauthnEmbargoDuration`.
+    base::UmaHistogramCustomTimes(
+        "Blink.FedCm.AutoReauthn.TimeFromEmbargoWhenBlocked",
+        *time_from_embargo, base::Milliseconds(10), base::Minutes(10),
+        /*buckets=*/50);
+    ukm_builder.SetAutoReauthn_TimeFromEmbargoWhenBlocked(
+        ukm::GetExponentialBucketMinForUserTiming(
+            time_from_embargo->InMilliseconds()));
+  }
+
   ukm_builder.SetAutoReauthn_ReturningAccounts(
       static_cast<int>(num_returning_accounts));
   ukm_builder.SetAutoReauthn_Succeeded(auto_reauthn_success);
+  ukm_builder.SetAutoReauthn_BlockedByContentSettings(
+      is_auto_reauthn_setting_blocked);
+  ukm_builder.SetAutoReauthn_BlockedByEmbargo(is_auto_reauthn_embargoed);
   ukm_builder.SetFedCmSessionID(session_id_);
   ukm_builder.Record(ukm::UkmRecorder::Get());
 }
diff --git a/content/browser/webid/fedcm_metrics.h b/content/browser/webid/fedcm_metrics.h
index e207e91d..fa985911 100644
--- a/content/browser/webid/fedcm_metrics.h
+++ b/content/browser/webid/fedcm_metrics.h
@@ -153,11 +153,14 @@
     kMaxValue = kMultiple
   };
 
-  // Records some auto reauthn metrics.
+  // Records several auto reauthn metrics using the given parameters.
   void RecordAutoReauthnMetrics(
       bool has_single_returning_account,
       const IdentityRequestAccount* auto_signin_account,
-      bool auto_reauthn_success);
+      bool auto_reauthn_success,
+      bool is_auto_reauthn_setting_blocked,
+      bool is_auto_reauthn_embargoed,
+      absl::optional<base::TimeDelta> time_from_embargo);
 
  private:
   // The page's SourceId. Used to log the UKM event Blink.FedCm.
diff --git a/content/browser/webid/federated_auth_request_impl.cc b/content/browser/webid/federated_auth_request_impl.cc
index 633fb15c..eafb8e0 100644
--- a/content/browser/webid/federated_auth_request_impl.cc
+++ b/content/browser/webid/federated_auth_request_impl.cc
@@ -921,12 +921,23 @@
       idp_enabled_auto_reauthn && IsFedCmAutoReauthnEnabled();
 
   bool auto_reauthn = auto_reauthn_enabled;
-  if (auto_reauthn_enabled &&
-      !auto_reauthn_permission_delegate_->HasAutoReauthnPermission(
-          GetEmbeddingOrigin())) {
-    // `auto_reauthn_permission_delegate_` will log the failure reason, so no
-    // need to log it here.
-    auto_reauthn = false;
+  bool has_auto_reauthn_content_setting = false;
+  bool is_auto_reauthn_embargoed = false;
+  absl::optional<base::TimeDelta> time_from_embargo;
+  if (auto_reauthn_enabled) {
+    has_auto_reauthn_content_setting =
+        auto_reauthn_permission_delegate_->HasAutoReauthnContentSetting();
+    auto_reauthn &= has_auto_reauthn_content_setting;
+    is_auto_reauthn_embargoed =
+        auto_reauthn_permission_delegate_->IsAutoReauthnEmbargoed(
+            GetEmbeddingOrigin());
+    if (is_auto_reauthn_embargoed) {
+      time_from_embargo =
+          base::Time::Now() -
+          auto_reauthn_permission_delegate_->GetAutoReauthnEmbargoStartTime(
+              GetEmbeddingOrigin());
+    }
+    auto_reauthn &= !is_auto_reauthn_embargoed;
   }
 
   const IdentityProviderData* auto_reauthn_idp = nullptr;
@@ -971,7 +982,9 @@
 
   if (auto_reauthn_enabled) {
     fedcm_metrics_->RecordAutoReauthnMetrics(
-        has_single_returning_account, auto_reauthn_account, auto_reauthn);
+        has_single_returning_account, auto_reauthn_account, auto_reauthn,
+        !has_auto_reauthn_content_setting, is_auto_reauthn_embargoed,
+        time_from_embargo);
   }
 }
 
diff --git a/content/browser/webid/federated_auth_request_impl_unittest.cc b/content/browser/webid/federated_auth_request_impl_unittest.cc
index 57d9ba6..f2821868 100644
--- a/content/browser/webid/federated_auth_request_impl_unittest.cc
+++ b/content/browser/webid/federated_auth_request_impl_unittest.cc
@@ -974,9 +974,28 @@
     EXPECT_TRUE(metric_found) << "No Status.SignInStateMatch was found";
   }
 
-  void ExpectAutoReauthnUKM(
+  void ExpectAutoReauthnMetrics(
       FedCmMetrics::NumReturningAccounts expected_returning_accounts,
-      bool expected_succeeded) {
+      bool expected_succeeded,
+      bool expected_auto_reauthn_setting_blocked,
+      bool expected_auto_reauthn_embargoed) {
+    // UMA checks
+    histogram_tester_.ExpectUniqueSample("Blink.FedCm.AutoReauthn.Succeeded",
+                                         expected_succeeded, 1);
+    histogram_tester_.ExpectUniqueSample(
+        "Blink.FedCm.AutoReauthn.ReturningAccounts",
+        static_cast<int>(expected_returning_accounts), 1);
+    histogram_tester_.ExpectUniqueSample(
+        "Blink.FedCm.AutoReauthn.BlockedByContentSettings",
+        expected_auto_reauthn_setting_blocked, 1);
+    histogram_tester_.ExpectUniqueSample(
+        "Blink.FedCm.AutoReauthn.BlockedByEmbargo",
+        expected_auto_reauthn_embargoed, 1);
+    histogram_tester_.ExpectTotalCount(
+        "Blink.FedCm.AutoReauthn.TimeFromEmbargoWhenBlocked",
+        expected_auto_reauthn_embargoed ? 1 : 0);
+
+    // UKM checks
     auto entries = ukm_recorder()->GetEntriesByName(FedCmEntry::kEntryName);
     ASSERT_FALSE(entries.empty()) << "No FedCM UKM entry was found!";
 
@@ -986,22 +1005,41 @@
           ukm_recorder()->GetEntryMetric(entry, "AutoReauthn.Succeeded");
       if (!metric) {
         EXPECT_FALSE(ukm_recorder()->GetEntryMetric(
-            entry, "AutoReauthn.ReturningAccounts"))
-            << "Found an entry with AutoReauthn.ReturningAccounts but without "
-               "AutoReauthn.Succeeded";
+            entry, "AutoReauthn.ReturningAccounts"));
+        EXPECT_FALSE(ukm_recorder()->GetEntryMetric(
+            entry, "AutoReauthn.BlockedByContentSettings"));
+        EXPECT_FALSE(ukm_recorder()->GetEntryMetric(
+            entry, "AutoReauthn.BlockedByEmbargo"));
+        EXPECT_FALSE(ukm_recorder()->GetEntryMetric(
+            entry, "AutoReauthn.TimeFromEmbargoWhenBlocked"));
         continue;
       }
       EXPECT_FALSE(metric_found) << "Found more than one AutoReauthn entry";
       metric_found = true;
       EXPECT_EQ(expected_succeeded, *metric);
-      const int64_t* returning_accounts_metric = ukm_recorder()->GetEntryMetric(
-          entry, "AutoReauthn.ReturningAccounts");
-      ASSERT_TRUE(returning_accounts_metric)
-          << "AutoReauthn.ReturningAccounts was not found";
-      EXPECT_EQ(static_cast<int>(expected_returning_accounts),
-                *returning_accounts_metric);
+
+      metric = ukm_recorder()->GetEntryMetric(entry,
+                                              "AutoReauthn.ReturningAccounts");
+      ASSERT_TRUE(metric) << "AutoReauthn.ReturningAccounts was not found";
+      EXPECT_EQ(static_cast<int>(expected_returning_accounts), *metric);
+
+      metric = ukm_recorder()->GetEntryMetric(
+          entry, "AutoReauthn.BlockedByContentSettings");
+      ASSERT_TRUE(metric)
+          << "AutoReauthn.BlockedByContentSettings was not found";
+      EXPECT_EQ(expected_auto_reauthn_setting_blocked, *metric);
+
+      metric =
+          ukm_recorder()->GetEntryMetric(entry, "AutoReauthn.BlockedByEmbargo");
+      ASSERT_TRUE(metric) << "AutoReauthn.BlockedByEmbargo was not found";
+      EXPECT_EQ(expected_auto_reauthn_embargoed, *metric);
+
+      metric = ukm_recorder()->GetEntryMetric(
+          entry, "AutoReauthn.TimeFromEmbargoWhenBlocked");
+      EXPECT_EQ(expected_auto_reauthn_embargoed, !!metric);
     }
     EXPECT_TRUE(metric_found) << "Did not find AutoReauthn metrics";
+    CheckAllFedCmSessionIDs();
   }
 
   void CheckAllFedCmSessionIDs() {
@@ -1382,8 +1420,11 @@
 
   // Pretend the auto re-authn permission has been granted.
   EXPECT_CALL(*test_auto_reauthn_permission_delegate_,
-              HasAutoReauthnPermission(OriginFromString(kRpUrl)))
+              HasAutoReauthnContentSetting())
       .WillOnce(Return(true));
+  EXPECT_CALL(*test_auto_reauthn_permission_delegate_,
+              IsAutoReauthnEmbargoed(OriginFromString(kRpUrl)))
+      .WillOnce(Return(false));
 
   RequestParameters request_parameters = kDefaultRequestParameters;
   request_parameters.auto_reauthn = true;
@@ -1395,14 +1436,10 @@
   EXPECT_TRUE(test_auto_reauthn_permission_delegate_->embargoed_origins_.count(
       OriginFromString(kRpUrl)));
 
-  histogram_tester_.ExpectUniqueSample("Blink.FedCm.AutoReauthn.Succeeded",
-                                       true, 1);
-  histogram_tester_.ExpectUniqueSample(
-      "Blink.FedCm.AutoReauthn.ReturningAccounts",
-      static_cast<int>(FedCmMetrics::NumReturningAccounts::kOne), 1);
-  ExpectAutoReauthnUKM(FedCmMetrics::NumReturningAccounts::kOne,
-                       /*expected_succeeded=*/true);
-  CheckAllFedCmSessionIDs();
+  ExpectAutoReauthnMetrics(FedCmMetrics::NumReturningAccounts::kOne,
+                           /*expected_succeeded=*/true,
+                           /*expected_auto_reauthn_setting_blocked=*/false,
+                           /*expected_auto_reauthn_embargoed=*/false);
 }
 
 // Test that auto re-authn with a single account where the account is a
@@ -1422,8 +1459,11 @@
 
   // Pretend the auto re-authn permission has been granted.
   EXPECT_CALL(*test_auto_reauthn_permission_delegate_,
-              HasAutoReauthnPermission(OriginFromString(kRpUrl)))
+              HasAutoReauthnContentSetting())
       .WillOnce(Return(true));
+  EXPECT_CALL(*test_auto_reauthn_permission_delegate_,
+              IsAutoReauthnEmbargoed(OriginFromString(kRpUrl)))
+      .WillOnce(Return(false));
 
   for (const auto& idp_info : kConfigurationValid.idp_info) {
     ASSERT_EQ(idp_info.second.accounts.size(), 1u);
@@ -1436,14 +1476,10 @@
   EXPECT_EQ(displayed_accounts()[0].login_state, LoginState::kSignIn);
   EXPECT_EQ(dialog_controller_state_.sign_in_mode, SignInMode::kAuto);
 
-  histogram_tester_.ExpectUniqueSample("Blink.FedCm.AutoReauthn.Succeeded",
-                                       true, 1);
-  histogram_tester_.ExpectUniqueSample(
-      "Blink.FedCm.AutoReauthn.ReturningAccounts",
-      static_cast<int>(FedCmMetrics::NumReturningAccounts::kOne), 1);
-  ExpectAutoReauthnUKM(FedCmMetrics::NumReturningAccounts::kOne,
-                       /*expected_succeeded=*/true);
-  CheckAllFedCmSessionIDs();
+  ExpectAutoReauthnMetrics(FedCmMetrics::NumReturningAccounts::kOne,
+                           /*expected_succeeded=*/true,
+                           /*expected_auto_reauthn_setting_blocked=*/false,
+                           /*expected_auto_reauthn_embargoed=*/false);
 }
 
 // Test that auto re-authn with multiple accounts and a single returning user
@@ -1477,8 +1513,11 @@
 
   // Pretend the auto re-authn permission has been granted.
   EXPECT_CALL(*test_auto_reauthn_permission_delegate_,
-              HasAutoReauthnPermission(OriginFromString(kRpUrl)))
+              HasAutoReauthnContentSetting())
       .WillOnce(Return(true));
+  EXPECT_CALL(*test_auto_reauthn_permission_delegate_,
+              IsAutoReauthnEmbargoed(OriginFromString(kRpUrl)))
+      .WillOnce(Return(false));
 
   RequestParameters request_parameters = kDefaultRequestParameters;
   request_parameters.auto_reauthn = true;
@@ -1492,14 +1531,10 @@
   EXPECT_EQ(CountNumLoginStateIsSignin(), 1);
   EXPECT_EQ(dialog_controller_state_.sign_in_mode, SignInMode::kAuto);
 
-  histogram_tester_.ExpectUniqueSample("Blink.FedCm.AutoReauthn.Succeeded",
-                                       true, 1);
-  histogram_tester_.ExpectUniqueSample(
-      "Blink.FedCm.AutoReauthn.ReturningAccounts",
-      static_cast<int>(FedCmMetrics::NumReturningAccounts::kOne), 1);
-  ExpectAutoReauthnUKM(FedCmMetrics::NumReturningAccounts::kOne,
-                       /*expected_succeeded=*/true);
-  CheckAllFedCmSessionIDs();
+  ExpectAutoReauthnMetrics(FedCmMetrics::NumReturningAccounts::kOne,
+                           /*expected_succeeded=*/true,
+                           /*expected_auto_reauthn_setting_blocked=*/false,
+                           /*expected_auto_reauthn_embargoed=*/false);
 }
 
 // Test that auto re-authn with multiple accounts and multiple returning users
@@ -1534,8 +1569,11 @@
 
   // Pretend the auto re-authn permission has been granted.
   EXPECT_CALL(*test_auto_reauthn_permission_delegate_,
-              HasAutoReauthnPermission(OriginFromString(kRpUrl)))
+              HasAutoReauthnContentSetting())
       .WillOnce(Return(true));
+  EXPECT_CALL(*test_auto_reauthn_permission_delegate_,
+              IsAutoReauthnEmbargoed(OriginFromString(kRpUrl)))
+      .WillOnce(Return(false));
 
   RequestParameters request_parameters = kDefaultRequestParameters;
   request_parameters.auto_reauthn = true;
@@ -1550,14 +1588,10 @@
   EXPECT_EQ(CountNumLoginStateIsSignin(), 2);
   EXPECT_EQ(dialog_controller_state_.sign_in_mode, SignInMode::kExplicit);
 
-  histogram_tester_.ExpectUniqueSample("Blink.FedCm.AutoReauthn.Succeeded",
-                                       false, 1);
-  histogram_tester_.ExpectUniqueSample(
-      "Blink.FedCm.AutoReauthn.ReturningAccounts",
-      static_cast<int>(FedCmMetrics::NumReturningAccounts::kMultiple), 1);
-  ExpectAutoReauthnUKM(FedCmMetrics::NumReturningAccounts::kMultiple,
-                       /*expected_succeeded=*/false);
-  CheckAllFedCmSessionIDs();
+  ExpectAutoReauthnMetrics(FedCmMetrics::NumReturningAccounts::kMultiple,
+                           /*expected_succeeded=*/false,
+                           /*expected_auto_reauthn_setting_blocked=*/false,
+                           /*expected_auto_reauthn_embargoed=*/false);
 }
 
 // Test that auto re-authn with single non-returning account sets the sign-in
@@ -1575,8 +1609,11 @@
 
   // Pretend the auto re-authn permission has been granted.
   EXPECT_CALL(*test_auto_reauthn_permission_delegate_,
-              HasAutoReauthnPermission(OriginFromString(kRpUrl)))
+              HasAutoReauthnContentSetting())
       .WillOnce(Return(true));
+  EXPECT_CALL(*test_auto_reauthn_permission_delegate_,
+              IsAutoReauthnEmbargoed(OriginFromString(kRpUrl)))
+      .WillOnce(Return(false));
 
   for (const auto& idp_info : kConfigurationValid.idp_info) {
     ASSERT_EQ(idp_info.second.accounts.size(), 1u);
@@ -1589,14 +1626,10 @@
   EXPECT_EQ(displayed_accounts()[0].login_state, LoginState::kSignUp);
   EXPECT_EQ(dialog_controller_state_.sign_in_mode, SignInMode::kExplicit);
 
-  histogram_tester_.ExpectUniqueSample("Blink.FedCm.AutoReauthn.Succeeded",
-                                       false, 1);
-  histogram_tester_.ExpectUniqueSample(
-      "Blink.FedCm.AutoReauthn.ReturningAccounts",
-      static_cast<int>(FedCmMetrics::NumReturningAccounts::kZero), 1);
-  ExpectAutoReauthnUKM(FedCmMetrics::NumReturningAccounts::kZero,
-                       /*expected_succeeded=*/false);
-  CheckAllFedCmSessionIDs();
+  ExpectAutoReauthnMetrics(FedCmMetrics::NumReturningAccounts::kZero,
+                           /*expected_succeeded=*/false,
+                           /*expected_auto_reauthn_setting_blocked=*/false,
+                           /*expected_auto_reauthn_embargoed=*/false);
 }
 
 // Test that auto re-authn with multiple accounts and a single returning user
@@ -1639,8 +1672,11 @@
 
   // Pretend the auto re-authn permission has been granted.
   EXPECT_CALL(*test_auto_reauthn_permission_delegate_,
-              HasAutoReauthnPermission(OriginFromString(kRpUrl)))
+              HasAutoReauthnContentSetting())
       .WillOnce(Return(true));
+  EXPECT_CALL(*test_auto_reauthn_permission_delegate_,
+              IsAutoReauthnEmbargoed(OriginFromString(kRpUrl)))
+      .WillOnce(Return(false));
 
   RequestParameters request_parameters = kDefaultRequestParameters;
   request_parameters.auto_reauthn = true;
@@ -1665,8 +1701,11 @@
 
   // Pretend the auto re-authn permission has been granted.
   EXPECT_CALL(*test_auto_reauthn_permission_delegate_,
-              HasAutoReauthnPermission(OriginFromString(kRpUrl)))
+              HasAutoReauthnContentSetting())
       .WillOnce(Return(true));
+  EXPECT_CALL(*test_auto_reauthn_permission_delegate_,
+              IsAutoReauthnEmbargoed(OriginFromString(kRpUrl)))
+      .WillOnce(Return(false));
 
   RequestParameters request_parameters = kDefaultRequestParameters;
   request_parameters.auto_reauthn = true;
@@ -1693,7 +1732,7 @@
 
   // Pretend the auto re-authn permission has been blocked for this account.
   EXPECT_CALL(*test_auto_reauthn_permission_delegate_,
-              HasAutoReauthnPermission(OriginFromString(kRpUrl)))
+              HasAutoReauthnContentSetting())
       .WillOnce(Return(false));
 
   RequestParameters request_parameters = kDefaultRequestParameters;
@@ -1703,6 +1742,47 @@
   ASSERT_EQ(displayed_accounts().size(), 1u);
   EXPECT_EQ(displayed_accounts()[0].login_state, LoginState::kSignIn);
   EXPECT_EQ(dialog_controller_state_.sign_in_mode, SignInMode::kExplicit);
+
+  ExpectAutoReauthnMetrics(FedCmMetrics::NumReturningAccounts::kOne,
+                           /*expected_succeeded=*/false,
+                           /*expected_auto_reauthn_setting_blocked=*/true,
+                           /*expected_auto_reauthn_embargoed=*/false);
+}
+
+// Test that auto re-authn where the auto re-authn cooldown is on sets
+// the sign-in mode to explicit.
+TEST_F(FederatedAuthRequestImplTest, AutoReauthnWithCooldown) {
+  base::test::ScopedFeatureList list;
+  list.InitAndEnableFeature(features::kFedCmAutoReauthn);
+
+  // Pretend the sharing permission has been granted for this account.
+  EXPECT_CALL(
+      *test_permission_delegate_,
+      HasSharingPermission(OriginFromString(kRpUrl), OriginFromString(kRpUrl),
+                           OriginFromString(kProviderUrlFull), kAccountId))
+      .WillRepeatedly(Return(true));
+
+  // Pretend the auto re-authn permission has been granted for this account.
+  EXPECT_CALL(*test_auto_reauthn_permission_delegate_,
+              HasAutoReauthnContentSetting())
+      .WillOnce(Return(true));
+  // Pretend that auto re-authn is embargoed.
+  EXPECT_CALL(*test_auto_reauthn_permission_delegate_,
+              IsAutoReauthnEmbargoed(OriginFromString(kRpUrl)))
+      .WillOnce(Return(true));
+
+  RequestParameters request_parameters = kDefaultRequestParameters;
+  request_parameters.auto_reauthn = true;
+  RunAuthTest(request_parameters, kExpectationSuccess, kConfigurationValid);
+
+  ASSERT_EQ(displayed_accounts().size(), 1u);
+  EXPECT_EQ(displayed_accounts()[0].login_state, LoginState::kSignIn);
+  EXPECT_EQ(dialog_controller_state_.sign_in_mode, SignInMode::kExplicit);
+
+  ExpectAutoReauthnMetrics(FedCmMetrics::NumReturningAccounts::kOne,
+                           /*expected_succeeded=*/false,
+                           /*expected_auto_reauthn_setting_blocked=*/false,
+                           /*expected_auto_reauthn_embargoed=*/true);
 }
 
 TEST_F(FederatedAuthRequestImplTest, MetricsForSuccessfulSignInCase) {
diff --git a/content/browser/webid/test/mock_auto_reauthn_permission_delegate.h b/content/browser/webid/test/mock_auto_reauthn_permission_delegate.h
index 186f7e9..5816162 100644
--- a/content/browser/webid/test/mock_auto_reauthn_permission_delegate.h
+++ b/content/browser/webid/test/mock_auto_reauthn_permission_delegate.h
@@ -22,7 +22,9 @@
   MockAutoReauthnPermissionDelegate& operator=(
       const MockAutoReauthnPermissionDelegate&) = delete;
 
-  MOCK_METHOD1(HasAutoReauthnPermission, bool(const url::Origin&));
+  MOCK_METHOD0(HasAutoReauthnContentSetting, bool());
+  MOCK_METHOD1(IsAutoReauthnEmbargoed, bool(const url::Origin&));
+  MOCK_METHOD1(GetAutoReauthnEmbargoStartTime, base::Time(const url::Origin&));
   MOCK_METHOD1(RecordDisplayAndEmbargo, void(const url::Origin&));
 };
 
diff --git a/content/gpu/gpu_sandbox_hook_linux.cc b/content/gpu/gpu_sandbox_hook_linux.cc
index d93285a5..62712eee 100644
--- a/content/gpu/gpu_sandbox_hook_linux.cc
+++ b/content/gpu/gpu_sandbox_hook_linux.cc
@@ -282,8 +282,11 @@
       // To support threads in mesa we use --gpu-sandbox-start-early and
       // that requires the following libs and files to be accessible.
       "/usr/lib64/libEGL.so.1", "/usr/lib64/libGLESv2.so.2",
-      "/usr/lib64/libglapi.so.0", "/usr/lib64/dri/i965_dri.so",
-      "/usr/lib64/dri/iris_dri.so",
+      "/usr/lib64/libelf.so.1", "/usr/lib64/libglapi.so.0",
+      "/usr/lib64/libdrm_amdgpu.so.1", "/usr/lib64/libdrm_radeon.so.1",
+      "/usr/lib64/libdrm_nouveau.so.2", "/usr/lib64/dri/crocus_dri.so",
+      "/usr/lib64/dri/i965_dri.so", "/usr/lib64/dri/iris_dri.so",
+      "/usr/lib64/dri/swrast_dri.so",
       // Allow libglvnd files and libs.
       "/usr/share/glvnd/egl_vendor.d",
       "/usr/share/glvnd/egl_vendor.d/50_mesa.json",
diff --git a/content/public/browser/federated_identity_auto_reauthn_permission_context_delegate.h b/content/public/browser/federated_identity_auto_reauthn_permission_context_delegate.h
index 907a01d..300c0c4 100644
--- a/content/public/browser/federated_identity_auto_reauthn_permission_context_delegate.h
+++ b/content/public/browser/federated_identity_auto_reauthn_permission_context_delegate.h
@@ -5,6 +5,7 @@
 #ifndef CONTENT_PUBLIC_BROWSER_FEDERATED_IDENTITY_AUTO_REAUTHN_PERMISSION_CONTEXT_DELEGATE_H_
 #define CONTENT_PUBLIC_BROWSER_FEDERATED_IDENTITY_AUTO_REAUTHN_PERMISSION_CONTEXT_DELEGATE_H_
 
+#include "base/time/time.h"
 #include "content/common/content_export.h"
 
 namespace url {
@@ -20,9 +21,22 @@
   FederatedIdentityAutoReauthnPermissionContextDelegate() = default;
   virtual ~FederatedIdentityAutoReauthnPermissionContextDelegate() = default;
 
-  // Returns the permission status of the FedCM API's auto re-authn feature for
-  // the passed-in |relying_party_embedder|.
-  virtual bool HasAutoReauthnPermission(
+  // Returns whether the FedCM API's auto re-authn is unblocked based on content
+  // settings. A caller should also use `IsAutoReauthnEmbargoed()` to determine
+  // whether auto re-authn is allowed or not.
+  virtual bool HasAutoReauthnContentSetting() = 0;
+
+  // Returns whether the FedCM API's auto re-authn feature is embargoed for the
+  // passed-in |relying_party_embedder|. A caller should also use
+  // `HasAutoReauthnContentSetting()` to determine whether auto re-authn is
+  // allowed or not.
+  virtual bool IsAutoReauthnEmbargoed(
+      const url::Origin& relying_party_embedder) = 0;
+
+  // Returns the most recent recorded time an auto-reauthn embargo was started
+  // with the given |relying_party_embedder|. Returns base::Time() if no record
+  // is found.
+  virtual base::Time GetAutoReauthnEmbargoStartTime(
       const url::Origin& relying_party_embedder) = 0;
 
   // Records that an auto re-authn prompt was displayed to the user and places
diff --git a/content/shell/browser/shell_application_ios.mm b/content/shell/browser/shell_application_ios.mm
index c92f702a..7592b83 100644
--- a/content/shell/browser/shell_application_ios.mm
+++ b/content/shell/browser/shell_application_ios.mm
@@ -8,6 +8,7 @@
 #error "This file requires ARC support."
 #endif
 
+#include "base/command_line.h"
 #include "content/shell/browser/shell.h"
 #include "content/shell/browser/shell_browser_context.h"
 #include "content/shell/browser/shell_content_browser_client.h"
@@ -27,10 +28,25 @@
   //  UIWindow* window = content::Shell::windows()[0]->window();
   content::ShellBrowserContext* browserContext =
       content::ShellContentBrowserClient::Get()->browser_context();
-  UIWindow* window =
-      content::Shell::CreateNewWindow(browserContext, GURL(url::kAboutBlankURL),
-                                      nullptr, gfx::Size())
-          ->window();
+
+  GURL initial_url(url::kAboutBlankURL);
+
+  // If a URL has been provided as an argument, use it. However, no attempt is
+  // made here to sanitize this input.
+  // TODO(crbug.com/1418123): usually this is done with GetStartupURL() and,
+  // ideally, we'd leverage that once the shell on ios shares more machinery.
+  base::CommandLine* command_line = base::CommandLine::ForCurrentProcess();
+  const auto& args = command_line->GetArgs();
+  if (!args.empty()) {
+    GURL candidate(args[0]);
+    if (candidate.is_valid()) {
+      initial_url = candidate;
+    }
+  }
+
+  UIWindow* window = content::Shell::CreateNewWindow(
+                         browserContext, initial_url, nullptr, gfx::Size())
+                         ->window();
   self.window = window;
   return YES;
 }
diff --git a/content/shell/browser/shell_federated_permission_context.cc b/content/shell/browser/shell_federated_permission_context.cc
index 2fb4520..a8e73fb 100644
--- a/content/shell/browser/shell_federated_permission_context.cc
+++ b/content/shell/browser/shell_federated_permission_context.cc
@@ -34,11 +34,20 @@
 }
 
 // FederatedIdentityAutoReauthnPermissionContextDelegate
-bool ShellFederatedPermissionContext::HasAutoReauthnPermission(
-    const url::Origin& relying_party_embedder) {
+bool ShellFederatedPermissionContext::HasAutoReauthnContentSetting() {
   return auto_reauthn_permission_;
 }
 
+bool ShellFederatedPermissionContext::IsAutoReauthnEmbargoed(
+    const url::Origin& relying_party_embedder) {
+  return false;
+}
+
+base::Time ShellFederatedPermissionContext::GetAutoReauthnEmbargoStartTime(
+    const url::Origin& relying_party_embedder) {
+  return base::Time();
+}
+
 void ShellFederatedPermissionContext::RecordDisplayAndEmbargo(
     const url::Origin& relying_party_embedder) {}
 
diff --git a/content/shell/browser/shell_federated_permission_context.h b/content/shell/browser/shell_federated_permission_context.h
index f550a413..5ae7e7f 100644
--- a/content/shell/browser/shell_federated_permission_context.h
+++ b/content/shell/browser/shell_federated_permission_context.h
@@ -12,6 +12,7 @@
 #include <vector>
 
 #include "base/functional/callback.h"
+#include "base/time/time.h"
 #include "content/public/browser/federated_identity_api_permission_context_delegate.h"
 #include "content/public/browser/federated_identity_auto_reauthn_permission_context_delegate.h"
 #include "content/public/browser/federated_identity_permission_context_delegate.h"
@@ -40,7 +41,10 @@
   bool ShouldCompleteRequestImmediately() const override;
 
   // FederatedIdentityAutoReauthnPermissionContextDelegate
-  bool HasAutoReauthnPermission(
+  bool HasAutoReauthnContentSetting() override;
+  bool IsAutoReauthnEmbargoed(
+      const url::Origin& relying_party_embedder) override;
+  base::Time GetAutoReauthnEmbargoStartTime(
       const url::Origin& relying_party_embedder) override;
   void RecordDisplayAndEmbargo(
       const url::Origin& relying_party_embedder) override;
diff --git a/content/test/gpu/gpu_tests/trace_integration_test.py b/content/test/gpu/gpu_tests/trace_integration_test.py
index 959db6e..171ec9c 100644
--- a/content/test/gpu/gpu_tests/trace_integration_test.py
+++ b/content/test/gpu/gpu_tests/trace_integration_test.py
@@ -546,30 +546,18 @@
          ]),
     ]
     for (name, first_load_page, cache_pages) in webgpu_xorigin_cache_miss_tests:
-      yield (
-          'WebGPUCachingTraceTest_' + name,
-          posixpath.join(gpu_data_relative_path, first_load_page),
-          [
-              _CacheTraceTestArguments(
-                  browser_args=webgpu_cache_test_browser_args + [
-                      # Currently it seems like this is not always the default
-                      # yet. We may be able to remove this once it is defaulted.
-                      # Without this flag, origin caching uses an isolation key
-                      # that depends only on the top-level page, and hence these
-                      # tests will fail. With the flag, the isolation key uses
-                      # both the top-level page and the subpage.
-                      # TODO(dawn:1568) Revisit whether this is necessary.
-                      '--disable-features=' +
-                      'ForceIsolationInfoFrameOriginToTopLevelFrame'
-                  ],
-                  category='gpu',
-                  test_harness_script=basic_test_harness_script,
-                  finish_js_condition='domAutomationController._finished',
-                  first_load_eval_func='CheckWebGPUFirstLoadCache',
-                  cache_eval_func='CheckNoWebGPUCacheHits',
-                  cache_pages=cache_pages,
-                  test_renavigation=False)
-          ])
+      yield ('WebGPUCachingTraceTest_' + name,
+             posixpath.join(gpu_data_relative_path, first_load_page), [
+                 _CacheTraceTestArguments(
+                     browser_args=webgpu_cache_test_browser_args,
+                     category='gpu',
+                     test_harness_script=basic_test_harness_script,
+                     finish_js_condition='domAutomationController._finished',
+                     first_load_eval_func='CheckWebGPUFirstLoadCache',
+                     cache_eval_func='CheckNoWebGPUCacheHits',
+                     cache_pages=cache_pages,
+                     test_renavigation=False)
+             ])
 
   def _RunActualGpuTraceTest(self,
                              test_path: str,
diff --git a/content/web_test/browser/web_test_browser_context.cc b/content/web_test/browser/web_test_browser_context.cc
index 50cc205..48b12a49 100644
--- a/content/web_test/browser/web_test_browser_context.cc
+++ b/content/web_test/browser/web_test_browser_context.cc
@@ -79,7 +79,7 @@
 PermissionControllerDelegate*
 WebTestBrowserContext::GetPermissionControllerDelegate() {
   if (!permission_manager_.get())
-    permission_manager_ = std::make_unique<WebTestPermissionManager>();
+    permission_manager_ = std::make_unique<WebTestPermissionManager>(*this);
   return permission_manager_.get();
 }
 
diff --git a/content/web_test/browser/web_test_control_host.cc b/content/web_test/browser/web_test_control_host.cc
index 7f77d104..c58ad3d 100644
--- a/content/web_test/browser/web_test_control_host.cc
+++ b/content/web_test/browser/web_test_control_host.cc
@@ -27,7 +27,6 @@
 #include "base/logging.h"
 #include "base/path_service.h"
 #include "base/ranges/algorithm.h"
-#include "base/run_loop.h"
 #include "base/strings/string_number_conversions.h"
 #include "base/strings/string_util.h"
 #include "base/strings/stringprintf.h"
@@ -1470,7 +1469,8 @@
   WebTestContentBrowserClient::Get()
       ->GetWebTestBrowserContext()
       ->GetWebTestPermissionManager()
-      ->SetPermission(type, status, origin, embedding_origin);
+      ->SetPermission(type, status, origin, embedding_origin,
+                      base::DoNothing());
 }
 
 void WebTestControlHost::GetWritableDirectory(
diff --git a/content/web_test/browser/web_test_permission_manager.cc b/content/web_test/browser/web_test_permission_manager.cc
index 67ff4c7b..1d2bf2e 100644
--- a/content/web_test/browser/web_test_permission_manager.cc
+++ b/content/web_test/browser/web_test_permission_manager.cc
@@ -10,11 +10,14 @@
 
 #include "base/functional/bind.h"
 #include "base/functional/callback.h"
+#include "components/content_settings/core/common/content_settings.h"
 #include "content/browser/permissions/permission_util.h"
 #include "content/public/browser/browser_thread.h"
 #include "content/public/browser/permission_controller.h"
+#include "content/public/browser/storage_partition.h"
 #include "content/public/browser/web_contents.h"
 #include "content/web_test/browser/web_test_content_browser_client.h"
+#include "services/network/public/mojom/cookie_manager.mojom.h"
 #include "third_party/blink/public/common/permissions/permission_utils.h"
 #include "third_party/blink/public/common/web_preferences/web_preferences.h"
 
@@ -51,10 +54,11 @@
   return hash;
 }
 
-WebTestPermissionManager::WebTestPermissionManager()
-    : PermissionControllerDelegate() {}
+WebTestPermissionManager::WebTestPermissionManager(
+    BrowserContext& browser_context)
+    : PermissionControllerDelegate(), browser_context_(browser_context) {}
 
-WebTestPermissionManager::~WebTestPermissionManager() {}
+WebTestPermissionManager::~WebTestPermissionManager() = default;
 
 void WebTestPermissionManager::RequestPermission(
     blink::PermissionType permission,
@@ -247,7 +251,8 @@
     blink::PermissionType permission,
     blink::mojom::PermissionStatus status,
     const GURL& url,
-    const GURL& embedding_url) {
+    const GURL& embedding_url,
+    blink::test::mojom::PermissionAutomation::SetPermissionCallback callback) {
   DCHECK_CURRENTLY_ON(BrowserThread::UI);
 
   PermissionDescription description(permission, url.DeprecatedGetOriginAsURL(),
@@ -266,7 +271,7 @@
     }
   }
 
-  OnPermissionChanged(description, status);
+  OnPermissionChanged(description, status, std::move(callback));
 }
 
 void WebTestPermissionManager::SetPermission(
@@ -287,8 +292,8 @@
     applicable_permission_url = overridden_origin.GetURL();
   }
 
-  SetPermission(*type, status, applicable_permission_url, embedding_url);
-  std::move(callback).Run(true);
+  SetPermission(*type, status, applicable_permission_url, embedding_url,
+                std::move(callback));
 }
 
 void WebTestPermissionManager::ResetPermissions() {
@@ -305,7 +310,9 @@
 
 void WebTestPermissionManager::OnPermissionChanged(
     const PermissionDescription& permission,
-    blink::mojom::PermissionStatus status) {
+    blink::mojom::PermissionStatus status,
+    blink::test::mojom::PermissionAutomation::SetPermissionCallback
+        permission_callback) {
   std::vector<base::OnceClosure> callbacks;
   callbacks.reserve(subscriptions_.size());
 
@@ -327,6 +334,37 @@
 
   for (auto& callback : callbacks)
     std::move(callback).Run();
+
+  if (permission.type != blink::PermissionType::STORAGE_ACCESS_GRANT) {
+    std::move(permission_callback).Run(true);
+    return;
+  }
+
+  // The network service expects to hear about any new storage-access permission
+  // grants, so we have to inform it.
+  absl::optional<ContentSetting> setting;
+  switch (status) {
+    case blink::mojom::PermissionStatus::GRANTED:
+      setting = ContentSetting::CONTENT_SETTING_ALLOW;
+      break;
+    case blink::mojom::PermissionStatus::DENIED:
+      setting = ContentSetting::CONTENT_SETTING_BLOCK;
+      break;
+    case blink::mojom::PermissionStatus::ASK:
+      break;
+  }
+  std::vector<ContentSettingPatternSource> patterns;
+  if (setting) {
+    patterns.emplace_back(
+        ContentSettingsPattern::FromURL(permission.origin),
+        ContentSettingsPattern::FromURL(permission.embedding_origin),
+        base::Value(*setting), /*source=*/"", /*incognito=*/false);
+  }
+  browser_context_->GetDefaultStoragePartition()
+      ->GetCookieManagerForBrowserProcess()
+      ->SetStorageAccessGrantSettings(
+          patterns,
+          base::BindOnce(std::move(permission_callback), /*success=*/true));
 }
 
 }  // namespace content
diff --git a/content/web_test/browser/web_test_permission_manager.h b/content/web_test/browser/web_test_permission_manager.h
index e35948ddf..82b5215 100644
--- a/content/web_test/browser/web_test_permission_manager.h
+++ b/content/web_test/browser/web_test_permission_manager.h
@@ -10,6 +10,7 @@
 #include "base/containers/id_map.h"
 #include "base/functional/callback_forward.h"
 #include "base/synchronization/lock.h"
+#include "content/public/browser/browser_context.h"
 #include "content/public/browser/permission_controller_delegate.h"
 #include "content/public/browser/permission_result.h"
 #include "mojo/public/cpp/bindings/receiver_set.h"
@@ -28,7 +29,8 @@
     : public PermissionControllerDelegate,
       public blink::test::mojom::PermissionAutomation {
  public:
-  WebTestPermissionManager();
+  // `browser_context` must outlive `this`.
+  explicit WebTestPermissionManager(BrowserContext& browser_context);
 
   WebTestPermissionManager(const WebTestPermissionManager&) = delete;
   WebTestPermissionManager& operator=(const WebTestPermissionManager&) = delete;
@@ -85,10 +87,12 @@
   void UnsubscribePermissionStatusChange(
       SubscriptionId subscription_id) override;
 
-  void SetPermission(blink::PermissionType permission,
-                     blink::mojom::PermissionStatus status,
-                     const GURL& url,
-                     const GURL& embedding_url);
+  void SetPermission(
+      blink::PermissionType permission,
+      blink::mojom::PermissionStatus status,
+      const GURL& url,
+      const GURL& embedding_url,
+      blink::test::mojom::PermissionAutomation::SetPermissionCallback callback);
   void ResetPermissions();
 
   // blink::test::mojom::PermissionAutomation
@@ -131,8 +135,12 @@
   using DefaultPermissionStatusMap =
       std::unordered_map<blink::PermissionType, blink::mojom::PermissionStatus>;
 
-  void OnPermissionChanged(const PermissionDescription& permission,
-                           blink::mojom::PermissionStatus status);
+  void OnPermissionChanged(
+      const PermissionDescription& permission,
+      blink::mojom::PermissionStatus status,
+      blink::test::mojom::PermissionAutomation::SetPermissionCallback callback);
+
+  raw_ref<BrowserContext> browser_context_;
 
   // Mutex for permissions access. Unfortunately, the permissions can be
   // accessed from the IO thread because of Notifications' synchronous IPC.
diff --git a/device/bluetooth/floss/bluetooth_adapter_floss.cc b/device/bluetooth/floss/bluetooth_adapter_floss.cc
index 16adb06..297679a 100644
--- a/device/bluetooth/floss/bluetooth_adapter_floss.cc
+++ b/device/bluetooth/floss/bluetooth_adapter_floss.cc
@@ -1062,7 +1062,9 @@
   // createRfcommSocketToServiceRecord(UUID). This should be called after
   // ConnectDevice. Since all that is required on Floss for insecure connection
   // is an address, this function currently just creates a device pointer.
-  // TODO(b/259725491): This function should actually create an ACL connection.
+  // TODO(b/269500327): This behavior is actually a better design. We should
+  // rename this function to CreateDevice which does only device creation and
+  // let the caller decide what to do with it (Connect, Pair, etc).
   BluetoothDeviceFloss* device_ptr;
   std::string canonical_address = device::CanonicalizeBluetoothAddress(address);
 
diff --git a/device/bluetooth/floss/bluetooth_device_floss.cc b/device/bluetooth/floss/bluetooth_device_floss.cc
index 9c9842d6..d25f44f1 100644
--- a/device/bluetooth/floss/bluetooth_device_floss.cc
+++ b/device/bluetooth/floss/bluetooth_device_floss.cc
@@ -393,7 +393,9 @@
 void BluetoothDeviceFloss::Pair(
     device::BluetoothDevice::PairingDelegate* pairing_delegate,
     ConnectCallback callback) {
-  NOTIMPLEMENTED();
+  // Pair is the same as Connect due to influence from BlueZ.
+  // TODO(b/269516642): We should make distinction between them in the future.
+  Connect(pairing_delegate, std::move(callback));
 }
 
 #if BUILDFLAG(IS_CHROMEOS)
diff --git a/docs/workflow/debugging-with-swarming.md b/docs/workflow/debugging-with-swarming.md
index 6cbf6b62..3112f8eb 100644
--- a/docs/workflow/debugging-with-swarming.md
+++ b/docs/workflow/debugging-with-swarming.md
@@ -173,7 +173,11 @@
 $ tools/luci-go/isolate login
 ```
 
-Use your google.com account for this.
+Use your google.com account for this. On Windows the command would be:
+
+```
+$ tools\luci-go\isolate.exe login
+```
 
 ## Uploading an isolate
 
diff --git a/extensions/browser/background_script_executor.cc b/extensions/browser/background_script_executor.cc
index aa33e2c8..f83e3d62 100644
--- a/extensions/browser/background_script_executor.cc
+++ b/extensions/browser/background_script_executor.cc
@@ -8,7 +8,6 @@
 #include "base/json/json_reader.h"
 #include "content/public/browser/browser_context.h"
 #include "content/public/browser/service_worker_context.h"
-#include "content/public/browser/storage_partition.h"
 #include "content/public/test/browser_test_utils.h"
 #include "content/public/test/service_worker_test_helpers.h"
 #include "extensions/browser/extension_host.h"
@@ -155,9 +154,8 @@
     script_result_queue_ = std::make_unique<ScriptResultQueue>();
 
   content::ServiceWorkerContext* service_worker_context =
-      util::GetStoragePartitionForExtensionId(extension_->id(),
-                                              browser_context_)
-          ->GetServiceWorkerContext();
+      util::GetServiceWorkerContextForExtensionId(extension_->id(),
+                                                  browser_context_);
 
   service_worker_context->ExecuteScriptForTest(  // IN-TEST
       script_, worker_ids[0].version_id,
diff --git a/extensions/browser/event_router.h b/extensions/browser/event_router.h
index 0e127bd..6f6e90d 100644
--- a/extensions/browser/event_router.h
+++ b/extensions/browser/event_router.h
@@ -357,6 +357,8 @@
                            OnUserSiteSettingsChanged);
   FRIEND_TEST_ALL_PREFIXES(DeveloperPrivateApiAllowlistUnitTest,
                            ExtensionUpdatedEventOnAllowlistWarningChange);
+  FRIEND_TEST_ALL_PREFIXES(DeveloperPrivateApiWithPermittedSitesUnitTest,
+                           OnUserSiteSettingsChanged);
   FRIEND_TEST_ALL_PREFIXES(StorageApiUnittest, StorageAreaOnChanged);
   FRIEND_TEST_ALL_PREFIXES(StorageApiUnittest,
                            StorageAreaOnChangedOtherListener);
diff --git a/extensions/browser/extension_event_histogram_value.h b/extensions/browser/extension_event_histogram_value.h
index cb390b3..4ab4369 100644
--- a/extensions/browser/extension_event_histogram_value.h
+++ b/extensions/browser/extension_event_histogram_value.h
@@ -523,6 +523,7 @@
   SMART_CARD_PROVIDER_PRIVATE_ON_RELEASE_CONTEXT_REQUESTED = 501,
   SMART_CARD_PROVIDER_PRIVATE_ON_LIST_READERS_REQUESTED = 502,
   SMART_CARD_PROVIDER_PRIVATE_ON_GET_STATUS_CHANGE_REQUESTED = 503,
+  PDF_VIEWER_PRIVATE_ON_PDF_OCR_PREF_CHANGED = 504,
   // Last entry: Add new entries above, then run:
   // tools/metrics/histograms/update_extension_histograms.py
   ENUM_BOUNDARY
diff --git a/extensions/browser/extension_registrar.cc b/extensions/browser/extension_registrar.cc
index df518a86..3fae9fb 100644
--- a/extensions/browser/extension_registrar.cc
+++ b/extensions/browser/extension_registrar.cc
@@ -13,7 +13,6 @@
 #include "content/public/browser/browser_context.h"
 #include "content/public/browser/browser_thread.h"
 #include "content/public/browser/devtools_agent_host.h"
-#include "content/public/browser/storage_partition.h"
 #include "extensions/browser/blocklist_extension_prefs.h"
 #include "extensions/browser/extension_host.h"
 #include "extensions/browser/extension_prefs.h"
@@ -284,9 +283,8 @@
     }
   } else {
     content::ServiceWorkerContext* context =
-        util::GetStoragePartitionForExtensionId(
-            extension->id(), process_manager->browser_context())
-            ->GetServiceWorkerContext();
+        util::GetServiceWorkerContextForExtensionId(
+            extension->id(), process_manager->browser_context());
     std::vector<WorkerId> service_worker_ids =
         process_manager->GetServiceWorkersForExtension(extension->id());
     for (const auto& worker_id : service_worker_ids) {
@@ -516,9 +514,8 @@
   // extension ServiceWorkerTaskQueue and would prevent newer service worker
   // version from installing (crbug/1340341).
   content::ServiceWorkerContext* context =
-      util::GetStoragePartitionForExtensionId(new_extension->id(),
-                                              browser_context_)
-          ->GetServiceWorkerContext();
+      util::GetServiceWorkerContextForExtensionId(new_extension->id(),
+                                                  browser_context_);
   // Even though the unregistration process for a service worker is
   // asynchronous, we begin the process before the new extension is added, so
   // the old worker will be unregistered before the new one is registered.
diff --git a/extensions/browser/extension_util.cc b/extensions/browser/extension_util.cc
index 06c313d..b0b0b815 100644
--- a/extensions/browser/extension_util.cc
+++ b/extensions/browser/extension_util.cc
@@ -12,6 +12,7 @@
 #include "content/public/browser/child_process_security_policy.h"
 #include "content/public/browser/render_frame_host.h"
 #include "content/public/browser/site_instance.h"
+#include "content/public/browser/storage_partition.h"
 #include "content/public/browser/storage_partition_config.h"
 #include "extensions/browser/extension_prefs.h"
 #include "extensions/browser/extension_registry.h"
@@ -117,6 +118,13 @@
   return storage_partition;
 }
 
+content::ServiceWorkerContext* GetServiceWorkerContextForExtensionId(
+    const ExtensionId& extension_id,
+    content::BrowserContext* browser_context) {
+  return GetStoragePartitionForExtensionId(extension_id, browser_context)
+      ->GetServiceWorkerContext();
+}
+
 // This function is security sensitive. Bugs could cause problems that break
 // restrictions on local file access or NaCl's validation caching. If you modify
 // this function, please get a security review from a NaCl person.
diff --git a/extensions/browser/extension_util.h b/extensions/browser/extension_util.h
index f8705a6..04b1df9 100644
--- a/extensions/browser/extension_util.h
+++ b/extensions/browser/extension_util.h
@@ -18,6 +18,7 @@
 
 namespace content {
 class BrowserContext;
+class ServiceWorkerContext;
 class SiteInstance;
 class StoragePartition;
 class StoragePartitionConfig;
@@ -62,6 +63,11 @@
     content::BrowserContext* browser_context,
     bool can_create = true);
 
+// Returns the ServiceWorkerContext associated with the given `extension_id`.
+content::ServiceWorkerContext* GetServiceWorkerContextForExtensionId(
+    const ExtensionId& extension_id,
+    content::BrowserContext* browser_context);
+
 // Maps a |file_url| to a |file_path| on the local filesystem, including
 // resources in extensions. Returns true on success. See NaClBrowserDelegate for
 // full details. If |use_blocking_api| is false, only a subset of URLs will be
diff --git a/extensions/browser/permissions_manager.cc b/extensions/browser/permissions_manager.cc
index 8e19db9..22cb518 100644
--- a/extensions/browser/permissions_manager.cc
+++ b/extensions/browser/permissions_manager.cc
@@ -201,8 +201,12 @@
       extension_prefs_(ExtensionPrefs::Get(browser_context)) {
   user_permissions_.restricted_sites =
       GetSitesFromPrefs(extension_prefs_, kRestrictedSites);
-  user_permissions_.permitted_sites =
-      GetSitesFromPrefs(extension_prefs_, kPermittedSites);
+  if (base::FeatureList::IsEnabled(
+          extensions_features::
+              kExtensionsMenuAccessControlWithPermittedSites)) {
+    user_permissions_.permitted_sites =
+        GetSitesFromPrefs(extension_prefs_, kPermittedSites);
+  }
 }
 
 PermissionsManager::~PermissionsManager() {
@@ -234,13 +238,21 @@
     PermissionsManager::UserSiteSetting site_setting) {
   switch (site_setting) {
     case UserSiteSetting::kGrantAllExtensions:
+      // Granting access to all extensions is allowed iff feature is
+      // enabled.
+      DCHECK(base::FeatureList::IsEnabled(
+          extensions_features::kExtensionsMenuAccessControlWithPermittedSites));
       AddUserPermittedSite(origin);
       break;
     case UserSiteSetting::kBlockAllExtensions:
       AddUserRestrictedSite(origin);
       break;
     case UserSiteSetting::kCustomizeByExtension:
-      RemoveUserPermittedSite(origin);
+      if (base::FeatureList::IsEnabled(
+              extensions_features::
+                  kExtensionsMenuAccessControlWithPermittedSites)) {
+        RemoveUserPermittedSite(origin);
+      }
       RemoveUserRestrictedSite(origin);
       break;
   }
@@ -264,8 +276,12 @@
 }
 
 void PermissionsManager::AddUserPermittedSite(const url::Origin& origin) {
-  if (base::Contains(user_permissions_.permitted_sites, origin))
+  DCHECK(base::FeatureList::IsEnabled(
+      extensions_features::kExtensionsMenuAccessControlWithPermittedSites));
+
+  if (base::Contains(user_permissions_.permitted_sites, origin)) {
     return;
+  }
 
   // Origin cannot be both restricted and permitted.
   RemoveRestrictedSiteAndUpdatePrefs(origin);
@@ -306,6 +322,9 @@
 }
 
 void PermissionsManager::RemoveUserPermittedSite(const url::Origin& origin) {
+  DCHECK(base::FeatureList::IsEnabled(
+      extensions_features::kExtensionsMenuAccessControlWithPermittedSites));
+
   if (RemovePermittedSiteAndUpdatePrefs(origin))
     OnUserPermissionsSettingsChanged();
 }
diff --git a/extensions/browser/permissions_manager_unittest.cc b/extensions/browser/permissions_manager_unittest.cc
index d163aeb88..7c4f573 100644
--- a/extensions/browser/permissions_manager_unittest.cc
+++ b/extensions/browser/permissions_manager_unittest.cc
@@ -4,14 +4,15 @@
 
 #include "extensions/browser/permissions_manager.h"
 #include "base/memory/raw_ptr.h"
+#include "base/test/gtest_util.h"
+#include "base/test/scoped_feature_list.h"
 #include "extensions/browser/extension_prefs.h"
 #include "extensions/browser/extension_registry.h"
 #include "extensions/browser/extension_util.h"
 #include "extensions/browser/extensions_test.h"
 #include "extensions/browser/pref_types.h"
 #include "extensions/common/extension_builder.h"
-#include "extensions/common/extension_icon_set.h"
-#include "extensions/common/extensions_client.h"
+#include "extensions/common/extension_features.h"
 #include "extensions/common/permissions/permissions_data.h"
 #include "extensions/common/url_pattern_set.h"
 #include "testing/gmock/include/gmock/gmock.h"
@@ -186,90 +187,16 @@
 }
 
 TEST_F(PermissionsManagerUnittest, AddAndRemovePermittedSite) {
-  const url::Origin url = url::Origin::Create(GURL("http://a.example.com"));
-  const std::string expected_url_pattern = "http://a.example.com/*";
-  std::set<url::Origin> set_with_url;
-  set_with_url.insert(url);
-  base::Value::List value_with_url;
-  value_with_url.Append(url.Serialize());
-
   // Verify the permitted sites list is empty.
   EXPECT_EQ(GetPermittedSitesFromManager(), std::set<url::Origin>());
   EXPECT_EQ(GetPermittedSitesFromPrefs(), nullptr);
   EXPECT_THAT(GetPermittedSitesFromPermissionsData(), testing::IsEmpty());
-  EXPECT_EQ(manager_->GetUserSiteSetting(url),
-            PermissionsManager::UserSiteSetting::kCustomizeByExtension);
 
-  // Add `url` to permitted sites. Verify the site is stored both in manager
-  // and prefs permitted sites.
-  manager_->AddUserPermittedSite(url);
-  EXPECT_EQ(GetPermittedSitesFromManager(), set_with_url);
-  EXPECT_EQ(*GetPermittedSitesFromPrefs(), value_with_url);
-  EXPECT_THAT(GetPermittedSitesFromPermissionsData(),
-              testing::UnorderedElementsAre(expected_url_pattern));
-  EXPECT_EQ(manager_->GetUserSiteSetting(url),
-            PermissionsManager::UserSiteSetting::kGrantAllExtensions);
-
-  // Adding an existent permitted site. Verify the entry is not duplicated.
-  manager_->AddUserPermittedSite(url);
-  EXPECT_EQ(GetPermittedSitesFromManager(), set_with_url);
-  EXPECT_EQ(*GetPermittedSitesFromPrefs(), value_with_url);
-  EXPECT_THAT(GetPermittedSitesFromPermissionsData(),
-              testing::UnorderedElementsAre(expected_url_pattern));
-
-  // Remove `url` from permitted sites. Verify the site is removed from both
-  // manager and prefs permitted sites.
-  manager_->RemoveUserPermittedSite(url);
-  EXPECT_EQ(GetPermittedSitesFromManager(), std::set<url::Origin>());
-  EXPECT_EQ(*GetPermittedSitesFromPrefs(),
-            base::Value(base::Value::Type::LIST));
-  EXPECT_THAT(GetPermittedSitesFromPermissionsData(), testing::IsEmpty());
-  EXPECT_EQ(manager_->GetUserSiteSetting(url),
-            PermissionsManager::UserSiteSetting::kCustomizeByExtension);
-}
-
-TEST_F(PermissionsManagerUnittest,
-       RestrictedAndPermittedSitesAreMutuallyExclusive) {
+  // Adding or removing a permitted site is only supported when
+  // kExtensionsMenuAccessControlWithPermittedSites is enabled.
   const url::Origin url = url::Origin::Create(GURL("http://a.example.com"));
-  std::set<url::Origin> empty_set;
-  std::set<url::Origin> set_with_url;
-  set_with_url.insert(url);
-
-  {
-    manager_->AddUserRestrictedSite(url);
-    const PermissionsManager::UserPermissionsSettings& actual_permissions =
-        manager_->GetUserPermissionsSettings();
-    EXPECT_EQ(actual_permissions.restricted_sites, set_with_url);
-    EXPECT_EQ(actual_permissions.permitted_sites, empty_set);
-    EXPECT_EQ(manager_->GetUserSiteSetting(url),
-              PermissionsManager::UserSiteSetting::kBlockAllExtensions);
-  }
-
-  {
-    // Adding an url to the permitted sites that is already in the restricted
-    // sites should remove it from restricted sites and add it to permitted
-    // sites.
-    manager_->AddUserPermittedSite(url);
-    const PermissionsManager::UserPermissionsSettings& actual_permissions =
-        manager_->GetUserPermissionsSettings();
-    EXPECT_EQ(actual_permissions.restricted_sites, empty_set);
-    EXPECT_EQ(actual_permissions.permitted_sites, set_with_url);
-    EXPECT_EQ(manager_->GetUserSiteSetting(url),
-              PermissionsManager::UserSiteSetting::kGrantAllExtensions);
-  }
-
-  {
-    // Adding an url to the restricted sites that is already in the permitted
-    // sites should remove it from permitted sites and add it to restricted
-    // sites.
-    manager_->AddUserRestrictedSite(url);
-    const PermissionsManager::UserPermissionsSettings& actual_permissions =
-        manager_->GetUserPermissionsSettings();
-    EXPECT_EQ(actual_permissions.restricted_sites, set_with_url);
-    EXPECT_EQ(actual_permissions.permitted_sites, empty_set);
-    EXPECT_EQ(manager_->GetUserSiteSetting(url),
-              PermissionsManager::UserSiteSetting::kBlockAllExtensions);
-  }
+  EXPECT_DCHECK_DEATH(manager_->AddUserPermittedSite(url));
+  EXPECT_DCHECK_DEATH(manager_->RemoveUserPermittedSite(url));
 }
 
 TEST_F(PermissionsManagerUnittest, UpdateUserSiteSetting) {
@@ -279,14 +206,10 @@
   set_with_url.insert(url);
 
   {
-    manager_->UpdateUserSiteSetting(
-        url, PermissionsManager::UserSiteSetting::kGrantAllExtensions);
-    const PermissionsManager::UserPermissionsSettings& actual_permissions =
-        manager_->GetUserPermissionsSettings();
-    EXPECT_EQ(actual_permissions.restricted_sites, empty_set);
-    EXPECT_EQ(actual_permissions.permitted_sites, set_with_url);
-    EXPECT_EQ(manager_->GetUserSiteSetting(url),
-              PermissionsManager::UserSiteSetting::kGrantAllExtensions);
+    // Granting all extensions is only supported when
+    // kExtensionsMenuAccessControlWithPermittedSites flag is enabled.
+    EXPECT_DCHECK_DEATH(manager_->UpdateUserSiteSetting(
+        url, PermissionsManager::UserSiteSetting::kGrantAllExtensions));
   }
 
   {
@@ -448,4 +371,130 @@
   }
 }
 
+class PermissionsManagerWithPermittedSitesUnitTest
+    : public PermissionsManagerUnittest {
+ public:
+  PermissionsManagerWithPermittedSitesUnitTest();
+  PermissionsManagerWithPermittedSitesUnitTest(
+      const PermissionsManagerWithPermittedSitesUnitTest&) = delete;
+  const PermissionsManagerWithPermittedSitesUnitTest& operator=(
+      const PermissionsManagerWithPermittedSitesUnitTest&) = delete;
+  ~PermissionsManagerWithPermittedSitesUnitTest() override = default;
+
+ private:
+  base::test::ScopedFeatureList feature_list_;
+};
+
+PermissionsManagerWithPermittedSitesUnitTest::
+    PermissionsManagerWithPermittedSitesUnitTest() {
+  feature_list_.InitAndEnableFeature(
+      extensions_features::kExtensionsMenuAccessControlWithPermittedSites);
+}
+
+TEST_F(PermissionsManagerWithPermittedSitesUnitTest,
+       AddAndRemovePermittedSite) {
+  const url::Origin url = url::Origin::Create(GURL("http://a.example.com"));
+  const std::string expected_url_pattern = "http://a.example.com/*";
+  std::set<url::Origin> set_with_url;
+  set_with_url.insert(url);
+  base::Value::List value_with_url;
+  value_with_url.Append(url.Serialize());
+
+  // Verify the permitted sites list is empty.
+  EXPECT_EQ(GetPermittedSitesFromManager(), std::set<url::Origin>());
+  EXPECT_EQ(GetPermittedSitesFromPrefs(), nullptr);
+  EXPECT_THAT(GetPermittedSitesFromPermissionsData(), testing::IsEmpty());
+  EXPECT_EQ(manager_->GetUserSiteSetting(url),
+            PermissionsManager::UserSiteSetting::kCustomizeByExtension);
+
+  manager_->AddUserPermittedSite(url);
+
+  // Verify the site is stored both in manager and prefs permitted sites.
+  EXPECT_EQ(GetPermittedSitesFromManager(), set_with_url);
+  EXPECT_EQ(*GetPermittedSitesFromPrefs(), value_with_url);
+  EXPECT_THAT(GetPermittedSitesFromPermissionsData(),
+              testing::UnorderedElementsAre(expected_url_pattern));
+  EXPECT_EQ(manager_->GetUserSiteSetting(url),
+            PermissionsManager::UserSiteSetting::kGrantAllExtensions);
+
+  // Adding an existent permitted site.
+  manager_->AddUserPermittedSite(url);
+
+  // Verify the entry is not duplicated.
+  EXPECT_EQ(GetPermittedSitesFromManager(), set_with_url);
+  EXPECT_EQ(*GetPermittedSitesFromPrefs(), value_with_url);
+  EXPECT_THAT(GetPermittedSitesFromPermissionsData(),
+              testing::UnorderedElementsAre(expected_url_pattern));
+
+  // Remove `url` from permitted sites. Verify the site is removed from both
+  // manager and prefs permitted sites.
+  manager_->RemoveUserPermittedSite(url);
+  EXPECT_EQ(GetPermittedSitesFromManager(), std::set<url::Origin>());
+  EXPECT_EQ(*GetPermittedSitesFromPrefs(),
+            base::Value(base::Value::Type::LIST));
+  EXPECT_THAT(GetPermittedSitesFromPermissionsData(), testing::IsEmpty());
+  EXPECT_EQ(manager_->GetUserSiteSetting(url),
+            PermissionsManager::UserSiteSetting::kCustomizeByExtension);
+}
+
+TEST_F(PermissionsManagerWithPermittedSitesUnitTest, GrantAllExtensionsAccess) {
+  const url::Origin url = url::Origin::Create(GURL("http://a.example.com"));
+  std::set<url::Origin> empty_set;
+  std::set<url::Origin> set_with_url;
+  set_with_url.insert(url);
+
+  manager_->UpdateUserSiteSetting(
+      url, PermissionsManager::UserSiteSetting::kGrantAllExtensions);
+  const PermissionsManager::UserPermissionsSettings& actual_permissions =
+      manager_->GetUserPermissionsSettings();
+  EXPECT_EQ(actual_permissions.restricted_sites, empty_set);
+  EXPECT_EQ(actual_permissions.permitted_sites, set_with_url);
+  EXPECT_EQ(manager_->GetUserSiteSetting(url),
+            PermissionsManager::UserSiteSetting::kGrantAllExtensions);
+}
+
+TEST_F(PermissionsManagerWithPermittedSitesUnitTest,
+       RestrictedAndPermittedSitesAreMutuallyExclusive) {
+  const url::Origin url = url::Origin::Create(GURL("http://a.example.com"));
+  std::set<url::Origin> empty_set;
+  std::set<url::Origin> set_with_url;
+  set_with_url.insert(url);
+
+  {
+    manager_->AddUserRestrictedSite(url);
+    const PermissionsManager::UserPermissionsSettings& actual_permissions =
+        manager_->GetUserPermissionsSettings();
+    EXPECT_EQ(actual_permissions.restricted_sites, set_with_url);
+    EXPECT_EQ(actual_permissions.permitted_sites, empty_set);
+    EXPECT_EQ(manager_->GetUserSiteSetting(url),
+              PermissionsManager::UserSiteSetting::kBlockAllExtensions);
+  }
+
+  {
+    // Adding an url to the permitted sites that is already in the restricted
+    // sites should remove it from restricted sites and add it to permitted
+    // sites.
+    manager_->AddUserPermittedSite(url);
+    const PermissionsManager::UserPermissionsSettings& actual_permissions =
+        manager_->GetUserPermissionsSettings();
+    EXPECT_EQ(actual_permissions.restricted_sites, empty_set);
+    EXPECT_EQ(actual_permissions.permitted_sites, set_with_url);
+    EXPECT_EQ(manager_->GetUserSiteSetting(url),
+              PermissionsManager::UserSiteSetting::kGrantAllExtensions);
+  }
+
+  {
+    // Adding an url to the restricted sites that is already in the permitted
+    // sites should remove it from permitted sites and add it to restricted
+    // sites.
+    manager_->AddUserRestrictedSite(url);
+    const PermissionsManager::UserPermissionsSettings& actual_permissions =
+        manager_->GetUserPermissionsSettings();
+    EXPECT_EQ(actual_permissions.restricted_sites, set_with_url);
+    EXPECT_EQ(actual_permissions.permitted_sites, empty_set);
+    EXPECT_EQ(manager_->GetUserSiteSetting(url),
+              PermissionsManager::UserSiteSetting::kBlockAllExtensions);
+  }
+}
+
 }  // namespace extensions
diff --git a/extensions/browser/process_manager.cc b/extensions/browser/process_manager.cc
index 3baf99b..115f31f 100644
--- a/extensions/browser/process_manager.cc
+++ b/extensions/browser/process_manager.cc
@@ -33,7 +33,6 @@
 #include "content/public/browser/service_worker_context.h"
 #include "content/public/browser/service_worker_external_request_result.h"
 #include "content/public/browser/site_instance.h"
-#include "content/public/browser/storage_partition.h"
 #include "content/public/browser/web_contents.h"
 #include "content/public/common/url_constants.h"
 #include "extensions/browser/extension_host.h"
@@ -767,8 +766,8 @@
 
   std::string request_uuid = base::GenerateGUID();
   content::ServiceWorkerContext* service_worker_context =
-      util::GetStoragePartitionForExtensionId(extension->id(), browser_context_)
-          ->GetServiceWorkerContext();
+      util::GetServiceWorkerContextForExtensionId(extension->id(),
+                                                  browser_context_);
 
   service_worker_context->StartingExternalRequest(service_worker_version_id,
                                                   timeout_type, request_uuid);
@@ -824,8 +823,8 @@
 
   int64_t service_worker_version_id = worker_id.version_id;
   content::ServiceWorkerContext* service_worker_context =
-      util::GetStoragePartitionForExtensionId(extension->id(), browser_context_)
-          ->GetServiceWorkerContext();
+      util::GetServiceWorkerContextForExtensionId(extension->id(),
+                                                  browser_context_);
 
   content::ServiceWorkerExternalRequestResult result =
       service_worker_context->FinishedExternalRequest(service_worker_version_id,
diff --git a/extensions/browser/service_worker_manager.cc b/extensions/browser/service_worker_manager.cc
index a186054..d229678 100644
--- a/extensions/browser/service_worker_manager.cc
+++ b/extensions/browser/service_worker_manager.cc
@@ -8,7 +8,6 @@
 #include "base/functional/callback_helpers.h"
 #include "content/public/browser/browser_context.h"
 #include "content/public/browser/service_worker_context.h"
-#include "content/public/browser/storage_partition.h"
 #include "extensions/browser/extension_util.h"
 #include "third_party/blink/public/common/storage_key/storage_key.h"
 
@@ -26,8 +25,7 @@
     content::BrowserContext* browser_context,
     const Extension* extension,
     UnloadedExtensionReason reason) {
-  util::GetStoragePartitionForExtensionId(extension->id(), browser_context_)
-      ->GetServiceWorkerContext()
+  util::GetServiceWorkerContextForExtensionId(extension->id(), browser_context_)
       ->StopAllServiceWorkersForStorageKey(
           blink::StorageKey::CreateFirstParty(extension->origin()));
 }
@@ -40,8 +38,7 @@
   // a) Keep track of extensions with registered service workers.
   // b) Add a callback to the (Un)SuspendServiceWorkersOnOrigin() method.
   // c) Check for any orphaned workers.
-  util::GetStoragePartitionForExtensionId(extension->id(), browser_context_)
-      ->GetServiceWorkerContext()
+  util::GetServiceWorkerContextForExtensionId(extension->id(), browser_context_)
       ->DeleteForStorageKey(
           blink::StorageKey::CreateFirstParty(extension->origin()),
           base::DoNothing());
diff --git a/extensions/browser/service_worker_task_queue.cc b/extensions/browser/service_worker_task_queue.cc
index 67e355ba..5afd0990 100644
--- a/extensions/browser/service_worker_task_queue.cc
+++ b/extensions/browser/service_worker_task_queue.cc
@@ -490,12 +490,8 @@
   WorkerState* worker_state = GetWorkerState(context_id);
   DCHECK_NE(BrowserState::kStarted, worker_state->browser_state_);
 
-  content::StoragePartition* partition =
-      util::GetStoragePartitionForExtensionId(
-          lazy_context_id.extension_id(), lazy_context_id.browser_context());
-
   content::ServiceWorkerContext* service_worker_context =
-      partition->GetServiceWorkerContext();
+      GetServiceWorkerContext(lazy_context_id.extension_id());
 
   const GURL& scope = context_id.first.service_worker_scope();
   service_worker_context->StartWorkerForScope(
@@ -720,8 +716,8 @@
 
 content::ServiceWorkerContext* ServiceWorkerTaskQueue::GetServiceWorkerContext(
     const ExtensionId& extension_id) {
-  return util::GetStoragePartitionForExtensionId(extension_id, browser_context_)
-      ->GetServiceWorkerContext();
+  return util::GetServiceWorkerContextForExtensionId(extension_id,
+                                                     browser_context_);
 }
 
 void ServiceWorkerTaskQueue::StartObserving(
diff --git a/extensions/common/extension_features.cc b/extensions/common/extension_features.cc
index 17f65053..a3a4115 100644
--- a/extensions/common/extension_features.cc
+++ b/extensions/common/extension_features.cc
@@ -156,4 +156,11 @@
              "TelemetryExtensionPendingApprovalApi",
              base::FEATURE_DISABLED_BY_DEFAULT);
 
+// If enabled, user permitted sites are granted access. This should only happen
+// if kExtensionsMenuAccessControl is enabled, since it's the only entry point
+// where user could set permitted sites.
+BASE_FEATURE(kExtensionsMenuAccessControlWithPermittedSites,
+             "ExtensionsMenuAccessControlWithPermittedSitesName",
+             base::FEATURE_DISABLED_BY_DEFAULT);
+
 }  // namespace extensions_features
diff --git a/extensions/common/extension_features.h b/extensions/common/extension_features.h
index 6cf4433..22aff15f 100644
--- a/extensions/common/extension_features.h
+++ b/extensions/common/extension_features.h
@@ -54,6 +54,8 @@
 
 BASE_DECLARE_FEATURE(kTelemetryExtensionPendingApprovalApi);
 
+BASE_DECLARE_FEATURE(kExtensionsMenuAccessControlWithPermittedSites);
+
 }  // namespace extensions_features
 
 #endif  // EXTENSIONS_COMMON_EXTENSION_FEATURES_H_
diff --git a/extensions/renderer/bindings/api_binding_test_util.cc b/extensions/renderer/bindings/api_binding_test_util.cc
index 84d5759..d2d8651 100644
--- a/extensions/renderer/bindings/api_binding_test_util.cc
+++ b/extensions/renderer/bindings/api_binding_test_util.cc
@@ -91,13 +91,6 @@
   return std::move(value).TakeDict();
 }
 
-std::unique_ptr<base::Value> DeprecatedValueFromString(base::StringPiece str) {
-  std::unique_ptr<base::Value> value =
-      base::JSONReader::ReadDeprecated(ReplaceSingleQuotes(str));
-  EXPECT_TRUE(value) << str;
-  return value;
-}
-
 std::string ValueToString(const base::ValueView& value_view) {
   std::string json;
   EXPECT_TRUE(base::JSONWriter::Write(value_view, &json));
diff --git a/extensions/renderer/bindings/api_binding_test_util.h b/extensions/renderer/bindings/api_binding_test_util.h
index 0da018a4..2b4395a 100644
--- a/extensions/renderer/bindings/api_binding_test_util.h
+++ b/extensions/renderer/bindings/api_binding_test_util.h
@@ -28,10 +28,6 @@
 // As above, but returning a Value::Dict.
 base::Value::Dict DictValueFromString(base::StringPiece str);
 
-// Returns a base::Value parsed from |str|. EXPECTs the conversion to succeed.
-// DEPRECATED: prefer `ValueFromString`.
-std::unique_ptr<base::Value> DeprecatedValueFromString(base::StringPiece str);
-
 // Converts the given |value| to a JSON string. EXPECTs the conversion to
 // succeed.
 std::string ValueToString(const base::ValueView&);
diff --git a/extensions/renderer/bindings/argument_spec_unittest.cc b/extensions/renderer/bindings/argument_spec_unittest.cc
index f364d8c..99a563f 100644
--- a/extensions/renderer/bindings/argument_spec_unittest.cc
+++ b/extensions/renderer/bindings/argument_spec_unittest.cc
@@ -194,7 +194,7 @@
 
 TEST_F(ArgumentSpecUnitTest, Test) {
   {
-    ArgumentSpec spec(*DeprecatedValueFromString("{'type': 'integer'}"));
+    ArgumentSpec spec(ValueFromString("{'type': 'integer'}"));
     ExpectSuccess(spec, "1", "1");
     ExpectSuccess(spec, "-1", "-1");
     ExpectSuccess(spec, "0", "0");
@@ -215,7 +215,7 @@
   }
 
   {
-    ArgumentSpec spec(*DeprecatedValueFromString("{'type': 'number'}"));
+    ArgumentSpec spec(ValueFromString("{'type': 'number'}"));
     ExpectSuccess(spec, "1", "1.0");
     ExpectSuccess(spec, "-1", "-1.0");
     ExpectSuccess(spec, "0", "0.0");
@@ -233,8 +233,7 @@
   }
 
   {
-    ArgumentSpec spec(
-        *DeprecatedValueFromString("{'type': 'integer', 'minimum': 1}"));
+    ArgumentSpec spec(ValueFromString("{'type': 'integer', 'minimum': 1}"));
     ExpectSuccess(spec, "2", "2");
     ExpectSuccess(spec, "1", "1");
     ExpectFailure(spec, "0", api_errors::NumberTooSmall(1));
@@ -242,15 +241,14 @@
   }
 
   {
-    ArgumentSpec spec(
-        *DeprecatedValueFromString("{'type': 'integer', 'maximum': 10}"));
+    ArgumentSpec spec(ValueFromString("{'type': 'integer', 'maximum': 10}"));
     ExpectSuccess(spec, "10", "10");
     ExpectSuccess(spec, "1", "1");
     ExpectFailure(spec, "11", api_errors::NumberTooLarge(10));
   }
 
   {
-    ArgumentSpec spec(*DeprecatedValueFromString("{'type': 'string'}"));
+    ArgumentSpec spec(ValueFromString("{'type': 'string'}"));
     ExpectSuccess(spec, "'foo'", "'foo'");
     ExpectSuccess(spec, "''", "''");
     ExpectFailure(spec, "1", InvalidType(kTypeString, kTypeInteger));
@@ -259,8 +257,8 @@
   }
 
   {
-    ArgumentSpec spec(*DeprecatedValueFromString(
-        "{'type': 'string', 'enum': ['foo', 'bar']}"));
+    ArgumentSpec spec(
+        ValueFromString("{'type': 'string', 'enum': ['foo', 'bar']}"));
     std::set<std::string> valid_enums = {"foo", "bar"};
     ExpectSuccess(spec, "'foo'", "'foo'");
     ExpectSuccess(spec, "'bar'", "'bar'");
@@ -272,7 +270,7 @@
   }
 
   {
-    ArgumentSpec spec(*DeprecatedValueFromString(
+    ArgumentSpec spec(ValueFromString(
         "{'type': 'string', 'enum': [{'name': 'foo'}, {'name': 'bar'}]}"));
     std::set<std::string> valid_enums = {"foo", "bar"};
     ExpectSuccess(spec, "'foo'", "'foo'");
@@ -285,7 +283,7 @@
   }
 
   {
-    ArgumentSpec spec(*DeprecatedValueFromString("{'type': 'boolean'}"));
+    ArgumentSpec spec(ValueFromString("{'type': 'boolean'}"));
     ExpectSuccess(spec, "true", "true");
     ExpectSuccess(spec, "false", "false");
     ExpectFailure(spec, "1", InvalidType(kTypeBoolean, kTypeInteger));
@@ -294,8 +292,8 @@
   }
 
   {
-    ArgumentSpec spec(*DeprecatedValueFromString(
-        "{'type': 'array', 'items': {'type': 'string'}}"));
+    ArgumentSpec spec(
+        ValueFromString("{'type': 'array', 'items': {'type': 'string'}}"));
     ExpectSuccess(spec, "[]", "[]");
     ExpectSuccess(spec, "['foo']", "['foo']");
     ExpectSuccess(spec, "['foo', 'bar']", "['foo','bar']");
@@ -329,7 +327,7 @@
         "    'prop2': {'type': 'integer', 'optional': true}"
         "  }"
         "}";
-    ArgumentSpec spec(*DeprecatedValueFromString(kObjectSpec));
+    ArgumentSpec spec(ValueFromString(kObjectSpec));
     ExpectSuccess(spec, "({prop1: 'foo', prop2: 2})",
                   "{'prop1':'foo','prop2':2}");
     ExpectSuccess(spec, "({prop1: 'foo'})", "{'prop1':'foo'}");
@@ -390,7 +388,7 @@
 
   {
     const char kFunctionSpec[] = "{ 'type': 'function' }";
-    ArgumentSpec spec(*DeprecatedValueFromString(kFunctionSpec));
+    ArgumentSpec spec(ValueFromString(kFunctionSpec));
     // Functions are serialized as empty dictionaries.
     ExpectSuccess(spec, "(function() {})", "{}");
     ExpectSuccessWithNoConversion(spec, "(function() {})");
@@ -406,7 +404,7 @@
 
   {
     const char kBinarySpec[] = "{ 'type': 'binary' }";
-    ArgumentSpec spec(*DeprecatedValueFromString(kBinarySpec));
+    ArgumentSpec spec(ValueFromString(kBinarySpec));
     // Simple case: empty ArrayBuffer -> empty BinaryValue.
     ExpectSuccess(spec, "(new ArrayBuffer())",
                   base::Value(base::Value::Type::BINARY));
@@ -432,7 +430,7 @@
   }
   {
     const char kAnySpec[] = "{ 'type': 'any' }";
-    ArgumentSpec spec(*DeprecatedValueFromString(kAnySpec));
+    ArgumentSpec spec(ValueFromString(kAnySpec));
     ExpectSuccess(spec, "42", "42");
     ExpectSuccess(spec, "'foo'", "'foo'");
     ExpectSuccess(spec, "({prop1:'bar'})", "{'prop1':'bar'}");
@@ -469,10 +467,10 @@
       "}";
   const char kEnumType[] =
       "{'id': 'refEnum', 'type': 'string', 'enum': ['alpha', 'beta']}";
-  AddTypeRef("refObj", std::make_unique<ArgumentSpec>(
-                           *DeprecatedValueFromString(kObjectType)));
-  AddTypeRef("refEnum", std::make_unique<ArgumentSpec>(
-                            *DeprecatedValueFromString(kEnumType)));
+  AddTypeRef("refObj",
+             std::make_unique<ArgumentSpec>(ValueFromString(kObjectType)));
+  AddTypeRef("refEnum",
+             std::make_unique<ArgumentSpec>(ValueFromString(kEnumType)));
   std::set<std::string> valid_enums = {"alpha", "beta"};
 
   {
@@ -485,7 +483,7 @@
         "    'sub': {'type': 'integer'}"
         "  }"
         "}";
-    ArgumentSpec spec(*DeprecatedValueFromString(kObjectWithRefEnumSpec));
+    ArgumentSpec spec(ValueFromString(kObjectWithRefEnumSpec));
     ExpectSuccess(spec, "({e: 'alpha', sub: 1})", "{'e':'alpha','sub':1}");
     ExpectSuccess(spec, "({e: 'beta', sub: 1})", "{'e':'beta','sub':1}");
     ExpectFailure(spec, "({e: 'gamma', sub: 1})",
@@ -503,7 +501,7 @@
         "    'o': {'$ref': 'refObj'}"
         "  }"
         "}";
-    ArgumentSpec spec(*DeprecatedValueFromString(kObjectWithRefObjectSpec));
+    ArgumentSpec spec(ValueFromString(kObjectWithRefObjectSpec));
     ExpectSuccess(spec, "({o: {prop1: 'foo'}})", "{'o':{'prop1':'foo'}}");
     ExpectSuccess(spec, "({o: {prop1: 'foo', prop2: 2}})",
                   "{'o':{'prop1':'foo','prop2':2}}");
@@ -517,7 +515,7 @@
   {
     const char kRefEnumListSpec[] =
         "{'type': 'array', 'items': {'$ref': 'refEnum'}}";
-    ArgumentSpec spec(*DeprecatedValueFromString(kRefEnumListSpec));
+    ArgumentSpec spec(ValueFromString(kRefEnumListSpec));
     ExpectSuccess(spec, "['alpha']", "['alpha']");
     ExpectSuccess(spec, "['alpha', 'alpha']", "['alpha','alpha']");
     ExpectSuccess(spec, "['alpha', 'beta']", "['alpha','beta']");
@@ -530,7 +528,7 @@
   {
     const char kSimpleChoices[] =
         "{'choices': [{'type': 'string'}, {'type': 'integer'}]}";
-    ArgumentSpec spec(*DeprecatedValueFromString(kSimpleChoices));
+    ArgumentSpec spec(ValueFromString(kSimpleChoices));
     ExpectSuccess(spec, "'alpha'", "'alpha'");
     ExpectSuccess(spec, "42", "42");
     const char kChoicesType[] = "[string|integer]";
@@ -545,7 +543,7 @@
         "    {'type': 'object', 'properties': {'prop1': {'type': 'string'}}}"
         "  ]"
         "}";
-    ArgumentSpec spec(*DeprecatedValueFromString(kComplexChoices));
+    ArgumentSpec spec(ValueFromString(kComplexChoices));
     ExpectSuccess(spec, "['alpha']", "['alpha']");
     ExpectSuccess(spec, "['alpha', 'beta']", "['alpha','beta']");
     ExpectSuccess(spec, "({prop1: 'alpha'})", "{'prop1':'alpha'}");
@@ -564,7 +562,7 @@
         "  'type': 'object',"
         "  'additionalProperties': {'type': 'any'}"
         "}";
-    ArgumentSpec spec(*DeprecatedValueFromString(kOnlyAnyAdditionalProperties));
+    ArgumentSpec spec(ValueFromString(kOnlyAnyAdditionalProperties));
     ExpectSuccess(spec, "({prop1: 'alpha', prop2: 42, prop3: {foo: 'bar'}})",
                   "{'prop1':'alpha','prop2':42,'prop3':{'foo':'bar'}}");
     ExpectSuccess(spec, "({})", "{}");
@@ -633,8 +631,7 @@
         "  },"
         "  'additionalProperties': {'type': 'any'}"
         "}";
-    ArgumentSpec spec(
-        *DeprecatedValueFromString(kPropertiesAndAnyAdditionalProperties));
+    ArgumentSpec spec(ValueFromString(kPropertiesAndAnyAdditionalProperties));
     ExpectSuccess(spec, "({prop1: 'alpha', prop2: 42, prop3: {foo: 'bar'}})",
                   "{'prop1':'alpha','prop2':42,'prop3':{'foo':'bar'}}");
     // Additional properties are optional.
@@ -651,7 +648,7 @@
         "  'type': 'object',"
         "  'additionalProperties': {'type': 'string'}"
         "}";
-    ArgumentSpec spec(*DeprecatedValueFromString(kTypedAdditionalProperties));
+    ArgumentSpec spec(ValueFromString(kTypedAdditionalProperties));
     ExpectSuccess(spec, "({prop1: 'alpha', prop2: 'beta', prop3: 'gamma'})",
                   "{'prop1':'alpha','prop2':'beta','prop3':'gamma'}");
     ExpectFailure(spec, "({prop1: 'alpha', prop2: 42})",
@@ -667,7 +664,7 @@
         "  'type': 'object',"
         "  'isInstanceOf': 'RegExp'"
         "}";
-    ArgumentSpec spec(*DeprecatedValueFromString(kInstanceOfRegExp));
+    ArgumentSpec spec(ValueFromString(kInstanceOfRegExp));
     ExpectSuccess(spec, "(new RegExp())", "{}");
     ExpectSuccess(spec, "({ __proto__: RegExp.prototype })", "{}");
     ExpectSuccess(spec,
@@ -696,7 +693,7 @@
         "  'type': 'object',"
         "  'isInstanceOf': 'customClass'"
         "}";
-    ArgumentSpec spec(*DeprecatedValueFromString(kInstanceOfCustomClass));
+    ArgumentSpec spec(ValueFromString(kInstanceOfCustomClass));
     ExpectSuccess(spec,
                   "(function() {\n"
                   "  function customClass() {}\n"
@@ -725,7 +722,7 @@
 TEST_F(ArgumentSpecUnitTest, MinAndMaxLengths) {
   {
     const char kMinLengthString[] = "{'type': 'string', 'minLength': 3}";
-    ArgumentSpec spec(*DeprecatedValueFromString(kMinLengthString));
+    ArgumentSpec spec(ValueFromString(kMinLengthString));
     ExpectSuccess(spec, "'aaa'", "'aaa'");
     ExpectSuccess(spec, "'aaaa'", "'aaaa'");
     ExpectFailure(spec, "'aa'", api_errors::TooFewStringChars(3, 2));
@@ -734,7 +731,7 @@
 
   {
     const char kMaxLengthString[] = "{'type': 'string', 'maxLength': 3}";
-    ArgumentSpec spec(*DeprecatedValueFromString(kMaxLengthString));
+    ArgumentSpec spec(ValueFromString(kMaxLengthString));
     ExpectSuccess(spec, "'aaa'", "'aaa'");
     ExpectSuccess(spec, "'aa'", "'aa'");
     ExpectSuccess(spec, "''", "''");
@@ -744,7 +741,7 @@
   {
     const char kMinLengthArray[] =
         "{'type': 'array', 'items': {'type': 'integer'}, 'minItems': 3}";
-    ArgumentSpec spec(*DeprecatedValueFromString(kMinLengthArray));
+    ArgumentSpec spec(ValueFromString(kMinLengthArray));
     ExpectSuccess(spec, "[1, 2, 3]", "[1,2,3]");
     ExpectSuccess(spec, "[1, 2, 3, 4]", "[1,2,3,4]");
     ExpectFailure(spec, "[1, 2]", api_errors::TooFewArrayItems(3, 2));
@@ -754,7 +751,7 @@
   {
     const char kMaxLengthArray[] =
         "{'type': 'array', 'items': {'type': 'integer'}, 'maxItems': 3}";
-    ArgumentSpec spec(*DeprecatedValueFromString(kMaxLengthArray));
+    ArgumentSpec spec(ValueFromString(kMaxLengthArray));
     ExpectSuccess(spec, "[1, 2, 3]", "[1,2,3]");
     ExpectSuccess(spec, "[1, 2]", "[1,2]");
     ExpectSuccess(spec, "[]", "[]");
@@ -770,7 +767,7 @@
         "  'additionalProperties': {'type': 'any'},"
         "  'preserveNull': true"
         "}";
-    ArgumentSpec spec(*DeprecatedValueFromString(kObjectSpec));
+    ArgumentSpec spec(ValueFromString(kObjectSpec));
     ExpectSuccess(spec, "({foo: 1, bar: null})", "{'bar':null,'foo':1}");
     // Subproperties shouldn't preserve null (if not specified).
     ExpectSuccess(spec, "({prop: {subprop1: 'foo', subprop2: null}})",
@@ -784,7 +781,7 @@
         "  'additionalProperties': {'type': 'any', 'preserveNull': true},"
         "  'preserveNull': true"
         "}";
-    ArgumentSpec spec(*DeprecatedValueFromString(kObjectSpec));
+    ArgumentSpec spec(ValueFromString(kObjectSpec));
     ExpectSuccess(spec, "({foo: 1, bar: null})", "{'bar':null,'foo':1}");
     // Here, subproperties should preserve null.
     ExpectSuccess(spec, "({prop: {subprop1: 'foo', subprop2: null}})",
@@ -798,7 +795,7 @@
         "  'properties': {'prop1': {'type': 'string', 'optional': true}},"
         "  'preserveNull': true"
         "}";
-    ArgumentSpec spec(*DeprecatedValueFromString(kObjectSpec));
+    ArgumentSpec spec(ValueFromString(kObjectSpec));
     ExpectSuccess(spec, "({})", "{}");
     ExpectSuccess(spec, "({prop1: null})", "{'prop1':null}");
     ExpectSuccess(spec, "({prop1: 'foo'})", "{'prop1':'foo'}");
@@ -814,14 +811,14 @@
 TEST_F(ArgumentSpecUnitTest, NaNFun) {
   {
     const char kAnySpec[] = "{'type': 'any'}";
-    ArgumentSpec spec(*DeprecatedValueFromString(kAnySpec));
+    ArgumentSpec spec(ValueFromString(kAnySpec));
     ExpectFailure(spec, "NaN", api_errors::UnserializableValue());
   }
 
   {
     const char kObjectWithAnyPropertiesSpec[] =
         "{'type': 'object', 'additionalProperties': {'type': 'any'}}";
-    ArgumentSpec spec(*DeprecatedValueFromString(kObjectWithAnyPropertiesSpec));
+    ArgumentSpec spec(ValueFromString(kObjectWithAnyPropertiesSpec));
     ExpectSuccess(spec, "({foo: NaN, bar: 'baz'})", "{'bar':'baz'}");
   }
 }
@@ -1033,7 +1030,7 @@
            "type": "function",
            "serializableFunction": true
          })";
-  ArgumentSpec spec(*DeprecatedValueFromString(kFunctionSpec));
+  ArgumentSpec spec(ValueFromString(kFunctionSpec));
 
   constexpr char kExpectedSerialization[] = R"("function() { }")";
   ExpectSuccess(spec, "(function() { })", kExpectedSerialization);
diff --git a/google_apis/BUILD.gn b/google_apis/BUILD.gn
index 04ef849a..a177cc2b 100644
--- a/google_apis/BUILD.gn
+++ b/google_apis/BUILD.gn
@@ -110,100 +110,90 @@
   }
 }
 
-# Variables:
-#   deps: Extra dependencies
-template("google_apis_tmpl") {
-  source_set(target_name) {
-    sources = [
-      "gaia/core_account_id.cc",
-      "gaia/core_account_id.h",
-      "gaia/gaia_access_token_fetcher.cc",
-      "gaia/gaia_access_token_fetcher.h",
-      "gaia/gaia_auth_consumer.cc",
-      "gaia/gaia_auth_consumer.h",
-      "gaia/gaia_auth_fetcher.cc",
-      "gaia/gaia_auth_fetcher.h",
-      "gaia/gaia_auth_util.cc",
-      "gaia/gaia_auth_util.h",
-      "gaia/gaia_config.cc",
-      "gaia/gaia_config.h",
-      "gaia/gaia_constants.cc",
-      "gaia/gaia_constants.h",
-      "gaia/gaia_oauth_client.cc",
-      "gaia/gaia_oauth_client.h",
-      "gaia/gaia_switches.cc",
-      "gaia/gaia_switches.h",
-      "gaia/gaia_urls.cc",
-      "gaia/gaia_urls.h",
-      "gaia/google_service_auth_error.cc",
-      "gaia/google_service_auth_error.h",
-      "gaia/oauth2_access_token_consumer.cc",
-      "gaia/oauth2_access_token_consumer.h",
-      "gaia/oauth2_access_token_fetcher.cc",
-      "gaia/oauth2_access_token_fetcher.h",
-      "gaia/oauth2_access_token_fetcher_immediate_error.cc",
-      "gaia/oauth2_access_token_fetcher_immediate_error.h",
-      "gaia/oauth2_access_token_fetcher_impl.cc",
-      "gaia/oauth2_access_token_fetcher_impl.h",
-      "gaia/oauth2_access_token_manager.cc",
-      "gaia/oauth2_access_token_manager.h",
-      "gaia/oauth2_api_call_flow.cc",
-      "gaia/oauth2_api_call_flow.h",
-      "gaia/oauth2_id_token_decoder.cc",
-      "gaia/oauth2_id_token_decoder.h",
-      "gaia/oauth2_mint_token_flow.cc",
-      "gaia/oauth2_mint_token_flow.h",
-      "gaia/oauth_multilogin_result.cc",
-      "gaia/oauth_multilogin_result.h",
-      "gaia/oauth_request_signer.cc",
-      "gaia/oauth_request_signer.h",
-      "google_api_keys.cc",
-      "google_api_keys.h",
-    ]
+component("google_apis") {
+  sources = [
+    "credentials_mode.cc",
+    "credentials_mode.h",
+    "gaia/core_account_id.cc",
+    "gaia/core_account_id.h",
+    "gaia/gaia_access_token_fetcher.cc",
+    "gaia/gaia_access_token_fetcher.h",
+    "gaia/gaia_auth_consumer.cc",
+    "gaia/gaia_auth_consumer.h",
+    "gaia/gaia_auth_fetcher.cc",
+    "gaia/gaia_auth_fetcher.h",
+    "gaia/gaia_auth_util.cc",
+    "gaia/gaia_auth_util.h",
+    "gaia/gaia_config.cc",
+    "gaia/gaia_config.h",
+    "gaia/gaia_constants.cc",
+    "gaia/gaia_constants.h",
+    "gaia/gaia_oauth_client.cc",
+    "gaia/gaia_oauth_client.h",
+    "gaia/gaia_switches.cc",
+    "gaia/gaia_switches.h",
+    "gaia/gaia_urls.cc",
+    "gaia/gaia_urls.h",
+    "gaia/google_service_auth_error.cc",
+    "gaia/google_service_auth_error.h",
+    "gaia/oauth2_access_token_consumer.cc",
+    "gaia/oauth2_access_token_consumer.h",
+    "gaia/oauth2_access_token_fetcher.cc",
+    "gaia/oauth2_access_token_fetcher.h",
+    "gaia/oauth2_access_token_fetcher_immediate_error.cc",
+    "gaia/oauth2_access_token_fetcher_immediate_error.h",
+    "gaia/oauth2_access_token_fetcher_impl.cc",
+    "gaia/oauth2_access_token_fetcher_impl.h",
+    "gaia/oauth2_access_token_manager.cc",
+    "gaia/oauth2_access_token_manager.h",
+    "gaia/oauth2_api_call_flow.cc",
+    "gaia/oauth2_api_call_flow.h",
+    "gaia/oauth2_id_token_decoder.cc",
+    "gaia/oauth2_id_token_decoder.h",
+    "gaia/oauth2_mint_token_flow.cc",
+    "gaia/oauth2_mint_token_flow.h",
+    "gaia/oauth_multilogin_result.cc",
+    "gaia/oauth_multilogin_result.h",
+    "gaia/oauth_request_signer.cc",
+    "gaia/oauth_request_signer.h",
+    "google_api_keys.cc",
+    "google_api_keys.h",
+  ]
 
-    configs += [ ":key_defines" ]
+  configs += [ ":key_defines" ]
 
-    public_deps = [
-      ":buildflags",
-      "//build:chromeos_buildflags",
-    ]
-
-    deps = [
-      ":oauth2_mint_token_consent_result_proto",
-      "//base",
-      "//base/third_party/dynamic_annotations",
-      "//build:branding_buildflags",
-      "//build:chromeos_buildflags",
-      "//crypto",
-      "//mojo/public/cpp/bindings:struct_traits",
-      "//services/network/public/cpp",
-    ]
-
-    if (_use_official_google_keys_and_generate_metrics_key_header) {
-      deps += [ "internal:generate_metrics_key_header" ]
-    }
-
-    if (defined(invoker.deps)) {
-      deps += invoker.deps
-    }
-
-    if (is_apple) {
-      sources += [
-        "google_api_keys_mac.h",
-        "google_api_keys_mac.mm",
-      ]
-
-      frameworks = [ "Foundation.framework" ]
-    }
-  }
-}
-
-google_apis_tmpl("google_apis") {
-  deps = [
-    "//net",
+  public_deps = [
+    ":buildflags",
+    "//base",
+    "//build:chromeos_buildflags",
     "//services/network/public/cpp",
+  ]
+
+  deps = [
+    ":oauth2_mint_token_consent_result_proto",
+    "//base/third_party/dynamic_annotations",
+    "//build:branding_buildflags",
+    "//build:chromeos_buildflags",
+    "//crypto",
+    "//mojo/public/cpp/bindings:struct_traits",
+    "//net",
     "//services/network/public/mojom",
   ]
+
+  if (_use_official_google_keys_and_generate_metrics_key_header) {
+    deps += [ "internal:generate_metrics_key_header" ]
+  }
+
+  if (is_apple) {
+    sources += [
+      "google_api_keys_mac.h",
+      "google_api_keys_mac.mm",
+    ]
+
+    frameworks = [ "Foundation.framework" ]
+  }
+
+  defines = [ "IS_GOOGLE_APIS_IMPL" ]
 }
 
 proto_library("oauth2_mint_token_consent_result_proto") {
diff --git a/google_apis/common/base_requests.cc b/google_apis/common/base_requests.cc
index c37149b..741aa2fd 100644
--- a/google_apis/common/base_requests.cc
+++ b/google_apis/common/base_requests.cc
@@ -20,6 +20,7 @@
 #include "base/values.h"
 #include "google_apis/common/request_sender.h"
 #include "google_apis/common/task_util.h"
+#include "google_apis/credentials_mode.h"
 #include "net/base/load_flags.h"
 #include "net/http/http_util.h"
 #include "services/network/public/cpp/resource_request.h"
@@ -174,7 +175,7 @@
   request->url = url;
   request->method = GetRequestType();
   request->load_flags = net::LOAD_DISABLE_CACHE;
-  request->credentials_mode = network::mojom::CredentialsMode::kOmit;
+  request->credentials_mode = GetOmitCredentialsModeForGaiaRequests();
 
   // Add request headers.
   // Note that SetHeader clears the current headers and sets it to the passed-in
diff --git a/google_apis/credentials_mode.cc b/google_apis/credentials_mode.cc
new file mode 100644
index 0000000..54cf67c4
--- /dev/null
+++ b/google_apis/credentials_mode.cc
@@ -0,0 +1,27 @@
+// Copyright 2023 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "google_apis/credentials_mode.h"
+
+#include "base/feature_list.h"
+#include "services/network/public/mojom/fetch_api.mojom.h"
+
+namespace google_apis {
+
+namespace {
+
+BASE_FEATURE(kGaiaCredentialsModeOmitBug_775438_Workaround,
+             "GaiaCredentialsModeOmitBug_775438_Workaround",
+             base::FEATURE_ENABLED_BY_DEFAULT);
+
+}  // namespace
+
+network::mojom::CredentialsMode GetOmitCredentialsModeForGaiaRequests() {
+  return base::FeatureList::IsEnabled(
+             kGaiaCredentialsModeOmitBug_775438_Workaround)
+             ? network::mojom::CredentialsMode::kOmitBug_775438_Workaround
+             : network::mojom::CredentialsMode::kOmit;
+}
+
+}  // namespace google_apis
diff --git a/google_apis/credentials_mode.h b/google_apis/credentials_mode.h
new file mode 100644
index 0000000..132f857
--- /dev/null
+++ b/google_apis/credentials_mode.h
@@ -0,0 +1,21 @@
+// Copyright 2023 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef GOOGLE_APIS_CREDENTIALS_MODE_H_
+#define GOOGLE_APIS_CREDENTIALS_MODE_H_
+
+#include "base/component_export.h"
+#include "services/network/public/mojom/fetch_api.mojom-forward.h"
+
+namespace google_apis {
+
+// Returns the CredentialsMode that should be used for requests to Gaia. It
+// returns kCredentialsMode::kOmit or kOmitBug_775438_Workaround depending on a
+// variations-controlled feature toggle.
+COMPONENT_EXPORT(GOOGLE_APIS)
+network::mojom::CredentialsMode GetOmitCredentialsModeForGaiaRequests();
+
+}  // namespace google_apis
+
+#endif  // GOOGLE_APIS_CREDENTIALS_MODE_H_
diff --git a/google_apis/gaia/core_account_id.h b/google_apis/gaia/core_account_id.h
index ae75a8f..f8f7e48 100644
--- a/google_apis/gaia/core_account_id.h
+++ b/google_apis/gaia/core_account_id.h
@@ -9,6 +9,7 @@
 #include <string>
 #include <vector>
 
+#include "base/component_export.h"
 #include "build/build_config.h"
 #include "build/chromeos_buildflags.h"
 
@@ -24,7 +25,7 @@
 // change on start-up.
 // --------------------------------------------------------------------------
 
-struct CoreAccountId {
+struct COMPONENT_EXPORT(GOOGLE_APIS) CoreAccountId {
   CoreAccountId();
   CoreAccountId(const CoreAccountId&);
   CoreAccountId(CoreAccountId&&) noexcept;
@@ -96,16 +97,21 @@
   std::string id_;
 };
 
+COMPONENT_EXPORT(GOOGLE_APIS)
 bool operator<(const CoreAccountId& lhs, const CoreAccountId& rhs);
 
+COMPONENT_EXPORT(GOOGLE_APIS)
 bool operator==(const CoreAccountId& lhs, const CoreAccountId& rhs);
 
+COMPONENT_EXPORT(GOOGLE_APIS)
 bool operator!=(const CoreAccountId& lhs, const CoreAccountId& rhs);
 
+COMPONENT_EXPORT(GOOGLE_APIS)
 std::ostream& operator<<(std::ostream& out, const CoreAccountId& a);
 
 // Returns the values of the account ids in a vector. Useful especially for
 // logs.
+COMPONENT_EXPORT(GOOGLE_APIS)
 std::vector<std::string> ToStringList(
     const std::vector<CoreAccountId>& account_ids);
 
diff --git a/google_apis/gaia/gaia_access_token_fetcher.h b/google_apis/gaia/gaia_access_token_fetcher.h
index b2d4db9e..571cffb4 100644
--- a/google_apis/gaia/gaia_access_token_fetcher.h
+++ b/google_apis/gaia/gaia_access_token_fetcher.h
@@ -8,6 +8,7 @@
 #include <memory>
 #include <string>
 
+#include "base/component_export.h"
 #include "base/memory/ref_counted.h"
 #include "google_apis/gaia/oauth2_access_token_fetcher_impl.h"
 
@@ -21,7 +22,8 @@
 // tokens from Google's authorization server.  See "Refreshing an access token"
 // for more Google specific info:
 // https://developers.google.com/identity/protocols/oauth2/web-server?csw=1#obtainingaccesstokens
-class GaiaAccessTokenFetcher : public OAuth2AccessTokenFetcherImpl {
+class COMPONENT_EXPORT(GOOGLE_APIS) GaiaAccessTokenFetcher
+    : public OAuth2AccessTokenFetcherImpl {
  public:
   static const char kOAuth2NetResponseCodeHistogramName[];
   static const char kOAuth2ResponseHistogramName[];
diff --git a/google_apis/gaia/gaia_auth_consumer.h b/google_apis/gaia/gaia_auth_consumer.h
index 0cc2391..17cc45d 100644
--- a/google_apis/gaia/gaia_auth_consumer.h
+++ b/google_apis/gaia/gaia_auth_consumer.h
@@ -9,14 +9,16 @@
 #include <string>
 #include <vector>
 
+#include "base/component_export.h"
+
 class GoogleServiceAuthError;
 class OAuthMultiloginResult;
 
 // An interface that defines the callbacks for objects that
 // GaiaAuthFetcher can return data to.
-class GaiaAuthConsumer {
+class COMPONENT_EXPORT(GOOGLE_APIS) GaiaAuthConsumer {
  public:
-  struct ClientOAuthResult {
+  struct COMPONENT_EXPORT(GOOGLE_APIS) ClientOAuthResult {
     ClientOAuthResult(const std::string& new_refresh_token,
                       const std::string& new_access_token,
                       int new_expires_in_secs,
diff --git a/google_apis/gaia/gaia_auth_fetcher.cc b/google_apis/gaia/gaia_auth_fetcher.cc
index 51faad8..24f3b12 100644
--- a/google_apis/gaia/gaia_auth_fetcher.cc
+++ b/google_apis/gaia/gaia_auth_fetcher.cc
@@ -22,6 +22,7 @@
 #include "base/system/sys_info.h"
 #include "base/types/optional_util.h"
 #include "base/values.h"
+#include "google_apis/credentials_mode.h"
 #include "google_apis/gaia/gaia_auth_consumer.h"
 #include "google_apis/gaia/gaia_auth_util.h"
 #include "google_apis/gaia/gaia_constants.h"
@@ -266,7 +267,9 @@
   resource_request->url = gaia_gurl;
   original_url_ = gaia_gurl;
 
-  if (credentials_mode != network::mojom::CredentialsMode::kOmit) {
+  if (credentials_mode != network::mojom::CredentialsMode::kOmit &&
+      credentials_mode !=
+          network::mojom::CredentialsMode::kOmitBug_775438_Workaround) {
     CHECK(gaia::HasGaiaSchemeHostPort(gaia_gurl)) << gaia_gurl;
 
     url::Origin origin = GaiaUrls::GetInstance()->gaia_origin();
@@ -410,10 +413,10 @@
             }
           }
         })");
-  CreateAndStartGaiaFetcher(request_body_, kFormEncodedContentType,
-                            std::string(), oauth2_revoke_gurl_,
-                            network::mojom::CredentialsMode::kOmit,
-                            traffic_annotation);
+  CreateAndStartGaiaFetcher(
+      request_body_, kFormEncodedContentType, std::string(),
+      oauth2_revoke_gurl_, google_apis::GetOmitCredentialsModeForGaiaRequests(),
+      traffic_annotation);
 }
 
 void GaiaAuthFetcher::StartAuthCodeForOAuth2TokenExchange(
@@ -458,7 +461,7 @@
         })");
   CreateAndStartGaiaFetcher(
       request_body_, kFormEncodedContentType, std::string(), oauth2_token_gurl_,
-      network::mojom::CredentialsMode::kOmit, traffic_annotation);
+      google_apis::GetOmitCredentialsModeForGaiaRequests(), traffic_annotation);
 }
 
 void GaiaAuthFetcher::StartMergeSession(const std::string& uber_token,
@@ -549,7 +552,7 @@
         })");
   CreateAndStartGaiaFetcher(
       std::string(), std::string(), authentication_header, uberauth_token_gurl_,
-      network::mojom::CredentialsMode::kOmit, traffic_annotation);
+      google_apis::GetOmitCredentialsModeForGaiaRequests(), traffic_annotation);
 }
 
 void GaiaAuthFetcher::StartListAccounts() {
@@ -747,9 +750,9 @@
   DCHECK(reauth_url.is_valid());
 
   // Start the request.
-  CreateAndStartGaiaFetcher(post_body, kJsonContentType, headers, reauth_url,
-                            network::mojom::CredentialsMode::kOmit,
-                            traffic_annotation);
+  CreateAndStartGaiaFetcher(
+      post_body, kJsonContentType, headers, reauth_url,
+      google_apis::GetOmitCredentialsModeForGaiaRequests(), traffic_annotation);
 }
 
 void GaiaAuthFetcher::StartGetCheckConnectionInfo() {
@@ -780,10 +783,10 @@
             }
           }
         })");
-  CreateAndStartGaiaFetcher(std::string(), std::string(), std::string(),
-                            get_check_connection_info_url_,
-                            network::mojom::CredentialsMode::kOmit,
-                            traffic_annotation);
+  CreateAndStartGaiaFetcher(
+      std::string(), std::string(), std::string(),
+      get_check_connection_info_url_,
+      google_apis::GetOmitCredentialsModeForGaiaRequests(), traffic_annotation);
 }
 
 // static
diff --git a/google_apis/gaia/gaia_auth_fetcher.h b/google_apis/gaia/gaia_auth_fetcher.h
index 9016c95..992bbb3 100644
--- a/google_apis/gaia/gaia_auth_fetcher.h
+++ b/google_apis/gaia/gaia_auth_fetcher.h
@@ -9,6 +9,7 @@
 #include <string>
 #include <vector>
 
+#include "base/component_export.h"
 #include "base/gtest_prod_util.h"
 #include "base/memory/raw_ptr.h"
 #include "base/memory/ref_counted.h"
@@ -43,7 +44,7 @@
 };
 
 // Specifies the "source" parameter for Gaia calls.
-class GaiaSource {
+class COMPONENT_EXPORT(GOOGLE_APIS) GaiaSource {
  public:
   enum Type {
     kChrome,
@@ -72,9 +73,9 @@
 class SharedURLLoaderFactory;
 }  // namespace network
 
-class GaiaAuthFetcher {
+class COMPONENT_EXPORT(GOOGLE_APIS) GaiaAuthFetcher {
  public:
-  struct MultiloginTokenIDPair {
+  struct COMPONENT_EXPORT(GOOGLE_APIS) MultiloginTokenIDPair {
     std::string token_;
     std::string gaia_id_;
 
diff --git a/google_apis/gaia/gaia_auth_fetcher_unittest.cc b/google_apis/gaia/gaia_auth_fetcher_unittest.cc
index e7d9e62..43e264e 100644
--- a/google_apis/gaia/gaia_auth_fetcher_unittest.cc
+++ b/google_apis/gaia/gaia_auth_fetcher_unittest.cc
@@ -17,6 +17,7 @@
 #include "base/test/task_environment.h"
 #include "base/values.h"
 #include "build/build_config.h"
+#include "google_apis/credentials_mode.h"
 #include "google_apis/gaia/gaia_auth_consumer.h"
 #include "google_apis/gaia/gaia_constants.h"
 #include "google_apis/gaia/gaia_urls.h"
@@ -255,7 +256,7 @@
   TestGaiaAuthFetcher auth(&consumer, GetURLLoaderFactory());
   auth.StartAuthCodeForOAuth2TokenExchange("auth_code");
   ASSERT_EQ(received_requests_.size(), 1U);
-  EXPECT_EQ(network::mojom::CredentialsMode::kOmit,
+  EXPECT_EQ(google_apis::GetOmitCredentialsModeForGaiaRequests(),
             received_requests_.at(0).credentials_mode);
   std::string body = GetRequestBodyAsString(&received_requests_.at(0));
   EXPECT_EQ(std::string::npos, body.find("device_type=chrome"));
@@ -274,7 +275,7 @@
                                                        "device_ABCDE_1");
 
   ASSERT_EQ(1U, received_requests_.size());
-  EXPECT_EQ(network::mojom::CredentialsMode::kOmit,
+  EXPECT_EQ(google_apis::GetOmitCredentialsModeForGaiaRequests(),
             received_requests_.at(0).credentials_mode);
   std::string body = GetRequestBodyAsString(&received_requests_.at(0));
   EXPECT_NE(std::string::npos, body.find("device_type=chrome"));
@@ -625,7 +626,8 @@
   TestGaiaAuthFetcher auth(&consumer, GetURLLoaderFactory());
   auth.CreateAndStartGaiaFetcherForTesting(
       /*body=*/"", /*headers=*/"", GaiaUrls::GetInstance()->reauth_api_url(),
-      network::mojom::CredentialsMode ::kOmit, TRAFFIC_ANNOTATION_FOR_TESTS);
+      google_apis::GetOmitCredentialsModeForGaiaRequests(),
+      TRAFFIC_ANNOTATION_FOR_TESTS);
   auth.TestOnURLLoadCompleteInternal(net::OK, net::HTTP_OK, data);
 }
 
@@ -640,7 +642,8 @@
   TestGaiaAuthFetcher auth(&consumer, GetURLLoaderFactory());
   auth.CreateAndStartGaiaFetcherForTesting(
       /*body=*/"", /*headers=*/"", GaiaUrls::GetInstance()->reauth_api_url(),
-      network::mojom::CredentialsMode ::kOmit, TRAFFIC_ANNOTATION_FOR_TESTS);
+      google_apis::GetOmitCredentialsModeForGaiaRequests(),
+      TRAFFIC_ANNOTATION_FOR_TESTS);
   auth.TestOnURLLoadCompleteInternal(net::OK, net::HTTP_BAD_REQUEST, data);
 }
 
@@ -655,7 +658,8 @@
   TestGaiaAuthFetcher auth(&consumer, GetURLLoaderFactory());
   auth.CreateAndStartGaiaFetcherForTesting(
       /*body=*/"", /*headers=*/"", GaiaUrls::GetInstance()->reauth_api_url(),
-      network::mojom::CredentialsMode ::kOmit, TRAFFIC_ANNOTATION_FOR_TESTS);
+      google_apis::GetOmitCredentialsModeForGaiaRequests(),
+      TRAFFIC_ANNOTATION_FOR_TESTS);
   auth.TestOnURLLoadCompleteInternal(net::OK, net::HTTP_BAD_REQUEST, data);
 }
 
@@ -671,7 +675,8 @@
   TestGaiaAuthFetcher auth(&consumer, GetURLLoaderFactory());
   auth.CreateAndStartGaiaFetcherForTesting(
       /*body=*/"", /*headers=*/"", GaiaUrls::GetInstance()->reauth_api_url(),
-      network::mojom::CredentialsMode ::kOmit, TRAFFIC_ANNOTATION_FOR_TESTS);
+      google_apis::GetOmitCredentialsModeForGaiaRequests(),
+      TRAFFIC_ANNOTATION_FOR_TESTS);
   auth.TestOnURLLoadCompleteInternal(net::OK, net::HTTP_FORBIDDEN, data);
 }
 
@@ -686,7 +691,8 @@
   TestGaiaAuthFetcher auth(&consumer, GetURLLoaderFactory());
   auth.CreateAndStartGaiaFetcherForTesting(
       /*body=*/"", /*headers=*/"", GaiaUrls::GetInstance()->reauth_api_url(),
-      network::mojom::CredentialsMode ::kOmit, TRAFFIC_ANNOTATION_FOR_TESTS);
+      google_apis::GetOmitCredentialsModeForGaiaRequests(),
+      TRAFFIC_ANNOTATION_FOR_TESTS);
   auth.TestOnURLLoadCompleteInternal(net::OK, net::HTTP_FORBIDDEN, data);
 }
 
@@ -701,6 +707,7 @@
   TestGaiaAuthFetcher auth(&consumer, GetURLLoaderFactory());
   auth.CreateAndStartGaiaFetcherForTesting(
       /*body=*/"", /*headers=*/"", GaiaUrls::GetInstance()->reauth_api_url(),
-      network::mojom::CredentialsMode ::kOmit, TRAFFIC_ANNOTATION_FOR_TESTS);
+      google_apis::GetOmitCredentialsModeForGaiaRequests(),
+      TRAFFIC_ANNOTATION_FOR_TESTS);
   auth.TestOnURLLoadCompleteInternal(net::OK, net::HTTP_FORBIDDEN, data);
 }
diff --git a/google_apis/gaia/gaia_auth_util.h b/google_apis/gaia/gaia_auth_util.h
index 2c36ac9..58a2696 100644
--- a/google_apis/gaia/gaia_auth_util.h
+++ b/google_apis/gaia/gaia_auth_util.h
@@ -9,13 +9,14 @@
 #include <utility>
 #include <vector>
 
+#include "base/component_export.h"
 #include "google_apis/gaia/core_account_id.h"
 
 class GURL;
 
 namespace gaia {
 
-struct ListedAccount {
+struct COMPONENT_EXPORT(GOOGLE_APIS) ListedAccount {
   // The account's ID, as per Chrome, will be determined in the
   // CookieManagerService.
   CoreAccountId id;
@@ -35,34 +36,41 @@
 // gmail does not consider '.' or caps inside a username to matter.
 // If |email_address| is not a valid, returns it in lower case without
 // additional canonicalization.
+COMPONENT_EXPORT(GOOGLE_APIS)
 std::string CanonicalizeEmail(const std::string& email_address);
 
 // Returns the canonical form of the given domain.
+COMPONENT_EXPORT(GOOGLE_APIS)
 std::string CanonicalizeDomain(const std::string& domain);
 
 // Sanitize emails. Currently, it only ensures all emails have a domain by
 // adding gmail.com if no domain is present.
+COMPONENT_EXPORT(GOOGLE_APIS)
 std::string SanitizeEmail(const std::string& email_address);
 
 // Returns true if the two specified email addresses are the same.  Both
 // addresses are first sanitized and then canonicalized before comparing.
+COMPONENT_EXPORT(GOOGLE_APIS)
 bool AreEmailsSame(const std::string& email1, const std::string& email2);
 
 // Extract the domain part from the canonical form of the given email.
+COMPONENT_EXPORT(GOOGLE_APIS)
 std::string ExtractDomainName(const std::string& email);
 
 // Returns whether the user's email is Google internal. This check is meant
 // to be used sparingly since it ship Googler-only code to all users.
+COMPONENT_EXPORT(GOOGLE_APIS)
 bool IsGoogleInternalAccountEmail(const std::string& email);
 
 // Returns true if |email| correspnds to the email of a robot account.
+COMPONENT_EXPORT(GOOGLE_APIS)
 bool IsGoogleRobotAccountEmail(const std::string& email);
 
 // Mechanically compares the scheme, host, and port of the |url| against the
 // GAIA url in GaiaUrls. This means that this function will *not* work for
 // determining whether a frame with an "about:blank" URL or "blob:..." URL has
 // a GAIA origin and will in that case return false.
-bool HasGaiaSchemeHostPort(const GURL& url);
+COMPONENT_EXPORT(GOOGLE_APIS) bool HasGaiaSchemeHostPort(const GURL& url);
 
 // Parses JSON data returned by /ListAccounts call, returning a vector of
 // email/valid pairs.  An email addresses is considered valid if a passive
@@ -70,6 +78,7 @@
 // If there an error parsing the JSON, then false is returned.
 // If either |accounts| or |signed_out_accounts| is null, the corresponding
 // accounts returned from /ListAccounts will be ignored.
+COMPONENT_EXPORT(GOOGLE_APIS)
 bool ParseListAccountsData(const std::string& data,
                            std::vector<ListedAccount>* accounts,
                            std::vector<ListedAccount>* signed_out_accounts);
@@ -79,6 +88,7 @@
 // that was shown the consent page.
 // Returns false if the method failed to decode the protobuf.
 // |approved| and |gaia_id| must not be null.
+COMPONENT_EXPORT(GOOGLE_APIS)
 bool ParseOAuth2MintTokenConsentResult(const std::string& consent_result,
                                        bool* approved,
                                        std::string* gaia_id);
diff --git a/google_apis/gaia/gaia_config.h b/google_apis/gaia/gaia_config.h
index ec4b212..5dd7a6f9 100644
--- a/google_apis/gaia/gaia_config.h
+++ b/google_apis/gaia/gaia_config.h
@@ -8,6 +8,7 @@
 #include <memory>
 #include <string>
 
+#include "base/component_export.h"
 #include "base/gtest_prod_util.h"
 #include "base/strings/string_piece_forward.h"
 #include "base/values.h"
@@ -38,7 +39,7 @@
 //     ...
 //   }
 // }
-class GaiaConfig {
+class COMPONENT_EXPORT(GOOGLE_APIS) GaiaConfig {
  public:
   // Returns a global instance of GaiaConfig.
   // This may return nullptr if the config file was not specified by a command
diff --git a/google_apis/gaia/gaia_constants.h b/google_apis/gaia/gaia_constants.h
index 5e403ac..8809748 100644
--- a/google_apis/gaia/gaia_constants.h
+++ b/google_apis/gaia/gaia_constants.h
@@ -7,68 +7,79 @@
 #ifndef GOOGLE_APIS_GAIA_GAIA_CONSTANTS_H_
 #define GOOGLE_APIS_GAIA_GAIA_CONSTANTS_H_
 
+#include "base/component_export.h"
+
 namespace GaiaConstants {
 
 // Gaia sources for accounting
-extern const char kChromeOSSource[];
-extern const char kChromeSource[];
+COMPONENT_EXPORT(GOOGLE_APIS) extern const char kChromeOSSource[];
+COMPONENT_EXPORT(GOOGLE_APIS) extern const char kChromeSource[];
 // Used as Gaia source suffix to detect retry requests because of
 // |GoogleServiceAuthError::UNEXPECTED_SERVICE_RESPONSE|.
-extern const char kUnexpectedServiceResponse[];
+COMPONENT_EXPORT(GOOGLE_APIS) extern const char kUnexpectedServiceResponse[];
 
 // OAuth2 scopes.
-extern const char kOAuth1LoginScope[];
-extern const char kDeviceManagementServiceOAuth[];
-extern const char kAnyApiOAuth2Scope[];
-extern const char kChromeSyncOAuth2Scope[];
+COMPONENT_EXPORT(GOOGLE_APIS) extern const char kOAuth1LoginScope[];
+COMPONENT_EXPORT(GOOGLE_APIS) extern const char kDeviceManagementServiceOAuth[];
+COMPONENT_EXPORT(GOOGLE_APIS) extern const char kAnyApiOAuth2Scope[];
+COMPONENT_EXPORT(GOOGLE_APIS) extern const char kChromeSyncOAuth2Scope[];
+COMPONENT_EXPORT(GOOGLE_APIS)
 extern const char kChromeSyncSupervisedOAuth2Scope[];
+COMPONENT_EXPORT(GOOGLE_APIS)
 extern const char kKidManagementPrivilegedOAuth2Scope[];
+COMPONENT_EXPORT(GOOGLE_APIS)
 extern const char kKidsSupervisionSetupChildOAuth2Scope[];
-extern const char kGoogleTalkOAuth2Scope[];
-extern const char kGoogleUserInfoEmail[];
-extern const char kGoogleUserInfoProfile[];
-extern const char kParentApprovalOAuth2Scope[];
-extern const char kPeopleApiReadOnlyOAuth2Scope[];
+COMPONENT_EXPORT(GOOGLE_APIS) extern const char kGoogleTalkOAuth2Scope[];
+COMPONENT_EXPORT(GOOGLE_APIS) extern const char kGoogleUserInfoEmail[];
+COMPONENT_EXPORT(GOOGLE_APIS) extern const char kGoogleUserInfoProfile[];
+COMPONENT_EXPORT(GOOGLE_APIS) extern const char kParentApprovalOAuth2Scope[];
+COMPONENT_EXPORT(GOOGLE_APIS) extern const char kPeopleApiReadOnlyOAuth2Scope[];
+COMPONENT_EXPORT(GOOGLE_APIS)
 extern const char kProgrammaticChallengeOAuth2Scope[];
-extern const char kAccountsReauthOAuth2Scope[];
-extern const char kAuditRecordingOAuth2Scope[];
-extern const char kClearCutOAuth2Scope[];
-extern const char kFCMOAuthScope[];
-extern const char kTachyonOAuthScope[];
-extern const char kPhotosOAuth2Scope[];
-extern const char kCastBackdropOAuth2Scope[];
-extern const char kCloudTranslationOAuth2Scope[];
+COMPONENT_EXPORT(GOOGLE_APIS) extern const char kAccountsReauthOAuth2Scope[];
+COMPONENT_EXPORT(GOOGLE_APIS) extern const char kAuditRecordingOAuth2Scope[];
+COMPONENT_EXPORT(GOOGLE_APIS) extern const char kClearCutOAuth2Scope[];
+COMPONENT_EXPORT(GOOGLE_APIS) extern const char kFCMOAuthScope[];
+COMPONENT_EXPORT(GOOGLE_APIS) extern const char kTachyonOAuthScope[];
+COMPONENT_EXPORT(GOOGLE_APIS) extern const char kPhotosOAuth2Scope[];
+COMPONENT_EXPORT(GOOGLE_APIS) extern const char kCastBackdropOAuth2Scope[];
+COMPONENT_EXPORT(GOOGLE_APIS) extern const char kCloudTranslationOAuth2Scope[];
+COMPONENT_EXPORT(GOOGLE_APIS)
 extern const char kPasswordsLeakCheckOAuth2Scope[];
+COMPONENT_EXPORT(GOOGLE_APIS)
 extern const char kChromeSafeBrowsingOAuth2Scope[];
+COMPONENT_EXPORT(GOOGLE_APIS)
 extern const char kClassifyUrlKidPermissionOAuth2Scope[];
-extern const char kKidFamilyReadonlyOAuth2Scope[];
-extern const char kPaymentsOAuth2Scope[];
-extern const char kCryptAuthOAuth2Scope[];
-extern const char kDriveOAuth2Scope[];
-extern const char kDriveReadOnlyOAuth2Scope[];
-extern const char kAssistantOAuth2Scope[];
+COMPONENT_EXPORT(GOOGLE_APIS) extern const char kKidFamilyReadonlyOAuth2Scope[];
+COMPONENT_EXPORT(GOOGLE_APIS) extern const char kPaymentsOAuth2Scope[];
+COMPONENT_EXPORT(GOOGLE_APIS) extern const char kCryptAuthOAuth2Scope[];
+COMPONENT_EXPORT(GOOGLE_APIS) extern const char kDriveOAuth2Scope[];
+COMPONENT_EXPORT(GOOGLE_APIS) extern const char kDriveReadOnlyOAuth2Scope[];
+COMPONENT_EXPORT(GOOGLE_APIS) extern const char kAssistantOAuth2Scope[];
+COMPONENT_EXPORT(GOOGLE_APIS)
 extern const char kCloudPlatformProjectsOAuth2Scope[];
-extern const char kNearbyShareOAuth2Scope[];
-extern const char kGCMGroupServerOAuth2Scope[];
-extern const char kGCMCheckinServerOAuth2Scope[];
-extern const char kChromeWebstoreOAuth2Scope[];
+COMPONENT_EXPORT(GOOGLE_APIS) extern const char kNearbyShareOAuth2Scope[];
+COMPONENT_EXPORT(GOOGLE_APIS) extern const char kGCMGroupServerOAuth2Scope[];
+COMPONENT_EXPORT(GOOGLE_APIS) extern const char kGCMCheckinServerOAuth2Scope[];
+COMPONENT_EXPORT(GOOGLE_APIS) extern const char kChromeWebstoreOAuth2Scope[];
+COMPONENT_EXPORT(GOOGLE_APIS)
 extern const char kAccountCapabilitiesOAuth2Scope[];
-extern const char kSupportContentOAuth2Scope[];
-extern const char kPhotosModuleOAuth2Scope[];
-extern const char kPhotosModuleImageOAuth2Scope[];
-extern const char kSecureConnectOAuth2Scope[];
-extern const char kFeedOAuth2Scope[];
-extern const char kKAnonymityServiceOAuth2Scope[];
-extern const char kCalendarReadOnlyOAuth2Scope[];
+COMPONENT_EXPORT(GOOGLE_APIS) extern const char kSupportContentOAuth2Scope[];
+COMPONENT_EXPORT(GOOGLE_APIS) extern const char kPhotosModuleOAuth2Scope[];
+COMPONENT_EXPORT(GOOGLE_APIS) extern const char kPhotosModuleImageOAuth2Scope[];
+COMPONENT_EXPORT(GOOGLE_APIS) extern const char kSecureConnectOAuth2Scope[];
+COMPONENT_EXPORT(GOOGLE_APIS) extern const char kFeedOAuth2Scope[];
+COMPONENT_EXPORT(GOOGLE_APIS) extern const char kKAnonymityServiceOAuth2Scope[];
+COMPONENT_EXPORT(GOOGLE_APIS) extern const char kCalendarReadOnlyOAuth2Scope[];
 
 // Used by wallet sign in helper.
-extern const char kClientOAuthEmailKey[];
+COMPONENT_EXPORT(GOOGLE_APIS) extern const char kClientOAuthEmailKey[];
 
 // Refresh token that is guaranteed to be invalid.
-extern const char kInvalidRefreshToken[];
+COMPONENT_EXPORT(GOOGLE_APIS) extern const char kInvalidRefreshToken[];
 
 // Name of the Google authentication cookie.
-extern const char kGaiaSigninCookieName[];
+COMPONENT_EXPORT(GOOGLE_APIS) extern const char kGaiaSigninCookieName[];
 }  // namespace GaiaConstants
 
 #endif  // GOOGLE_APIS_GAIA_GAIA_CONSTANTS_H_
diff --git a/google_apis/gaia/gaia_oauth_client.cc b/google_apis/gaia/gaia_oauth_client.cc
index 502ba94..4f64f84 100644
--- a/google_apis/gaia/gaia_oauth_client.cc
+++ b/google_apis/gaia/gaia_oauth_client.cc
@@ -19,6 +19,7 @@
 #include "base/strings/string_util.h"
 #include "base/task/single_thread_task_runner.h"
 #include "base/values.h"
+#include "google_apis/credentials_mode.h"
 #include "google_apis/gaia/gaia_auth_util.h"
 #include "google_apis/gaia/gaia_urls.h"
 #include "net/base/backoff_entry.h"
@@ -460,7 +461,8 @@
   auto resource_request = std::make_unique<network::ResourceRequest>();
   resource_request->url = url_;
   resource_request->method = post_body_.empty() ? "GET" : "POST";
-  resource_request->credentials_mode = network::mojom::CredentialsMode::kOmit;
+  resource_request->credentials_mode =
+      google_apis::GetOmitCredentialsModeForGaiaRequests();
   if (!authorization_header_.empty())
     resource_request->headers.SetHeader("Authorization", authorization_header_);
   if (!http_method_override_header_.empty()) {
diff --git a/google_apis/gaia/gaia_oauth_client.h b/google_apis/gaia/gaia_oauth_client.h
index a15211b..1b3176a 100644
--- a/google_apis/gaia/gaia_oauth_client.h
+++ b/google_apis/gaia/gaia_oauth_client.h
@@ -9,6 +9,7 @@
 #include <string>
 #include <vector>
 
+#include "base/component_export.h"
 #include "base/memory/ref_counted.h"
 #include "base/values.h"
 
@@ -23,15 +24,15 @@
 // instances.
 namespace gaia {
 
-struct OAuthClientInfo {
+struct COMPONENT_EXPORT(GOOGLE_APIS) OAuthClientInfo {
   std::string client_id;
   std::string client_secret;
   std::string redirect_uri;
 };
 
-class GaiaOAuthClient {
+class COMPONENT_EXPORT(GOOGLE_APIS) GaiaOAuthClient {
  public:
-  class Delegate {
+  class COMPONENT_EXPORT(GOOGLE_APIS) Delegate {
    public:
     // Invoked on a successful response to the GetTokensFromAuthCode request.
     virtual void OnGetTokensResponse(const std::string& refresh_token,
@@ -154,6 +155,7 @@
   class Core;
   scoped_refptr<Core> core_;
 };
-}
+
+}  // namespace gaia
 
 #endif  // GOOGLE_APIS_GAIA_GAIA_OAUTH_CLIENT_H_
diff --git a/google_apis/gaia/gaia_switches.h b/google_apis/gaia/gaia_switches.h
index ca8b737..7ac4bcd 100644
--- a/google_apis/gaia/gaia_switches.h
+++ b/google_apis/gaia/gaia_switches.h
@@ -5,42 +5,44 @@
 #ifndef GOOGLE_APIS_GAIA_GAIA_SWITCHES_H_
 #define GOOGLE_APIS_GAIA_GAIA_SWITCHES_H_
 
+#include "base/component_export.h"
+
 namespace switches {
 
 // Specifies the path to a config file containing GAIA urls.
 // See "google_apis/test/data/gaia/all_urls.json" for a format example.
-extern const char kGaiaConfigPath[];
+COMPONENT_EXPORT(GOOGLE_APIS) extern const char kGaiaConfigPath[];
 
 // Specifies a string containing the JSON config for GAIA urls. This is
 // equivalent to pointing to a file with the same content via kGaiaConfigPath.
 // See "google_apis/test/data/gaia/all_urls.json" for a format example.
-extern const char kGaiaConfigContents[];
+COMPONENT_EXPORT(GOOGLE_APIS) extern const char kGaiaConfigContents[];
 
 // Specifies the domain of the SAPISID cookie. The default value is
 // "http://.google.com".
-extern const char kGoogleUrl[];
+COMPONENT_EXPORT(GOOGLE_APIS) extern const char kGoogleUrl[];
 
 // Specifies the path for GAIA authentication URL. The default value is
 // "https://accounts.google.com".
-extern const char kGaiaUrl[];
+COMPONENT_EXPORT(GOOGLE_APIS) extern const char kGaiaUrl[];
 
 // Specifies the backend server used for Google API calls.
 // "https://www.googleapis.com".
-extern const char kGoogleApisUrl[];
+COMPONENT_EXPORT(GOOGLE_APIS) extern const char kGoogleApisUrl[];
 
 // Specifies the backend server used for lso authentication calls.
 // "https://accounts.google.com".
-extern const char kLsoUrl[];
+COMPONENT_EXPORT(GOOGLE_APIS) extern const char kLsoUrl[];
 
 // Specifies the backend server used for OAuth issue token calls.
 // "https://oauthaccountmanager.googleapis.com".
-extern const char kOAuthAccountManagerUrl[];
+COMPONENT_EXPORT(GOOGLE_APIS) extern const char kOAuthAccountManagerUrl[];
 
 // Specifies custom OAuth2 client id for testing purposes.
-extern const char kOAuth2ClientID[];
+COMPONENT_EXPORT(GOOGLE_APIS) extern const char kOAuth2ClientID[];
 
 // Specifies custom OAuth2 client secret for testing purposes.
-extern const char kOAuth2ClientSecret[];
+COMPONENT_EXPORT(GOOGLE_APIS) extern const char kOAuth2ClientSecret[];
 
 }  // namespace switches
 
diff --git a/google_apis/gaia/gaia_urls.h b/google_apis/gaia/gaia_urls.h
index f720aad..a608c6d 100644
--- a/google_apis/gaia/gaia_urls.h
+++ b/google_apis/gaia/gaia_urls.h
@@ -7,6 +7,7 @@
 
 #include <string>
 
+#include "base/component_export.h"
 #include "base/memory/singleton.h"
 #include "url/gurl.h"
 #include "url/origin.h"
@@ -14,7 +15,7 @@
 // A singleton that provides all the URLs that are used for connecting to GAIA.
 //
 // Please update InitializeFromConfig() when adding new URLs.
-class GaiaUrls {
+class COMPONENT_EXPORT(GOOGLE_APIS) GaiaUrls {
  public:
   static GaiaUrls* GetInstance();
 
diff --git a/google_apis/gaia/google_service_auth_error.h b/google_apis/gaia/google_service_auth_error.h
index 5f913ef..27dba23 100644
--- a/google_apis/gaia/google_service_auth_error.h
+++ b/google_apis/gaia/google_service_auth_error.h
@@ -13,9 +13,10 @@
 
 #include <string>
 
+#include "base/component_export.h"
 #include "url/gurl.h"
 
-class GoogleServiceAuthError {
+class COMPONENT_EXPORT(GOOGLE_APIS) GoogleServiceAuthError {
  public:
   //
   // These enumerations are referenced by integer value in HTML login code and
diff --git a/google_apis/gaia/oauth2_access_token_consumer.h b/google_apis/gaia/oauth2_access_token_consumer.h
index 3dad545..b7bce19 100644
--- a/google_apis/gaia/oauth2_access_token_consumer.h
+++ b/google_apis/gaia/oauth2_access_token_consumer.h
@@ -7,16 +7,17 @@
 
 #include <string>
 
+#include "base/component_export.h"
 #include "base/time/time.h"
 
 class GoogleServiceAuthError;
 
 // An interface that defines the callbacks for consumers to which
 // OAuth2AccessTokenFetcher can return results.
-class OAuth2AccessTokenConsumer {
+class COMPONENT_EXPORT(GOOGLE_APIS) OAuth2AccessTokenConsumer {
  public:
   // Structure representing information contained in OAuth2 access token.
-  struct TokenResponse {
+  struct COMPONENT_EXPORT(GOOGLE_APIS) TokenResponse {
     TokenResponse();
     TokenResponse(const TokenResponse& response);
     TokenResponse(TokenResponse&& response);
@@ -39,7 +40,7 @@
     std::string id_token;
 
     // Helper class to make building TokenResponse objects clearer.
-    class Builder {
+    class COMPONENT_EXPORT(GOOGLE_APIS) Builder {
      public:
       Builder();
       ~Builder();
diff --git a/google_apis/gaia/oauth2_access_token_fetcher.h b/google_apis/gaia/oauth2_access_token_fetcher.h
index 585611d..29db3b4 100644
--- a/google_apis/gaia/oauth2_access_token_fetcher.h
+++ b/google_apis/gaia/oauth2_access_token_fetcher.h
@@ -8,6 +8,7 @@
 #include <string>
 #include <vector>
 
+#include "base/component_export.h"
 #include "base/memory/raw_ptr.h"
 #include "google_apis/gaia/oauth2_access_token_consumer.h"
 
@@ -21,7 +22,7 @@
 //
 // This class can handle one request at a time. To parallelize requests,
 // create multiple instances.
-class OAuth2AccessTokenFetcher {
+class COMPONENT_EXPORT(GOOGLE_APIS) OAuth2AccessTokenFetcher {
  public:
   explicit OAuth2AccessTokenFetcher(OAuth2AccessTokenConsumer* consumer);
 
diff --git a/google_apis/gaia/oauth2_access_token_fetcher_immediate_error.h b/google_apis/gaia/oauth2_access_token_fetcher_immediate_error.h
index 1dba817..ddb17a9 100644
--- a/google_apis/gaia/oauth2_access_token_fetcher_immediate_error.h
+++ b/google_apis/gaia/oauth2_access_token_fetcher_immediate_error.h
@@ -5,6 +5,7 @@
 #ifndef GOOGLE_APIS_GAIA_OAUTH2_ACCESS_TOKEN_FETCHER_IMMEDIATE_ERROR_H_
 #define GOOGLE_APIS_GAIA_OAUTH2_ACCESS_TOKEN_FETCHER_IMMEDIATE_ERROR_H_
 
+#include "base/component_export.h"
 #include "base/memory/raw_ptr.h"
 #include "base/memory/ref_counted.h"
 #include "google_apis/gaia/google_service_auth_error.h"
@@ -28,7 +29,8 @@
 //
 // This class can handle one request at a time. To parallelize requests,
 // create multiple instances.
-class OAuth2AccessTokenFetcherImmediateError : public OAuth2AccessTokenFetcher {
+class COMPONENT_EXPORT(GOOGLE_APIS) OAuth2AccessTokenFetcherImmediateError
+    : public OAuth2AccessTokenFetcher {
  public:
   OAuth2AccessTokenFetcherImmediateError(OAuth2AccessTokenConsumer* consumer,
                                          const GoogleServiceAuthError& error);
@@ -47,7 +49,8 @@
   void CancelRequest() override;
 
  private:
-  class FailCaller : public base::RefCounted<FailCaller> {
+  class COMPONENT_EXPORT(GOOGLE_APIS) FailCaller
+      : public base::RefCounted<FailCaller> {
    public:
     FailCaller(OAuth2AccessTokenFetcherImmediateError* fetcher);
 
diff --git a/google_apis/gaia/oauth2_access_token_fetcher_impl.cc b/google_apis/gaia/oauth2_access_token_fetcher_impl.cc
index 4a3c42a..c3fcb30 100644
--- a/google_apis/gaia/oauth2_access_token_fetcher_impl.cc
+++ b/google_apis/gaia/oauth2_access_token_fetcher_impl.cc
@@ -16,6 +16,7 @@
 #include "base/strings/stringprintf.h"
 #include "base/time/time.h"
 #include "base/values.h"
+#include "google_apis/credentials_mode.h"
 #include "google_apis/gaia/gaia_auth_util.h"
 #include "google_apis/gaia/google_service_auth_error.h"
 #include "net/http/http_status_code.h"
@@ -95,7 +96,8 @@
     const net::NetworkTrafficAnnotationTag& traffic_annotation) {
   auto resource_request = std::make_unique<network::ResourceRequest>();
   resource_request->url = url;
-  resource_request->credentials_mode = network::mojom::CredentialsMode::kOmit;
+  resource_request->credentials_mode =
+      google_apis::GetOmitCredentialsModeForGaiaRequests();
   if (!body.empty())
     resource_request->method = "POST";
 
diff --git a/google_apis/gaia/oauth2_access_token_fetcher_impl.h b/google_apis/gaia/oauth2_access_token_fetcher_impl.h
index 8570a3a..1372527 100644
--- a/google_apis/gaia/oauth2_access_token_fetcher_impl.h
+++ b/google_apis/gaia/oauth2_access_token_fetcher_impl.h
@@ -9,6 +9,7 @@
 #include <string>
 #include <vector>
 
+#include "base/component_export.h"
 #include "base/gtest_prod_util.h"
 #include "base/memory/ref_counted.h"
 #include "google_apis/gaia/oauth2_access_token_consumer.h"
@@ -39,7 +40,8 @@
 // This class can handle one request at a time. To parallelize requests,
 // create multiple instances.  Prefer `GaiaAccessTokenFetcher` over this class
 // while talking to Google's OAuth servers.
-class OAuth2AccessTokenFetcherImpl : public OAuth2AccessTokenFetcher {
+class COMPONENT_EXPORT(GOOGLE_APIS) OAuth2AccessTokenFetcherImpl
+    : public OAuth2AccessTokenFetcher {
  public:
   // Enumerated constants of server responses, matching RFC 6749.
   // These values are persisted to logs. Entries should not be renumbered and
diff --git a/google_apis/gaia/oauth2_access_token_manager.h b/google_apis/gaia/oauth2_access_token_manager.h
index ba1b0793..f787f48 100644
--- a/google_apis/gaia/oauth2_access_token_manager.h
+++ b/google_apis/gaia/oauth2_access_token_manager.h
@@ -8,6 +8,7 @@
 #include <map>
 #include <set>
 
+#include "base/component_export.h"
 #include "base/gtest_prod_util.h"
 #include "base/memory/raw_ptr.h"
 #include "base/observer_list.h"
@@ -24,13 +25,13 @@
 class OAuth2AccessTokenFetcher;
 
 // Class that manages requests for OAuth2 access tokens.
-class OAuth2AccessTokenManager {
+class COMPONENT_EXPORT(GOOGLE_APIS) OAuth2AccessTokenManager {
  public:
   // A set of scopes in OAuth2 authentication.
   typedef std::set<std::string> ScopeSet;
   class RequestImpl;
 
-  class Delegate {
+  class COMPONENT_EXPORT(GOOGLE_APIS) Delegate {
    public:
     Delegate();
     virtual ~Delegate();
@@ -79,7 +80,7 @@
   };
 
   // Class representing a request that fetches an OAuth2 access token.
-  class Request {
+  class COMPONENT_EXPORT(GOOGLE_APIS) Request {
    public:
     virtual ~Request();
     virtual CoreAccountId GetAccountId() const = 0;
@@ -90,7 +91,7 @@
 
   // Class representing the consumer of a Request passed to |StartRequest|,
   // which will be called back when the request completes.
-  class Consumer {
+  class COMPONENT_EXPORT(GOOGLE_APIS) Consumer {
    public:
     explicit Consumer(const std::string& id);
     virtual ~Consumer();
@@ -112,8 +113,9 @@
   // Implements a cancelable |OAuth2AccessTokenManager::Request|, which should
   // be operated on the UI thread.
   // TODO(davidroche): move this out of header file.
-  class RequestImpl : public base::SupportsWeakPtr<RequestImpl>,
-                      public Request {
+  class COMPONENT_EXPORT(GOOGLE_APIS) RequestImpl
+      : public base::SupportsWeakPtr<RequestImpl>,
+        public Request {
    public:
     // |consumer| is required to outlive this.
     RequestImpl(const CoreAccountId& account_id, Consumer* consumer);
@@ -140,7 +142,7 @@
   // Classes that want to monitor status of access token and access token
   // request should implement this interface and register with the
   // AddDiagnosticsObserver() call.
-  class DiagnosticsObserver {
+  class COMPONENT_EXPORT(GOOGLE_APIS) DiagnosticsObserver {
    public:
     // Called when receiving request for access token.
     virtual void OnAccessTokenRequested(const CoreAccountId& account_id,
@@ -162,7 +164,7 @@
   };
 
   // The parameters used to fetch an OAuth2 access token.
-  struct RequestParameters {
+  struct COMPONENT_EXPORT(GOOGLE_APIS) RequestParameters {
     RequestParameters(const std::string& client_id,
                       const CoreAccountId& account_id,
                       const ScopeSet& scopes);
diff --git a/google_apis/gaia/oauth2_api_call_flow.cc b/google_apis/gaia/oauth2_api_call_flow.cc
index 37a1b34..b9f28b94 100644
--- a/google_apis/gaia/oauth2_api_call_flow.cc
+++ b/google_apis/gaia/oauth2_api_call_flow.cc
@@ -10,6 +10,7 @@
 #include "base/functional/bind.h"
 #include "base/strings/escape.h"
 #include "base/strings/stringprintf.h"
+#include "google_apis/credentials_mode.h"
 #include "google_apis/gaia/gaia_auth_util.h"
 #include "google_apis/gaia/gaia_urls.h"
 #include "net/base/load_flags.h"
@@ -98,7 +99,8 @@
   auto request = std::make_unique<network::ResourceRequest>();
   request->url = CreateApiCallUrl();
   request->method = request_type;
-  request->credentials_mode = network::mojom::CredentialsMode::kOmit;
+  request->credentials_mode =
+      google_apis::GetOmitCredentialsModeForGaiaRequests();
   request->headers = CreateApiCallHeaders();
   request->headers.SetHeader("Authorization",
                              MakeAuthorizationValue(access_token));
diff --git a/google_apis/gaia/oauth2_api_call_flow.h b/google_apis/gaia/oauth2_api_call_flow.h
index c194eeb..9e8b075a 100644
--- a/google_apis/gaia/oauth2_api_call_flow.h
+++ b/google_apis/gaia/oauth2_api_call_flow.h
@@ -8,6 +8,7 @@
 #include <memory>
 #include <string>
 
+#include "base/component_export.h"
 #include "base/memory/scoped_refptr.h"
 #include "net/traffic_annotation/network_traffic_annotation.h"
 #include "services/network/public/mojom/url_response_head.mojom-forward.h"
@@ -26,7 +27,7 @@
 // given an access token to the service.  This class abstracts the basic steps
 // and exposes template methods for sub-classes to implement for API specific
 // details.
-class OAuth2ApiCallFlow {
+class COMPONENT_EXPORT(GOOGLE_APIS) OAuth2ApiCallFlow {
  public:
   OAuth2ApiCallFlow();
 
diff --git a/google_apis/gaia/oauth2_id_token_decoder.h b/google_apis/gaia/oauth2_id_token_decoder.h
index d7bdfa4..64a9c6b 100644
--- a/google_apis/gaia/oauth2_id_token_decoder.h
+++ b/google_apis/gaia/oauth2_id_token_decoder.h
@@ -8,6 +8,8 @@
 #include <string>
 #include <vector>
 
+#include "base/component_export.h"
+
 // This file holds methods decodes the id token received for OAuth2 token
 // endpoint, and derive useful information from it, such as whether the account
 // is a child account, and whether this account is under advanced protection.
@@ -15,12 +17,13 @@
 namespace gaia {
 
 // Service flags extracted from ID token.
-struct TokenServiceFlags {
+struct COMPONENT_EXPORT(GOOGLE_APIS) TokenServiceFlags {
   bool is_child_account = false;
   bool is_under_advanced_protection = false;
 };
 
 // Parses service flag from ID token.
+COMPONENT_EXPORT(GOOGLE_APIS)
 TokenServiceFlags ParseServiceFlags(const std::string& id_token);
 
 }  // namespace gaia
diff --git a/google_apis/gaia/oauth2_mint_token_flow.h b/google_apis/gaia/oauth2_mint_token_flow.h
index fe633950..ef861fe 100644
--- a/google_apis/gaia/oauth2_mint_token_flow.h
+++ b/google_apis/gaia/oauth2_mint_token_flow.h
@@ -9,6 +9,7 @@
 #include <string>
 #include <vector>
 
+#include "base/component_export.h"
 #include "base/gtest_prod_util.h"
 #include "base/memory/raw_ptr.h"
 #include "base/memory/weak_ptr.h"
@@ -21,6 +22,7 @@
 class GoogleServiceAuthError;
 class OAuth2MintTokenFlowTest;
 
+COMPONENT_EXPORT(GOOGLE_APIS)
 extern const char kOAuth2MintTokenApiCallResultHistogram[];
 
 // Values carrying the result of processing a successful API call.
@@ -48,7 +50,7 @@
 // Data for the remote consent resolution:
 // - URL of the consent page to be displayed to the user.
 // - Cookies that should be set before navigating to that URL.
-struct RemoteConsentResolutionData {
+struct COMPONENT_EXPORT(GOOGLE_APIS) RemoteConsentResolutionData {
   RemoteConsentResolutionData();
   ~RemoteConsentResolutionData();
 
@@ -65,7 +67,8 @@
 // This class implements the OAuth2 flow to Google to mint an OAuth2 access
 // token for the given client and the given set of scopes from the OAuthLogin
 // scoped "master" OAuth2 token for the user logged in to Chrome.
-class OAuth2MintTokenFlow : public OAuth2ApiCallFlow {
+class COMPONENT_EXPORT(GOOGLE_APIS) OAuth2MintTokenFlow
+    : public OAuth2ApiCallFlow {
  public:
   // There are four different modes when minting a token to grant
   // access to third-party app for a user.
@@ -81,7 +84,7 @@
   };
 
   // Parameters needed to mint a token.
-  struct Parameters {
+  struct COMPONENT_EXPORT(GOOGLE_APIS) Parameters {
    public:
     Parameters();
     Parameters(const std::string& eid,
@@ -109,7 +112,7 @@
     Mode mode;
   };
 
-  class Delegate {
+  class COMPONENT_EXPORT(GOOGLE_APIS) Delegate {
    public:
     virtual void OnMintTokenSuccess(const std::string& access_token,
                                     const std::set<std::string>& granted_scopes,
diff --git a/google_apis/gaia/oauth_multilogin_result.h b/google_apis/gaia/oauth_multilogin_result.h
index bd1e024..0e219a3 100644
--- a/google_apis/gaia/oauth_multilogin_result.h
+++ b/google_apis/gaia/oauth_multilogin_result.h
@@ -7,6 +7,7 @@
 
 #include <string>
 
+#include "base/component_export.h"
 #include "base/gtest_prod_util.h"
 #include "base/values.h"
 #include "google_apis/gaia/gaia_auth_util.h"
@@ -49,10 +50,11 @@
 };
 
 // Parses the status field of the response.
+COMPONENT_EXPORT(GOOGLE_APIS)
 OAuthMultiloginResponseStatus ParseOAuthMultiloginResponseStatus(
     const std::string& status);
 
-class OAuthMultiloginResult {
+class COMPONENT_EXPORT(GOOGLE_APIS) OAuthMultiloginResult {
  public:
   // Parses cookies and status from JSON response. Maps status to
   // GoogleServiceAuthError::State values or sets error to
diff --git a/google_apis/gaia/oauth_request_signer.h b/google_apis/gaia/oauth_request_signer.h
index 06789733..5926cc8 100644
--- a/google_apis/gaia/oauth_request_signer.h
+++ b/google_apis/gaia/oauth_request_signer.h
@@ -8,13 +8,15 @@
 #include <map>
 #include <string>
 
+#include "base/component_export.h"
+
 class GURL;
 
 // Implements the OAuth request signing process as described here:
 //   http://oauth.net/core/1.0/#signing_process
 //
 // NOTE: Currently the only supported SignatureMethod is HMAC_SHA1_SIGNATURE
-class OAuthRequestSigner {
+class COMPONENT_EXPORT(GOOGLE_APIS) OAuthRequestSigner {
  public:
   enum SignatureMethod {
     HMAC_SHA1_SIGNATURE,
diff --git a/google_apis/gcm/engine/checkin_request.cc b/google_apis/gcm/engine/checkin_request.cc
index 544bdca..ff40cfa 100644
--- a/google_apis/gcm/engine/checkin_request.cc
+++ b/google_apis/gcm/engine/checkin_request.cc
@@ -9,6 +9,7 @@
 #include "base/metrics/histogram_functions.h"
 #include "base/task/sequenced_task_runner.h"
 #include "build/chromeos_buildflags.h"
+#include "google_apis/credentials_mode.h"
 #include "google_apis/gcm/monitoring/gcm_stats_recorder.h"
 #include "google_apis/gcm/protocol/checkin.pb.h"
 #include "net/base/load_flags.h"
@@ -193,7 +194,8 @@
   auto resource_request = std::make_unique<network::ResourceRequest>();
   resource_request->url = checkin_url_;
   resource_request->method = "POST";
-  resource_request->credentials_mode = network::mojom::CredentialsMode::kOmit;
+  resource_request->credentials_mode =
+      google_apis::GetOmitCredentialsModeForGaiaRequests();
 
   DVLOG(1) << "Performing check-in request with android id: "
            << request_info_.android_id
diff --git a/google_apis/gcm/engine/registration_request.cc b/google_apis/gcm/engine/registration_request.cc
index 50d754d9..36adf30 100644
--- a/google_apis/gcm/engine/registration_request.cc
+++ b/google_apis/gcm/engine/registration_request.cc
@@ -14,6 +14,7 @@
 #include "base/strings/string_number_conversions.h"
 #include "base/task/sequenced_task_runner.h"
 #include "base/values.h"
+#include "google_apis/credentials_mode.h"
 #include "google_apis/gcm/base/gcm_util.h"
 #include "google_apis/gcm/monitoring/gcm_stats_recorder.h"
 #include "net/base/load_flags.h"
@@ -176,7 +177,8 @@
   auto request = std::make_unique<network::ResourceRequest>();
   request->url = registration_url_;
   request->method = "POST";
-  request->credentials_mode = network::mojom::CredentialsMode::kOmit;
+  request->credentials_mode =
+      google_apis::GetOmitCredentialsModeForGaiaRequests();
   BuildRequestHeaders(&request->headers);
 
   std::string body;
diff --git a/google_apis/gcm/engine/registration_request_unittest.cc b/google_apis/gcm/engine/registration_request_unittest.cc
index 7dc1cb8..ec5ce5b 100644
--- a/google_apis/gcm/engine/registration_request_unittest.cc
+++ b/google_apis/gcm/engine/registration_request_unittest.cc
@@ -15,6 +15,7 @@
 #include "base/strings/string_number_conversions.h"
 #include "base/strings/string_tokenizer.h"
 #include "base/task/single_thread_task_runner.h"
+#include "google_apis/credentials_mode.h"
 #include "google_apis/gcm/engine/gcm_registration_request_handler.h"
 #include "google_apis/gcm/engine/gcm_request_test_base.h"
 #include "google_apis/gcm/engine/instance_id_get_token_request_handler.h"
@@ -459,7 +460,7 @@
   const network::ResourceRequest* pending_request;
   ASSERT_TRUE(
       test_url_loader_factory()->IsPending(kRegistrationURL, &pending_request));
-  EXPECT_EQ(network::mojom::CredentialsMode::kOmit,
+  EXPECT_EQ(google_apis::GetOmitCredentialsModeForGaiaRequests(),
             pending_request->credentials_mode);
 
   // Verify that authorization header was put together properly.
diff --git a/google_apis/gcm/engine/unregistration_request.cc b/google_apis/gcm/engine/unregistration_request.cc
index 3782675..195f5a97 100644
--- a/google_apis/gcm/engine/unregistration_request.cc
+++ b/google_apis/gcm/engine/unregistration_request.cc
@@ -13,6 +13,7 @@
 #include "base/strings/string_piece.h"
 #include "base/task/sequenced_task_runner.h"
 #include "base/values.h"
+#include "google_apis/credentials_mode.h"
 #include "google_apis/gcm/base/gcm_util.h"
 #include "google_apis/gcm/monitoring/gcm_stats_recorder.h"
 #include "net/base/load_flags.h"
@@ -163,7 +164,8 @@
   auto request = std::make_unique<network::ResourceRequest>();
   request->url = registration_url_;
   request->method = "POST";
-  request->credentials_mode = network::mojom::CredentialsMode::kOmit;
+  request->credentials_mode =
+      google_apis::GetOmitCredentialsModeForGaiaRequests();
   BuildRequestHeaders(&request->headers);
 
   std::string body;
diff --git a/google_apis/gcm/engine/unregistration_request_unittest.cc b/google_apis/gcm/engine/unregistration_request_unittest.cc
index 760ea34..d4637128 100644
--- a/google_apis/gcm/engine/unregistration_request_unittest.cc
+++ b/google_apis/gcm/engine/unregistration_request_unittest.cc
@@ -14,6 +14,7 @@
 #include "base/strings/string_number_conversions.h"
 #include "base/strings/string_tokenizer.h"
 #include "base/task/single_thread_task_runner.h"
+#include "google_apis/credentials_mode.h"
 #include "google_apis/gcm/engine/gcm_request_test_base.h"
 #include "google_apis/gcm/engine/gcm_unregistration_request_handler.h"
 #include "google_apis/gcm/engine/instance_id_delete_token_request_handler.h"
@@ -118,7 +119,7 @@
   const network::ResourceRequest* pending_request;
   ASSERT_TRUE(
       test_url_loader_factory()->IsPending(kRegistrationURL, &pending_request));
-  EXPECT_EQ(network::mojom::CredentialsMode::kOmit,
+  EXPECT_EQ(google_apis::GetOmitCredentialsModeForGaiaRequests(),
             pending_request->credentials_mode);
 
   // Verify that authorization header was put together properly.
diff --git a/google_apis/google_api_keys.h b/google_apis/google_api_keys.h
index 4275578d..ba958fc 100644
--- a/google_apis/google_api_keys.h
+++ b/google_apis/google_api_keys.h
@@ -9,6 +9,7 @@
 // google_api_keys_unittest.cc.
 #include <string>
 
+#include "base/component_export.h"
 #include "build/build_config.h"
 #include "build/chromeos_buildflags.h"
 #include "google_apis/buildflags.h"
@@ -61,47 +62,47 @@
 
 namespace google_apis {
 
-extern const char kAPIKeysDevelopersHowToURL[];
+COMPONENT_EXPORT(GOOGLE_APIS) extern const char kAPIKeysDevelopersHowToURL[];
 
 // Returns true if no dummy API key is set.
-bool HasAPIKeyConfigured();
+COMPONENT_EXPORT(GOOGLE_APIS) bool HasAPIKeyConfigured();
 
 // Retrieves the API key, a.k.a. developer key, or a dummy string
 // if not set.
 //
 // Note that the key should be escaped for the context you use it in,
 // e.g. URL-escaped if you use it in a URL.
-std::string GetAPIKey();
+COMPONENT_EXPORT(GOOGLE_APIS) std::string GetAPIKey();
 
 // Non-stable channels may have a different Google API key.
-std::string GetNonStableAPIKey();
+COMPONENT_EXPORT(GOOGLE_APIS) std::string GetNonStableAPIKey();
 
 // Retrieves the Chrome Remote Desktop API key.
-std::string GetRemotingAPIKey();
+COMPONENT_EXPORT(GOOGLE_APIS) std::string GetRemotingAPIKey();
 
 // Retrieves the Speech On-Device API (SODA) API Key.
-std::string GetSodaAPIKey();
+COMPONENT_EXPORT(GOOGLE_APIS) std::string GetSodaAPIKey();
 
 #if BUILDFLAG(IS_CHROMEOS_ASH)
 // Retrieves the Sharing API Key.
-std::string GetSharingAPIKey();
+COMPONENT_EXPORT(GOOGLE_APIS) std::string GetSharingAPIKey();
 
 // Retrieves the ReadAloud API Key.
-std::string GetReadAloudAPIKey();
+COMPONENT_EXPORT(GOOGLE_APIS) std::string GetReadAloudAPIKey();
 
 // Retrieves the Fresnel API Key.
-std::string GetFresnelAPIKey();
+COMPONENT_EXPORT(GOOGLE_APIS) std::string GetFresnelAPIKey();
 #endif
 
 #if BUILDFLAG(SUPPORT_EXTERNAL_GOOGLE_API_KEY)
 // Sets the API key. This should be called as early as possible before this
 // API key is even accessed. It must be called before GetAPIKey.
 // TODO(https://crbug.com/1166007): Enforce this is called before GetAPIKey.
-void SetAPIKey(const std::string& api_key);
+COMPONENT_EXPORT(GOOGLE_APIS) void SetAPIKey(const std::string& api_key);
 #endif
 
 // Retrieves the key used to sign metrics (UMA/UKM) uploads.
-std::string GetMetricsKey();
+COMPONENT_EXPORT(GOOGLE_APIS) std::string GetMetricsKey();
 
 // Represents the different sets of client IDs and secrets in use.
 enum OAuth2Client {
@@ -113,13 +114,14 @@
 };
 
 // Returns true if no dummy OAuth2 client ID and secret are set.
-bool HasOAuthClientConfigured();
+COMPONENT_EXPORT(GOOGLE_APIS) bool HasOAuthClientConfigured();
 
 // Retrieves the OAuth2 client ID for the specified client, or the
 // empty string if not set.
 //
 // Note that the ID should be escaped for the context you use it in,
 // e.g. URL-escaped if you use it in a URL.
+COMPONENT_EXPORT(GOOGLE_APIS)
 std::string GetOAuth2ClientID(OAuth2Client client);
 
 // Retrieves the OAuth2 client secret for the specified client, or the
@@ -127,22 +129,25 @@
 //
 // Note that the secret should be escaped for the context you use it
 // in, e.g. URL-escaped if you use it in a URL.
+COMPONENT_EXPORT(GOOGLE_APIS)
 std::string GetOAuth2ClientSecret(OAuth2Client client);
 
 #if BUILDFLAG(IS_IOS)
 // Sets the client id for the specified client. Should be called as early as
 // possible before these ids are accessed.
+COMPONENT_EXPORT(GOOGLE_APIS)
 void SetOAuth2ClientID(OAuth2Client client, const std::string& client_id);
 
 // Sets the client secret for the specified client. Should be called as early as
 // possible before these secrets are accessed.
+COMPONENT_EXPORT(GOOGLE_APIS)
 void SetOAuth2ClientSecret(OAuth2Client client,
                            const std::string& client_secret);
 #endif
 
 // Returns if the API key using in the current build is the one for official
 // Google Chrome.
-bool IsGoogleChromeAPIKeyUsed();
+COMPONENT_EXPORT(GOOGLE_APIS) bool IsGoogleChromeAPIKeyUsed();
 
 }  // namespace google_apis
 
diff --git a/google_apis/google_api_keys_mac.h b/google_apis/google_api_keys_mac.h
index 8c1fc8a..efc03a6 100644
--- a/google_apis/google_api_keys_mac.h
+++ b/google_apis/google_api_keys_mac.h
@@ -7,8 +7,11 @@
 
 #include <string>
 
+#include "base/component_export.h"
+
 namespace google_apis {
 
+COMPONENT_EXPORT(GOOGLE_APIS)
 std::string GetAPIKeyFromInfoPlist(const std::string& key_name);
 
 }  // namespace google_apis
diff --git a/gpu/command_buffer/service/abstract_texture_android.h b/gpu/command_buffer/service/abstract_texture_android.h
index 2cfc335e..9e97d63 100644
--- a/gpu/command_buffer/service/abstract_texture_android.h
+++ b/gpu/command_buffer/service/abstract_texture_android.h
@@ -71,7 +71,7 @@
   bool have_context_ = true;
 
   std::unique_ptr<TextureBase> texture_for_testing_;
-  raw_ptr<gles2::Texture, DanglingUntriaged> texture_;
+  raw_ptr<gles2::Texture, DanglingUntriaged> texture_ = nullptr;
   scoped_refptr<gles2::TexturePassthrough> texture_passthrough_;
   raw_ptr<gl::GLApi, DanglingUntriaged> api_ = nullptr;
 };
diff --git a/gpu/command_buffer/service/webgpu_decoder_impl.cc b/gpu/command_buffer/service/webgpu_decoder_impl.cc
index 8cc325d2..d819846 100644
--- a/gpu/command_buffer/service/webgpu_decoder_impl.cc
+++ b/gpu/command_buffer/service/webgpu_decoder_impl.cc
@@ -1532,8 +1532,17 @@
   swiftShaderOptions.forceSwiftShader = true;
   dawn_instance_->DiscoverAdapters(&swiftShaderOptions);
 #endif  // BUILDFLAG(ENABLE_VULKAN)
-#endif  // BUILDFLAG(IS_WIN)
+  if (use_webgpu_adapter_ == WebGPUAdapterName::kCompat) {
+    // On compat, discover default adapters to also discover the compat adapter.
+    // TODO(senorblanco): This may incorrectly discover a compat adapter that
+    // does not match the one ANGLE is using.
+    dawn_instance_->DiscoverDefaultAdapters();
+  }
+#else   // BUILDFLAG(IS_WIN)
+  // Only discover default adapters on non-Windows. Windows requires
+  // compatibility with ANGLE. Other adapters will not be compatible.
   dawn_instance_->DiscoverDefaultAdapters();
+#endif  // BUILDFLAG(IS_WIN)
 
   std::vector<dawn::native::Adapter> adapters = dawn_instance_->GetAdapters();
   for (dawn::native::Adapter& adapter : adapters) {
diff --git a/headless/lib/browser/devtools_api/domain_types_cc.template b/headless/lib/browser/devtools_api/domain_types_cc.template
index abedb81..c6a2f78 100644
--- a/headless/lib/browser/devtools_api/domain_types_cc.template
+++ b/headless/lib/browser/devtools_api/domain_types_cc.template
@@ -31,9 +31,12 @@
   std::unique_ptr<{{type.id}}> result(new {{type.id}}());
   errors->Push();
   errors->SetName("{{type.id}}");
+  {% if type.properties %}
+  const base::Value::Dict& dict = value.GetDict();
+  {% endif %}
   {% for property in type.properties %}
     {% set value_name = property.name | camelcase_to_hacker_style + "_value" %}
-  const base::Value* {{value_name}} = value.FindKey("{{property.name}}");
+  const base::Value* {{value_name}} = dict.Find("{{property.name}}");
   if ({{value_name}}) {
     errors->SetName("{{property.name}}");
     {% if property.optional %}
diff --git a/headless/lib/browser/headless_browser_main_parts_linux.cc b/headless/lib/browser/headless_browser_main_parts_linux.cc
index 0a4264c2..89e4640 100644
--- a/headless/lib/browser/headless_browser_main_parts_linux.cc
+++ b/headless/lib/browser/headless_browser_main_parts_linux.cc
@@ -4,26 +4,129 @@
 
 #include "headless/lib/browser/headless_browser_main_parts.h"
 
+#include <signal.h>
+#include <unistd.h>
+
+#include "base/functional/bind.h"
+#include "base/functional/callback.h"
+#include "base/logging.h"
+#include "base/memory/scoped_refptr.h"
+#include "base/no_destructor.h"
+#include "base/task/single_thread_task_runner.h"
 #include "build/build_config.h"
 #include "build/chromeos_buildflags.h"
-#include "device/bluetooth/dbus/bluez_dbus_manager.h"
+#include "content/public/browser/browser_task_traits.h"
+#include "content/public/browser/browser_thread.h"
+#include "headless/lib/browser/headless_browser_impl.h"
 
 #if !BUILDFLAG(IS_CHROMEOS)
 #include "base/command_line.h"
 #include "components/os_crypt/key_storage_config_linux.h"
 #include "components/os_crypt/os_crypt.h"
-#include "content/public/browser/browser_task_traits.h"
-#include "content/public/browser/browser_thread.h"
 #include "headless/public/switches.h"
 #endif
 
+#if defined(USE_DBUS) && !BUILDFLAG(IS_CHROMEOS_ASH)
+#include "device/bluetooth/dbus/bluez_dbus_manager.h"
+#endif
+
 namespace headless {
 
+namespace {
+
+class BrowserShutdownHandler {
+ public:
+  BrowserShutdownHandler(const BrowserShutdownHandler&) = delete;
+  BrowserShutdownHandler& operator=(const BrowserShutdownHandler&) = delete;
+
+  static void Install(base::OnceClosure shutdown_callback) {
+    GetInstance().Init(content::GetUIThreadTaskRunner({}),
+                       std::move(shutdown_callback));
+
+    // We need to handle SIGTERM, because that is how many POSIX-based distros
+    // ask processes to quit gracefully at shutdown time.
+    struct sigaction action;
+    memset(&action, 0, sizeof(action));
+    action.sa_handler = SIGTERMHandler;
+    PCHECK(sigaction(SIGTERM, &action, nullptr) == 0);
+
+    // Also handle SIGINT - when the user terminates the browser via Ctrl+C. If
+    // the browser process is being debugged, GDB will catch the SIGINT first.
+    action.sa_handler = SIGINTHandler;
+    PCHECK(sigaction(SIGINT, &action, nullptr) == 0);
+
+    // And SIGHUP, for when the terminal disappears. On shutdown, many Linux
+    // distros send SIGHUP, SIGTERM, and then SIGKILL.
+    action.sa_handler = SIGHUPHandler;
+    PCHECK(sigaction(SIGHUP, &action, nullptr) == 0);
+  }
+
+ private:
+  friend class base::NoDestructor<BrowserShutdownHandler>;
+
+  BrowserShutdownHandler() = default;
+  ~BrowserShutdownHandler() = default;
+
+  static BrowserShutdownHandler& GetInstance() {
+    static base::NoDestructor<BrowserShutdownHandler> instance;
+    return *instance;
+  }
+
+  void Init(scoped_refptr<base::SingleThreadTaskRunner> task_runner,
+            base::OnceClosure shutdown_callback) {
+    task_runner_ = std::move(task_runner);
+    shutdown_callback_ = std::move(shutdown_callback);
+  }
+
+  void Shutdown(int signal) {
+    if (shutdown_callback_) {
+      if (!task_runner_->PostTask(FROM_HERE, std::move(shutdown_callback_))) {
+        RAW_LOG(WARNING, "No valid task runner, exiting ungracefully.");
+        kill(getpid(), signal);
+      }
+    }
+  }
+
+  static void SIGHUPHandler(int signal) {
+    RAW_CHECK(signal == SIGHUP);
+    ShutdownHandler(signal);
+  }
+
+  static void SIGINTHandler(int signal) {
+    RAW_CHECK(signal == SIGINT);
+    ShutdownHandler(signal);
+  }
+
+  static void SIGTERMHandler(int signal) {
+    RAW_CHECK(signal == SIGTERM);
+    ShutdownHandler(signal);
+  }
+
+  static void ShutdownHandler(int signal) {
+    // Reinstall the default handler. We have only one shot at graceful
+    // shutdown.
+    struct sigaction action;
+    memset(&action, 0, sizeof(action));
+    action.sa_handler = SIG_DFL;
+    RAW_CHECK(sigaction(signal, &action, nullptr) == 0);
+
+    GetInstance().Shutdown(signal);
+  }
+
+  scoped_refptr<base::SingleThreadTaskRunner> task_runner_;
+  base::OnceClosure shutdown_callback_;
+};
+
+}  // namespace
+
 #if !BUILDFLAG(IS_CHROMEOS)
 constexpr char kProductName[] = "HeadlessChrome";
 #endif
 
 void HeadlessBrowserMainParts::PostCreateMainMessageLoop() {
+  BrowserShutdownHandler::Install(
+      base::BindOnce(&HeadlessBrowserImpl::Shutdown, browser_->GetWeakPtr()));
+
 #if defined(USE_DBUS) && !BUILDFLAG(IS_CHROMEOS_ASH)
   bluez::BluezDBusManager::Initialize(/*system_bus=*/nullptr);
 #endif
diff --git a/infra/config/dev.star b/infra/config/dev.star
index aaf286a0..4a17e62 100755
--- a/infra/config/dev.star
+++ b/infra/config/dev.star
@@ -37,9 +37,10 @@
     data = io.read_file("luci-analysis-dev.cfg"),
 )
 
-lucicfg.emit(
-    dest = "luci/chops-weetbix-dev.cfg",
-    data = io.read_file("chops-weetbix-dev.cfg"),
-)
+# TODO(b/270163072): Re-emit after staging is fixed.
+# lucicfg.emit(
+#     dest = "luci/chops-weetbix-dev.cfg",
+#     data = io.read_file("chops-weetbix-dev.cfg"),
+# )
 
 branches.exec("//dev/dev.star")
diff --git a/infra/config/generated/luci/chops-weetbix-dev.cfg b/infra/config/generated/luci/chops-weetbix-dev.cfg
deleted file mode 100644
index 07e05c1..0000000
--- a/infra/config/generated/luci/chops-weetbix-dev.cfg
+++ /dev/null
@@ -1,77 +0,0 @@
-# Schema for this config file: ProjectConfig in:
-# https://luci-config.appspot.com/schemas/projects:luci-analysis.cfg
-
-bug_filing_threshold {
-  # Do not file bugs for now.
-}
-
-clustering {
-  test_name_rules {
-    name: "Blink Web Tests"
-    # To match blink_web_tests as well as webgpu_blink_web_tests and any others.
-    pattern: "^ninja://:(?P<target>\\w*blink_web_tests)/(virtual/[^/]+/)?(?P<test>([^/]+/)+[^/]+\\.[a-zA-Z]+).*$"
-    like_template: "ninja://:${target}/%${test}%"
-  }
-  test_name_rules {
-    name: "Google Test (Value-parameterized)"
-    pattern: "^ninja:(?P<target>[\\w/]+:\\w+)/(\\w+/)?(?P<suite>\\w+)\\.(?P<test>\\w+)/[\\w.]+$"
-    like_template: "ninja:${target}/%${suite}.${test}%"
-  }
-  test_name_rules {
-    name: "Google Test (Type-parameterized)"
-    pattern: "^ninja:(?P<target>[\\w/]+:\\w+)/(\\w+/)?(?P<suite>\\w+)/\\w+\\.(?P<test>\\w+)$"
-    like_template: "ninja:${target}/%${suite}/%.${test}"
-  }
-  test_name_rules {
-    name: "JUnit Test (Parameterized)"
-    # Matches parameterized JUnit tests like:
-    # ninja://android_webview/test:webview_instrumentation_test_apk/org.chromium.android_webview.test.MyTest#testFoo__parameter1
-    # Also matches tests parameterized with different command line flags,
-    # as constructed by https://source.chromium.org/chromium/chromium/src/+/main:build/android/pylib/instrumentation/instrumentation_test_instance.py?q=%22def%20GetUniqueTestName(%22
-    # E.g. ninja://chrome/android:chrome_public_test_apk/org.chromium.chrome.browser.omnibox.OmniboxTest#testDefaultText_with___disable_features=SpannableInlineAutocomplete
-    pattern: "^ninja:(?P<target>[\\w/]+:\\w+)/(?P<class>[\\w$.]+)#(?P<test>\\w+?)(?P<sep>__|_with_)[\\w.=,]+$"
-    like_template: "ninja:${target}/${class}#${test}${sep}%"
-  }
-}
-
-monorail {
-  project: "chromium"
-  default_field_values {
-    # Type field.
-    field_id: 10
-    value: "Bug"
-  }
-  priority_field_id: 11
-  priorities {
-    priority: "0"
-    threshold {
-      presubmit_runs_failed {
-        one_day: 20
-      }
-    }
-  }
-  priorities {
-    priority: "1"
-    threshold {
-      presubmit_runs_failed {
-        one_day: 10
-      }
-    }
-  }
-  priorities {
-    priority: "2"
-    threshold {
-      # Clusters which fail to meet this threshold will be closed.
-      presubmit_runs_failed {
-        seven_day: 1
-      }
-      critical_failures_exonerated {
-        seven_day: 1
-      }
-    }
-  }
-  priority_hysteresis_percent: 50
-  monorail_hostname: "monorail-staging.appspot.com"
-  display_prefix: "crbug.com"
-}
-
diff --git a/ios/chrome/browser/signin/gaia_auth_fetcher_ios.mm b/ios/chrome/browser/signin/gaia_auth_fetcher_ios.mm
index 2c42335d..49ffe7f 100644
--- a/ios/chrome/browser/signin/gaia_auth_fetcher_ios.mm
+++ b/ios/chrome/browser/signin/gaia_auth_fetcher_ios.mm
@@ -37,7 +37,10 @@
   DCHECK(!HasPendingFetch()) << "Tried to fetch two things at once!";
 
   bool cookies_required =
-      credentials_mode != network::mojom::CredentialsMode::kOmit;
+      credentials_mode != network::mojom::CredentialsMode::kOmit &&
+      credentials_mode !=
+          network::mojom::CredentialsMode::kOmitBug_775438_Workaround;
+
   if (!cookies_required) {
     GaiaAuthFetcher::CreateAndStartGaiaFetcher(body, body_content_type, headers,
                                                gaia_gurl, credentials_mode,
diff --git a/ios/chrome/browser/ui/bookmarks/bookmark_utils_ios.h b/ios/chrome/browser/ui/bookmarks/bookmark_utils_ios.h
index a453ff9..e9576b0 100644
--- a/ios/chrome/browser/ui/bookmarks/bookmark_utils_ios.h
+++ b/ios/chrome/browser/ui/bookmarks/bookmark_utils_ios.h
@@ -24,6 +24,11 @@
 class BookmarkNode;
 }  // namespace bookmarks
 
+enum class BookmarkModelType {
+  kProfile,
+  kAccount,
+};
+
 namespace bookmark_utils_ios {
 
 typedef std::vector<const bookmarks::BookmarkNode*> NodeVector;
@@ -47,6 +52,20 @@
 // to display a slighly different wording for the default folders.
 NSString* TitleForBookmarkNode(const bookmarks::BookmarkNode* node);
 
+// Returns the model type for a node, based on profile model and account model,
+// based on the root node.
+// `bookmark_node` is the bookmark to query. It can not be null.
+// `profile_model` is the profile mode. It can not be null.
+// `account_model` is the account mode. It can be null.
+// The node must belongs to one of the two models.
+// This function is linear in time in the depth of the bookmark_node.
+// TODO(crbug.com/1417992): once the bookmark nodes has access to its model,
+// rewrite the function to be constant time.
+BookmarkModelType GetBookmarkModelType(
+    const bookmarks::BookmarkNode* bookmark_node,
+    bookmarks::BookmarkModel* profile_model,
+    bookmarks::BookmarkModel* account_model);
+
 #pragma mark - Updating Bookmarks
 
 // Creates the bookmark if `node` is NULL. Otherwise updates `node`.
diff --git a/ios/chrome/browser/ui/bookmarks/bookmark_utils_ios.mm b/ios/chrome/browser/ui/bookmarks/bookmark_utils_ios.mm
index 82f36a3..e22cdad5 100644
--- a/ios/chrome/browser/ui/bookmarks/bookmark_utils_ios.mm
+++ b/ios/chrome/browser/ui/bookmarks/bookmark_utils_ios.mm
@@ -16,6 +16,7 @@
 #import "base/hash/hash.h"
 #import "base/i18n/string_compare.h"
 #import "base/metrics/user_metrics_action.h"
+#import "base/notreached.h"
 #import "base/ranges/algorithm.h"
 #import "base/strings/string_number_conversions.h"
 #import "base/strings/sys_string_conversions.h"
@@ -108,6 +109,19 @@
   return title;
 }
 
+BookmarkModelType GetBookmarkModelType(
+    const bookmarks::BookmarkNode* bookmark_node,
+    bookmarks::BookmarkModel* profile_model,
+    bookmarks::BookmarkModel* account_model) {
+  DCHECK(profile_model);
+  if (bookmark_node->HasAncestor(profile_model->root_node())) {
+    return BookmarkModelType::kProfile;
+  }
+  DCHECK(account_model &&
+         bookmark_node->HasAncestor(account_model->root_node()));
+  return BookmarkModelType::kAccount;
+}
+
 #pragma mark - Updating Bookmarks
 
 // Deletes all subnodes of `node`, including `node`, that are in `bookmarks`.
diff --git a/ios/chrome/browser/ui/content_suggestions/content_suggestions_constants.h b/ios/chrome/browser/ui/content_suggestions/content_suggestions_constants.h
index a51650d..19b0d01 100644
--- a/ios/chrome/browser/ui/content_suggestions/content_suggestions_constants.h
+++ b/ios/chrome/browser/ui/content_suggestions/content_suggestions_constants.h
@@ -37,4 +37,25 @@
 // TrendingQueryIndex enum so it can capture a higher max value.
 const int kMaxTrendingQueries = 4;
 
+// Tile Ablation constants.
+// The amount of time between two tile ablation NTP impressions. (User opens
+// NTP, 1 impression. If user goes back to NTP within
+// `kTileAblationImpressionThresholdMinutes` don't count that as an NTP
+// impression.
+extern const int kTileAblationImpressionThresholdMinutes;
+// Minimum and Maximum amount of days the Tile Ablation experiment can run for.
+extern const int kTileAblationMinimumUseThresholdInDays;
+extern const int kTileAblationMaximumUseThresholdInDays;
+// Minimum an Maximum number of impressions until the Tile Ablation experiment
+// ends before the NTP goes back to the normal state.
+extern const int kMinimumImpressionThresholdTileAblation;
+extern const int kMaximumImpressionThresholdTileAblation;
+// Stores the last time an NTP impression was recorded.
+extern NSString* const kLastNTPImpressionRecordedKey;
+// Stores the number of NTP impressions over a period of time.
+extern NSString* const kNumberOfNTPImpressionsRecordedKey;
+// Stores the first NTP impression for the MVT experiment.
+extern NSString* const kFirstImpressionRecordedTileAblationKey;
+extern NSString* const kDoneWithTileAblationKey;
+
 #endif  // IOS_CHROME_BROWSER_UI_CONTENT_SUGGESTIONS_CONTENT_SUGGESTIONS_CONSTANTS_H_
diff --git a/ios/chrome/browser/ui/content_suggestions/content_suggestions_constants.mm b/ios/chrome/browser/ui/content_suggestions/content_suggestions_constants.mm
index 6735d01..fb4d580 100644
--- a/ios/chrome/browser/ui/content_suggestions/content_suggestions_constants.mm
+++ b/ios/chrome/browser/ui/content_suggestions/content_suggestions_constants.mm
@@ -20,3 +20,15 @@
     @"contentSuggestionsShortcutsAccessibilityIdentifierPrefix";
 
 const CGFloat kMostVisitedBottomMargin = 13;
+
+const int kTileAblationImpressionThresholdMinutes = 5;
+const int kTileAblationMinimumUseThresholdInDays = 7;
+const int kTileAblationMaximumUseThresholdInDays = 14;
+const int kMinimumImpressionThresholdTileAblation = 10;
+const int kMaximumImpressionThresholdTileAblation = 20;
+NSString* const kLastNTPImpressionRecordedKey = @"LastNTPImpressionRecorded";
+NSString* const kNumberOfNTPImpressionsRecordedKey =
+    @"NumberOfNTPImpressionsRecorded";
+NSString* const kFirstImpressionRecordedTileAblationKey =
+    @"kFirstImpressionRecordedTileAblationKey";
+NSString* const kDoneWithTileAblationKey = @"DoneWithTileAblation";
diff --git a/ios/chrome/browser/ui/content_suggestions/content_suggestions_mediator.mm b/ios/chrome/browser/ui/content_suggestions/content_suggestions_mediator.mm
index ac615bb..5d48b11 100644
--- a/ios/chrome/browser/ui/content_suggestions/content_suggestions_mediator.mm
+++ b/ios/chrome/browser/ui/content_suggestions/content_suggestions_mediator.mm
@@ -222,6 +222,7 @@
 }
 
 - (void)reloadAllData {
+  BOOL isTileAblationComplete = [self isTileAblationComplete];
   if (!self.consumer) {
     return;
   }
@@ -229,10 +230,12 @@
     [self.consumer
         showReturnToRecentTabTileWithConfig:self.returnToRecentTabItem];
   }
-  if ([self.mostVisitedItems count] && !ShouldHideMostVisited()) {
+  if ([self.mostVisitedItems count] &&
+      (!ShouldHideMostVisited() || isTileAblationComplete)) {
     [self.consumer setMostVisitedTilesWithConfigs:self.mostVisitedItems];
   }
-  if (!ShouldHideShortcutsForTrendingQueries() && !ShouldHideShortcuts()) {
+  if (!ShouldHideShortcutsForTrendingQueries() &&
+      (!ShouldHideShortcuts() || isTileAblationComplete)) {
     [self.consumer setShortcutTilesWithConfigs:self.actionButtonItems];
   }
   if (IsTrendingQueriesModuleEnabled()) {
@@ -479,7 +482,7 @@
 
 - (void)onMostVisitedURLsAvailable:
     (const ntp_tiles::NTPTilesVector&)mostVisited {
-  if (ShouldHideMostVisited()) {
+  if (ShouldHideMostVisited() && ![self isTileAblationComplete]) {
     return;
   }
 
@@ -529,7 +532,7 @@
 
 // Replaces the Most Visited items currently displayed by the most recent ones.
 - (void)useFreshMostVisited {
-  if (ShouldHideMostVisited()) {
+  if (ShouldHideMostVisited() && ![self isTileAblationComplete]) {
     return;
   }
   self.mostVisitedItems = self.freshMostVisitedItems;
@@ -641,6 +644,52 @@
   return !isSignedIn;
 }
 
+// Checks if users have met conditions to drop from the experiment to hide the
+// Most Visited Tiles and Shortcuts from the NTP.
+- (BOOL)isTileAblationComplete {
+  // Conditions:
+  // MVT/Shortcuts Should be shown again if:
+  // 1. User has used Bling < `kTileAblationMinimumUseThresholdInDays` days AND
+  // NTP Impressions > `kMinimumImpressionThresholdTileAblation`; or
+  // 2. User has used Bling >= `kTileAblationMaximumUseThresholdInDays` days
+  // or
+  // 3. NTP Impressions > `kMaximumImpressionThresholdTileAblation`;
+  // NTP impression time threshold is >=
+  // `kTileAblationImpressionThresholdMinutes` minutes per impression.
+  // (eg. 2 NTP impressions within <5 minutes of each other will count as 1 NTP
+  // impression for the purposes of this test.
+
+  // Return early if ablation was already complete.
+  NSUserDefaults* defaults = [NSUserDefaults standardUserDefaults];
+  if ([defaults boolForKey:kDoneWithTileAblationKey]) {
+    return YES;
+  }
+
+  int impressions = [defaults integerForKey:kNumberOfNTPImpressionsRecordedKey];
+  NSDate* firstImpressionDate = base::mac::ObjCCast<NSDate>(
+      [defaults objectForKey:kFirstImpressionRecordedTileAblationKey]);
+  // Return early if no NTP impression has been recorded.
+  if (firstImpressionDate == nil) {
+    return NO;
+  }
+  base::Time firstImpressionTime = base::Time::FromNSDate(firstImpressionDate);
+
+  if (  // User has used Bling < `kTileAblationMinimumUseThresholdInDays` days
+        // AND NTP Impressions > `kMinimumImpressionThresholdTileAblation`; or
+      (base::Time::Now() - firstImpressionTime >=
+           base::Days(kTileAblationMinimumUseThresholdInDays) &&
+       impressions >= kMinimumImpressionThresholdTileAblation) ||
+      // User has used Bling >= `kTileAblationMaximumUseThresholdInDays` days
+      (base::Time::Now() - firstImpressionTime >=
+       base::Days(kTileAblationMaximumUseThresholdInDays)) ||
+      // NTP Impressions >= `kMaximumImpressionThresholdTileAblation`;
+      (impressions >= kMaximumImpressionThresholdTileAblation)) {
+    [defaults setBool:YES forKey:kDoneWithTileAblationKey];
+    return YES;
+  }
+  return NO;
+}
+
 #pragma mark - Properties
 
 - (NSArray<ContentSuggestionsMostVisitedActionItem*>*)actionButtonItems {
diff --git a/ios/chrome/browser/ui/content_suggestions/ntp_home_metrics.h b/ios/chrome/browser/ui/content_suggestions/ntp_home_metrics.h
index 5266330..ea206ed 100644
--- a/ios/chrome/browser/ui/content_suggestions/ntp_home_metrics.h
+++ b/ios/chrome/browser/ui/content_suggestions/ntp_home_metrics.h
@@ -19,7 +19,8 @@
 
 // Records an NTP impression of type `impression_type`.
 void RecordNTPImpression(ntp_home::IOSNTPImpression impression_type);
-
+// Records when an NTP impression has occurred for Tile Ablation.
+void NTPImpressionHasOccurred();
 }  // namespace ntp_home
 
 // These values are persisted to IOS.ContentSuggestions.ActionOn* histograms.
diff --git a/ios/chrome/browser/ui/content_suggestions/ntp_home_metrics.mm b/ios/chrome/browser/ui/content_suggestions/ntp_home_metrics.mm
index ebe0de2..b6a543a0 100644
--- a/ios/chrome/browser/ui/content_suggestions/ntp_home_metrics.mm
+++ b/ios/chrome/browser/ui/content_suggestions/ntp_home_metrics.mm
@@ -4,9 +4,11 @@
 
 #import "ios/chrome/browser/ui/content_suggestions/ntp_home_metrics.h"
 
+#import "base/mac/foundation_util.h"
 #import "base/metrics/histogram_macros.h"
 #import "base/metrics/user_metrics.h"
 #import "base/metrics/user_metrics_action.h"
+#import "ios/chrome/browser/ui/content_suggestions/content_suggestions_constants.h"
 
 #if !defined(__has_feature) || !__has_feature(objc_arc)
 #error "This file requires ARC support."
@@ -14,8 +16,41 @@
 
 namespace ntp_home {
 
+// Records when an NTP impression has occurred for purposes of Tile Ablation.
+void NTPImpressionHasOccurred() {
+  base::Time now = base::Time::Now();
+  NSUserDefaults* defaults = [NSUserDefaults standardUserDefaults];
+  if ([defaults boolForKey:kDoneWithTileAblationKey]) {
+    return;
+  }
+  // Find/Set first NTP impression ever.
+  NSDate* firstImpressionRecordedTileAblationExperiment =
+      base::mac::ObjCCast<NSDate>(
+          [defaults objectForKey:kFirstImpressionRecordedTileAblationKey]);
+  int impressions = [defaults integerForKey:kNumberOfNTPImpressionsRecordedKey];
+  // Record first NTP impression.
+  if (firstImpressionRecordedTileAblationExperiment == nil) {
+    [defaults setObject:now.ToNSDate()
+                 forKey:kFirstImpressionRecordedTileAblationKey];
+    [defaults setObject:now.ToNSDate() forKey:kLastNTPImpressionRecordedKey];
+    [defaults setInteger:1 forKey:kNumberOfNTPImpressionsRecordedKey];
+    return;
+  }
+  NSDate* lastImpressionTileAblation = base::mac::ObjCCast<NSDate>(
+      [defaults objectForKey:kLastNTPImpressionRecordedKey]);
+  // Check when the last impression happened.
+  if (now - base::Time::FromNSDate(lastImpressionTileAblation) >=
+      base::Minutes(kTileAblationImpressionThresholdMinutes)) {
+    // Count impression for MVT/Shortcuts Experiment.
+    [defaults setObject:now.ToNSDate() forKey:kLastNTPImpressionRecordedKey];
+    [defaults setInteger:impressions + 1
+                  forKey:kNumberOfNTPImpressionsRecordedKey];
+  }
+}
+
 void RecordNTPImpression(IOSNTPImpression impression_type) {
   UMA_HISTOGRAM_ENUMERATION("IOS.NTP.Impression", impression_type, COUNT);
+  NTPImpressionHasOccurred();
 }
 
 }  // namespace ntp_home
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 edf2d11..0078f5c 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
@@ -214,7 +214,6 @@
 
   self.headerController.showing = YES;
 
-  [self applyCollectionViewConstraints];
   [self updateNTPLayout];
 
   // Scroll to the top before coming into view to minimize sudden visual jerking
@@ -435,14 +434,15 @@
   // changes in feed visibility.
   [self applyCollectionViewConstraints];
 
+  // Force relayout so that the views added in this method are rendered ASAP,
+  // ensuring it is showing in the new tab animation.
+  [self.view setNeedsLayout];
+  [self.view layoutIfNeeded];
+
   // If the feed is not visible, we control the delegate ourself (since it is
-  // otherwise controlled by the feed service). The view is also layed out
-  // so that we can correctly calculate the minimum height.
+  // otherwise controlled by the feed service).
   if (!self.isFeedVisible) {
     self.feedWrapperViewController.contentCollectionView.delegate = self;
-
-    [self.view setNeedsLayout];
-    [self.view layoutIfNeeded];
     [self setMinimumHeight];
   }
 }
diff --git a/ios/chrome/browser/ui/overlays/infobar_banner/passwords/password_infobar_banner_overlay_mediator_unittest.mm b/ios/chrome/browser/ui/overlays/infobar_banner/passwords/password_infobar_banner_overlay_mediator_unittest.mm
index 6870893..0b3ee77c 100644
--- a/ios/chrome/browser/ui/overlays/infobar_banner/passwords/password_infobar_banner_overlay_mediator_unittest.mm
+++ b/ios/chrome/browser/ui/overlays/infobar_banner/passwords/password_infobar_banner_overlay_mediator_unittest.mm
@@ -98,7 +98,7 @@
   InfoBarIOS infobar(InfobarType::kInfobarTypePasswordSave,
                      MockIOSChromeSavePasswordInfoBarDelegate::Create(
                          kUsername, kPassword, GURL::EmptyGURL(),
-                         /*account_store_password=*/absl::nullopt));
+                         /*account_to_store_password=*/absl::nullopt));
   // Package the infobar into an OverlayRequest, then create a mediator that
   // uses this request in order to set up a fake consumer.
   std::unique_ptr<OverlayRequest> request = OverlayRequest::CreateWithConfig<
diff --git a/ios/chrome/browser/ui/settings/password/password_checkup/BUILD.gn b/ios/chrome/browser/ui/settings/password/password_checkup/BUILD.gn
index cc86d72..a757337 100644
--- a/ios/chrome/browser/ui/settings/password/password_checkup/BUILD.gn
+++ b/ios/chrome/browser/ui/settings/password/password_checkup/BUILD.gn
@@ -15,6 +15,8 @@
   deps = [
     ":password_checkup_ui",
     "//components/password_manager/core/browser",
+    "//ios/chrome/browser/main:public",
+    "//ios/chrome/browser/passwords",
     "//ios/chrome/browser/ui/coordinators:chrome_coordinators",
     "//ios/chrome/browser/ui/table_view:utils",
   ]
diff --git a/ios/chrome/browser/ui/settings/password/password_checkup/password_checkup_coordinator.mm b/ios/chrome/browser/ui/settings/password/password_checkup/password_checkup_coordinator.mm
index 8751e960..933bf226 100644
--- a/ios/chrome/browser/ui/settings/password/password_checkup/password_checkup_coordinator.mm
+++ b/ios/chrome/browser/ui/settings/password/password_checkup/password_checkup_coordinator.mm
@@ -4,6 +4,9 @@
 
 #import "ios/chrome/browser/ui/settings/password/password_checkup/password_checkup_coordinator.h"
 
+#import "ios/chrome/browser/main/browser.h"
+#import "ios/chrome/browser/passwords/ios_chrome_password_check_manager.h"
+#import "ios/chrome/browser/passwords/ios_chrome_password_check_manager_factory.h"
 #import "ios/chrome/browser/ui/settings/password/password_checkup/password_checkup_commands.h"
 #import "ios/chrome/browser/ui/settings/password/password_checkup/password_checkup_mediator.h"
 #import "ios/chrome/browser/ui/settings/password/password_checkup/password_checkup_view_controller.h"
@@ -43,13 +46,17 @@
   self.viewController = [[PasswordCheckupViewController alloc]
       initWithStyle:ChromeTableViewStyle()];
   self.viewController.handler = self;
-  self.mediator = [[PasswordCheckupMediator alloc] init];
+  self.mediator = [[PasswordCheckupMediator alloc]
+      initWithPasswordCheckManager:IOSChromePasswordCheckManagerFactory::
+                                       GetForBrowserState(
+                                           self.browser->GetBrowserState())];
   self.mediator.consumer = self.viewController;
   [self.baseNavigationController pushViewController:self.viewController
                                            animated:YES];
 }
 
 - (void)stop {
+  [self.mediator disconnect];
   self.mediator = nil;
   self.viewController.handler = nil;
   self.viewController = nil;
diff --git a/ios/chrome/browser/ui/settings/password/password_checkup/password_checkup_mediator.h b/ios/chrome/browser/ui/settings/password/password_checkup/password_checkup_mediator.h
index 0ceb72d0..026bcce 100644
--- a/ios/chrome/browser/ui/settings/password/password_checkup/password_checkup_mediator.h
+++ b/ios/chrome/browser/ui/settings/password/password_checkup/password_checkup_mediator.h
@@ -7,15 +7,26 @@
 
 #import <Foundation/Foundation.h>
 
+#import "ios/chrome/browser/passwords/ios_chrome_password_check_manager.h"
+
 @protocol PasswordCheckupConsumer;
 
 // This mediator fetches and organises the insecure credentials for its
 // consumer.
 @interface PasswordCheckupMediator : NSObject
 
+- (instancetype)initWithPasswordCheckManager:
+    (scoped_refptr<IOSChromePasswordCheckManager>)passwordCheckManager
+    NS_DESIGNATED_INITIALIZER;
+
+- (instancetype)init NS_UNAVAILABLE;
+
 // Consumer of this mediator.
 @property(nonatomic, weak) id<PasswordCheckupConsumer> consumer;
 
+// Disconnect the observers.
+- (void)disconnect;
+
 @end
 
 #endif  // IOS_CHROME_BROWSER_UI_SETTINGS_PASSWORD_PASSWORD_CHECKUP_PASSWORD_CHECKUP_MEDIATOR_H_
diff --git a/ios/chrome/browser/ui/settings/password/password_checkup/password_checkup_mediator.mm b/ios/chrome/browser/ui/settings/password/password_checkup/password_checkup_mediator.mm
index eb16435..d860ad7 100644
--- a/ios/chrome/browser/ui/settings/password/password_checkup/password_checkup_mediator.mm
+++ b/ios/chrome/browser/ui/settings/password/password_checkup/password_checkup_mediator.mm
@@ -4,12 +4,38 @@
 
 #import "ios/chrome/browser/ui/settings/password/password_checkup/password_checkup_mediator.h"
 
+#import "ios/chrome/browser/passwords/ios_chrome_password_check_manager.h"
+#import "ios/chrome/browser/passwords/password_check_observer_bridge.h"
+#import "ios/chrome/browser/ui/settings/password/password_checkup/password_checkup_consumer.h"
+
 #if !defined(__has_feature) || !__has_feature(objc_arc)
 #error "This file requires ARC support."
 #endif
 
+@interface PasswordCheckupMediator () <PasswordCheckObserver> {
+  // The service responsible for password check feature.
+  scoped_refptr<IOSChromePasswordCheckManager> _passwordCheckManager;
+
+  // A helper object for passing data about changes in password check status
+  // and changes to compromised credentials list.
+  std::unique_ptr<PasswordCheckObserverBridge> _passwordCheckObserver;
+}
+
+@end
+
 @implementation PasswordCheckupMediator
 
+- (instancetype)initWithPasswordCheckManager:
+    (scoped_refptr<IOSChromePasswordCheckManager>)passwordCheckManager {
+  self = [super init];
+  if (self) {
+    _passwordCheckManager = passwordCheckManager;
+    _passwordCheckObserver = std::make_unique<PasswordCheckObserverBridge>(
+        self, _passwordCheckManager.get());
+  }
+  return self;
+}
+
 - (void)setConsumer:(id<PasswordCheckupConsumer>)consumer {
   if (_consumer == consumer) {
     return;
@@ -17,4 +43,19 @@
   _consumer = consumer;
 }
 
+- (void)disconnect {
+  _passwordCheckObserver.reset();
+  _passwordCheckManager.reset();
+}
+
+#pragma mark - PasswordCheckObserver
+
+- (void)passwordCheckStateDidChange:(PasswordCheckState)state {
+  // TODO(crbug.com/1406540): Add method's body.
+}
+
+- (void)insecureCredentialsDidChange {
+  // TODO(crbug.com/1406540): Add method's body.
+}
+
 @end
diff --git a/ios/chrome/browser/ui/settings/password/password_details/BUILD.gn b/ios/chrome/browser/ui/settings/password/password_details/BUILD.gn
index 931aca06..49a25bc3 100644
--- a/ios/chrome/browser/ui/settings/password/password_details/BUILD.gn
+++ b/ios/chrome/browser/ui/settings/password/password_details/BUILD.gn
@@ -35,6 +35,7 @@
     "//ios/chrome/browser/ui/alert_coordinator",
     "//ios/chrome/browser/ui/commands",
     "//ios/chrome/browser/ui/coordinators:chrome_coordinators",
+    "//ios/chrome/browser/ui/settings/password:common",
     "//ios/chrome/browser/ui/settings/utils",
     "//ios/chrome/browser/ui/table_view",
     "//ios/chrome/browser/ui/util",
diff --git a/ios/chrome/browser/ui/settings/password/password_details/password_details_coordinator.h b/ios/chrome/browser/ui/settings/password/password_details/password_details_coordinator.h
index 42bd0d76..6c0b403 100644
--- a/ios/chrome/browser/ui/settings/password/password_details/password_details_coordinator.h
+++ b/ios/chrome/browser/ui/settings/password/password_details/password_details_coordinator.h
@@ -48,11 +48,6 @@
 // Displays the password data in edit mode without requiring any authentication.
 - (void)showPasswordDetailsInEditModeWithoutAuthentication;
 
-// Remove the credential from the cache and reload password details view
-// controller after a change was made.
-- (void)removeCredentialFromCacheAndRefreshTableView:
-    (const password_manager::CredentialUIEntry&)credential;
-
 // Delegate.
 @property(nonatomic, weak) id<PasswordDetailsCoordinatorDelegate> delegate;
 
diff --git a/ios/chrome/browser/ui/settings/password/password_details/password_details_coordinator.mm b/ios/chrome/browser/ui/settings/password/password_details/password_details_coordinator.mm
index b1e6798..f2922520 100644
--- a/ios/chrome/browser/ui/settings/password/password_details/password_details_coordinator.mm
+++ b/ios/chrome/browser/ui/settings/password/password_details/password_details_coordinator.mm
@@ -326,15 +326,6 @@
   [self.viewController showEditViewWithoutAuthentication];
 }
 
-- (void)removeCredentialFromCacheAndRefreshTableView:
-    (const password_manager::CredentialUIEntry&)credential {
-  // Remove credential from the credentials cache of the password details
-  // manager.
-  [self.mediator removeCredential:credential];
-
-  [self.mediator didFinishEditingPasswordDetails];
-}
-
 - (void)onPasswordCopiedByUser {
   if (IsCredentialProviderExtensionPromoEnabledOnPasswordCopied()) {
     DCHECK(_credentialProviderPromoHandler);
@@ -344,6 +335,12 @@
   }
 }
 
+- (void)onAllPasswordsDeleted {
+  DCHECK_EQ(self.baseNavigationController.topViewController,
+            self.viewController);
+  [self.baseNavigationController popViewControllerAnimated:YES];
+}
+
 #pragma mark - Private
 
 // Notifies delegate about password deletion and records metric if needed.
@@ -369,9 +366,7 @@
     return;
   }
 
-  [self.delegate passwordDetailsCoordinator:self
-                           deleteCredential:*it
-                          shouldDismissView:(credentials.size() - 1 == 0)];
+  [self.mediator removeCredential:*it];
   if (compromised) {
     base::UmaHistogramEnumeration(
         "PasswordManager.BulkCheck.UserAction",
diff --git a/ios/chrome/browser/ui/settings/password/password_details/password_details_coordinator_delegate.h b/ios/chrome/browser/ui/settings/password/password_details/password_details_coordinator_delegate.h
index ccc6011..1b0aacde 100644
--- a/ios/chrome/browser/ui/settings/password/password_details/password_details_coordinator_delegate.h
+++ b/ios/chrome/browser/ui/settings/password/password_details/password_details_coordinator_delegate.h
@@ -5,10 +5,6 @@
 #ifndef IOS_CHROME_BROWSER_UI_SETTINGS_PASSWORD_PASSWORD_DETAILS_PASSWORD_DETAILS_COORDINATOR_DELEGATE_H_
 #define IOS_CHROME_BROWSER_UI_SETTINGS_PASSWORD_PASSWORD_DETAILS_PASSWORD_DETAILS_COORDINATOR_DELEGATE_H_
 
-namespace password_manager {
-struct CredentialUIEntry;
-}  // namespace password_manager
-
 @class PasswordDetailsCoordinator;
 
 // Delegate for PasswordIssuesCoordinator.
@@ -18,15 +14,6 @@
 - (void)passwordDetailsCoordinatorDidRemove:
     (PasswordDetailsCoordinator*)coordinator;
 
-// Called when user deleted password. This action should be handled outside to
-// update the list of passwords immediately. Callers should pass YES for
-// `shouldDismiss` if this is the last password on the page, to ensure the view
-// controller gets dismissed.
-- (void)passwordDetailsCoordinator:(PasswordDetailsCoordinator*)coordinator
-                  deleteCredential:
-                      (const password_manager::CredentialUIEntry&)credential
-                 shouldDismissView:(BOOL)shouldDismiss;
-
 @end
 
 #endif  // IOS_CHROME_BROWSER_UI_SETTINGS_PASSWORD_PASSWORD_DETAILS_PASSWORD_DETAILS_COORDINATOR_DELEGATE_H_
diff --git a/ios/chrome/browser/ui/settings/password/password_details/password_details_handler.h b/ios/chrome/browser/ui/settings/password/password_details/password_details_handler.h
index 0ab08eb..f9c1972 100644
--- a/ios/chrome/browser/ui/settings/password/password_details/password_details_handler.h
+++ b/ios/chrome/browser/ui/settings/password/password_details/password_details_handler.h
@@ -33,6 +33,9 @@
 // Called by the view controller when the user successfully copied a password.
 - (void)onPasswordCopiedByUser;
 
+// Called when all passwords were deleted, in order to close the view.
+- (void)onAllPasswordsDeleted;
+
 @end
 
 #endif  // IOS_CHROME_BROWSER_UI_SETTINGS_PASSWORD_PASSWORD_DETAILS_PASSWORD_DETAILS_HANDLER_H_
diff --git a/ios/chrome/browser/ui/settings/password/password_details/password_details_mediator.mm b/ios/chrome/browser/ui/settings/password/password_details/password_details_mediator.mm
index b3052ae..bb75de9 100644
--- a/ios/chrome/browser/ui/settings/password/password_details/password_details_mediator.mm
+++ b/ios/chrome/browser/ui/settings/password/password_details/password_details_mediator.mm
@@ -9,6 +9,7 @@
 #import <vector>
 
 #import "base/containers/contains.h"
+#import "base/containers/cxx20_erase.h"
 #import "base/memory/raw_ptr.h"
 #import "base/strings/sys_string_conversions.h"
 #import "components/password_manager/core/browser/move_password_to_account_store_helper.h"
@@ -127,10 +128,16 @@
 
 - (void)removeCredential:
     (const password_manager::CredentialUIEntry&)credential {
-  auto it = base::ranges::find(_credentials, credential);
-  if (it != _credentials.end()) {
-    _credentials.erase(it);
-  }
+  // TODO(crbug.com/1359392). Once kPasswordsGrouping launches, the mediator
+  // should update the passwords model and receive the updates via
+  // SavedPasswordsPresenterObserver, instead of replicating the updates to its
+  // own copy and calling [self providePasswordsToConsumer:]. Today when the
+  // flag is disabled and the password is edited, it's impossible to identify
+  // the new object to show (sign-on realm can't be used as an id, there might
+  // be multiple credentials; nor username/password since the values changed).
+  base::Erase(_credentials, credential);
+  _manager->GetSavedPasswordsPresenter()->RemoveCredential(credential);
+  [self providePasswordsToConsumer];
 }
 
 - (void)moveCredentialToAccountStore:
diff --git a/ios/chrome/browser/ui/settings/password/password_details/password_details_table_view_controller.mm b/ios/chrome/browser/ui/settings/password/password_details/password_details_table_view_controller.mm
index 933563b..770d87b 100644
--- a/ios/chrome/browser/ui/settings/password/password_details/password_details_table_view_controller.mm
+++ b/ios/chrome/browser/ui/settings/password/password_details/password_details_table_view_controller.mm
@@ -570,15 +570,18 @@
 
 - (void)setPasswords:(NSArray<PasswordDetails*>*)passwords
             andTitle:(NSString*)title {
-  if (IsPasswordGroupingEnabled()) {
-    DCHECK(passwords.count > 0);
-  } else {
-    DCHECK(passwords.count == 1);
-  }
-
+  BOOL hadPasswords = [_passwords count];
   _passwords = passwords;
   _pageTitle = title;
 
+  if (![passwords count]) {
+    // onAllPasswordsDeleted() mustn't be called twice.
+    if (hadPasswords) {
+      [self.handler onAllPasswordsDeleted];
+    }
+    return;
+  }
+
   [self updateNavigationTitle];
 
   [self reloadData];
diff --git a/ios/chrome/browser/ui/settings/password/password_details/password_details_table_view_controller_unittest.mm b/ios/chrome/browser/ui/settings/password/password_details/password_details_table_view_controller_unittest.mm
index 5bf8469..b6d3d747 100644
--- a/ios/chrome/browser/ui/settings/password/password_details/password_details_table_view_controller_unittest.mm
+++ b/ios/chrome/browser/ui/settings/password/password_details/password_details_table_view_controller_unittest.mm
@@ -93,6 +93,9 @@
   self.passwordCopiedByUserCalled = YES;
 }
 
+- (void)onAllPasswordsDeleted {
+}
+
 @end
 
 // Test class that conforms to PasswordDetailsViewControllerDelegate in order
diff --git a/ios/chrome/browser/ui/settings/password/password_issues_coordinator.h b/ios/chrome/browser/ui/settings/password/password_issues_coordinator.h
index f9dce71..fef9f22 100644
--- a/ios/chrome/browser/ui/settings/password/password_issues_coordinator.h
+++ b/ios/chrome/browser/ui/settings/password/password_issues_coordinator.h
@@ -7,10 +7,6 @@
 
 #import "ios/chrome/browser/ui/coordinators/chrome_coordinator.h"
 
-namespace password_manager {
-struct CredentialUIEntry;
-}  // namespace password_manager
-
 @protocol ApplicationCommands;
 class Browser;
 class IOSChromePasswordCheckManager;
@@ -24,11 +20,6 @@
 - (void)passwordIssuesCoordinatorDidRemove:
     (PasswordIssuesCoordinator*)coordinator;
 
-// Called when the user deleted password. Returns whether presenter will
-// handle it or not.
-- (BOOL)willHandlePasswordDeletion:
-    (const password_manager::CredentialUIEntry&)credential;
-
 @end
 
 // This coordinator presents a list of compromised credentials for the user.
diff --git a/ios/chrome/browser/ui/settings/password/password_issues_coordinator.mm b/ios/chrome/browser/ui/settings/password/password_issues_coordinator.mm
index 9c6251a6..4e7aea0 100644
--- a/ios/chrome/browser/ui/settings/password/password_issues_coordinator.mm
+++ b/ios/chrome/browser/ui/settings/password/password_issues_coordinator.mm
@@ -138,14 +138,4 @@
   self.passwordDetails = nil;
 }
 
-- (void)passwordDetailsCoordinator:(PasswordDetailsCoordinator*)coordinator
-                  deleteCredential:
-                      (const password_manager::CredentialUIEntry&)credential
-                 shouldDismissView:(BOOL)shouldDismiss {
-  if (![self.delegate willHandlePasswordDeletion:credential]) {
-    [self.mediator deleteCredential:credential];
-  }
-  [self.baseNavigationController popViewControllerAnimated:YES];
-}
-
 @end
diff --git a/ios/chrome/browser/ui/settings/password/password_issues_mediator.h b/ios/chrome/browser/ui/settings/password/password_issues_mediator.h
index bc10781..dca1c31d1 100644
--- a/ios/chrome/browser/ui/settings/password/password_issues_mediator.h
+++ b/ios/chrome/browser/ui/settings/password/password_issues_mediator.h
@@ -36,9 +36,6 @@
 
 @property(nonatomic, weak) id<PasswordIssuesConsumer> consumer;
 
-// Deletes password from the password store.
-- (void)deleteCredential:(const password_manager::CredentialUIEntry&)credential;
-
 @end
 
 #endif  // IOS_CHROME_BROWSER_UI_SETTINGS_PASSWORD_PASSWORD_ISSUES_MEDIATOR_H_
diff --git a/ios/chrome/browser/ui/settings/password/password_issues_mediator.mm b/ios/chrome/browser/ui/settings/password/password_issues_mediator.mm
index dc3d207..1bf26be7 100644
--- a/ios/chrome/browser/ui/settings/password/password_issues_mediator.mm
+++ b/ios/chrome/browser/ui/settings/password/password_issues_mediator.mm
@@ -9,23 +9,30 @@
 
 #import "base/memory/raw_ptr.h"
 #import "components/password_manager/core/browser/ui/insecure_credentials_manager.h"
+#import "components/password_manager/core/browser/ui/saved_passwords_presenter.h"
 #import "components/sync/driver/sync_service.h"
 #import "ios/chrome/browser/favicon/favicon_loader.h"
 #import "ios/chrome/browser/net/crurl.h"
+#import "ios/chrome/browser/passwords/ios_chrome_password_check_manager.h"
 #import "ios/chrome/browser/passwords/password_check_observer_bridge.h"
 #import "ios/chrome/browser/passwords/password_manager_util_ios.h"
 #import "ios/chrome/browser/ui/settings/password/password_issues_consumer.h"
+#import "ios/chrome/browser/ui/settings/password/saved_passwords_presenter_observer.h"
 #import "ios/chrome/common/ui/favicon/favicon_constants.h"
 
 #if !defined(__has_feature) || !__has_feature(objc_arc)
 #error "This file requires ARC support."
 #endif
 
-@interface PasswordIssuesMediator () <PasswordCheckObserver> {
+@interface PasswordIssuesMediator () <PasswordCheckObserver,
+                                      SavedPasswordsPresenterObserver> {
   raw_ptr<IOSChromePasswordCheckManager> _manager;
 
   std::unique_ptr<PasswordCheckObserverBridge> _passwordCheckObserver;
 
+  std::unique_ptr<SavedPasswordsPresenterObserverBridge>
+      _passwordsPresenterObserver;
+
   std::vector<password_manager::CredentialUIEntry> _insecureCredentials;
 
   // Object storing the time of the previous successful re-authentication.
@@ -57,12 +64,16 @@
     _manager = manager;
     _passwordCheckObserver =
         std::make_unique<PasswordCheckObserverBridge>(self, manager);
+    _passwordsPresenterObserver =
+        std::make_unique<SavedPasswordsPresenterObserverBridge>(
+            self, _manager->GetSavedPasswordsPresenter());
   }
   return self;
 }
 
 - (void)disconnect {
   _passwordCheckObserver.reset();
+  _passwordsPresenterObserver.reset();
 
   _manager = nullptr;
   _faviconLoader = nullptr;
@@ -76,13 +87,6 @@
   [self providePasswordsToConsumer];
 }
 
-- (void)deleteCredential:
-    (const password_manager::CredentialUIEntry&)credential {
-  _manager->GetSavedPasswordsPresenter()->RemoveCredential(credential);
-  // TODO:(crbug.com/1075494) - Update list of compromised passwords without
-  // awaiting insecureCredentialsDidChange.
-}
-
 #pragma mark - PasswordCheckObserver
 
 - (void)passwordCheckStateDidChange:(PasswordCheckState)state {
@@ -93,6 +97,12 @@
   [self providePasswordsToConsumer];
 }
 
+#pragma mark - SavedPasswordsPresenterObserver
+
+- (void)savedPasswordsDidChange {
+  [self providePasswordsToConsumer];
+}
+
 #pragma mark - Private Methods
 
 - (void)providePasswordsToConsumer {
diff --git a/ios/chrome/browser/ui/settings/password/password_issues_mediator_unittest.mm b/ios/chrome/browser/ui/settings/password/password_issues_mediator_unittest.mm
index b132831..344bbe8f 100644
--- a/ios/chrome/browser/ui/settings/password/password_issues_mediator_unittest.mm
+++ b/ios/chrome/browser/ui/settings/password/password_issues_mediator_unittest.mm
@@ -159,19 +159,6 @@
   EXPECT_NSEQ(@"example.com", password.website);
 }
 
-// Tests that mediator deletes password from the store.
-TEST_F(PasswordIssuesMediatorTest, TestPasswordDeletion) {
-  MakeTestPasswordIssue();
-  RunUntilIdle();
-
-  EXPECT_EQ(1u, [[consumer() passwords] count]);
-
-  auto password = store()->stored_passwords().at(kExampleCom).at(0);
-  [mediator() deleteCredential:password_manager::CredentialUIEntry(password)];
-  RunUntilIdle();
-  EXPECT_EQ(0u, [[consumer() passwords] count]);
-}
-
 // Tests that passwords are sorted properly.
 TEST_F(PasswordIssuesMediatorTest, TestPasswordSorting) {
   EXPECT_EQ(0u, [[consumer() passwords] count]);
diff --git a/ios/chrome/browser/ui/settings/password/password_manager_egtest.mm b/ios/chrome/browser/ui/settings/password/password_manager_egtest.mm
index 6b78d0a4a..2c57a2f 100644
--- a/ios/chrome/browser/ui/settings/password/password_manager_egtest.mm
+++ b/ios/chrome/browser/ui/settings/password/password_manager_egtest.mm
@@ -2855,11 +2855,10 @@
   OpenPasswordManager();
   OpenSettingsSubmenu();
 
-  [EarlGrey
-      selectElementWithMatcher:
-          grey_allOf(grey_accessibilityID(
-                         kPasswordSettingsAccountStorageSwitchTableViewId),
-                     grey_notVisible(), nil)];
+  [[EarlGrey selectElementWithMatcher:
+                 grey_accessibilityID(
+                     kPasswordSettingsAccountStorageSwitchTableViewId)]
+      assertWithMatcher:grey_nil()];
 }
 
 - (void)testAccountStorageSwitchShownIfSignedInAndFlagEnabled {
@@ -2875,32 +2874,32 @@
                     chrome_test_util::TableViewSwitchCell(
                         kPasswordSettingsAccountStorageSwitchTableViewId,
                         /*is_toggled_on=*/YES)];
+  [accountStorageSwitch assertWithMatcher:grey_sufficientlyVisible()];
   // The toggle text must contain the signed-in account.
-  [EarlGrey
-      selectElementWithMatcher:
-          grey_allOf(
-              grey_descendant(grey_accessibilityID(
-                  kPasswordSettingsAccountStorageSwitchTableViewId)),
-              ElementWithAccessibilityLabelSubstring(fakeIdentity.userEmail),
-              nil)];
+  [[EarlGrey
+      selectElementWithMatcher:grey_accessibilityLabel(l10n_util::GetNSStringF(
+                                   IDS_IOS_ACCOUNT_STORAGE_OPT_IN_SUBLABEL,
+                                   base::SysNSStringToUTF16(
+                                       fakeIdentity.userEmail)))]
+      assertWithMatcher:grey_sufficientlyVisible()];
 
   [accountStorageSwitch performAction:TurnTableViewSwitchOn(NO)];
 
-  [EarlGrey selectElementWithMatcher:
-                chrome_test_util::TableViewSwitchCell(
-                    kPasswordSettingsAccountStorageSwitchTableViewId,
-                    /*is_toggled_on=*/NO)];
+  [[EarlGrey selectElementWithMatcher:
+                 chrome_test_util::TableViewSwitchCell(
+                     kPasswordSettingsAccountStorageSwitchTableViewId,
+                     /*is_toggled_on=*/NO)]
+      assertWithMatcher:grey_sufficientlyVisible()];
 }
 
 - (void)testAccountStorageSwitchHiddenIfSignedOut {
   OpenPasswordManager();
   OpenSettingsSubmenu();
 
-  [EarlGrey
-      selectElementWithMatcher:
-          grey_allOf(grey_accessibilityID(
-                         kPasswordSettingsAccountStorageSwitchTableViewId),
-                     grey_notVisible(), nil)];
+  [[EarlGrey selectElementWithMatcher:
+                 grey_accessibilityID(
+                     kPasswordSettingsAccountStorageSwitchTableViewId)]
+      assertWithMatcher:grey_nil()];
 }
 
 - (void)testAccountStorageSwitchHiddenIfSyncing {
@@ -2912,11 +2911,10 @@
   OpenPasswordManager();
   OpenSettingsSubmenu();
 
-  [EarlGrey
-      selectElementWithMatcher:
-          grey_allOf(grey_accessibilityID(
-                         kPasswordSettingsAccountStorageSwitchTableViewId),
-                     grey_notVisible(), nil)];
+  [[EarlGrey selectElementWithMatcher:
+                 grey_accessibilityID(
+                     kPasswordSettingsAccountStorageSwitchTableViewId)]
+      assertWithMatcher:grey_nil()];
 }
 
 @end
diff --git a/ios/chrome/browser/ui/settings/password/passwords_coordinator.mm b/ios/chrome/browser/ui/settings/password/passwords_coordinator.mm
index 3db3b054..52e07eb 100644
--- a/ios/chrome/browser/ui/settings/password/passwords_coordinator.mm
+++ b/ios/chrome/browser/ui/settings/password/passwords_coordinator.mm
@@ -315,12 +315,6 @@
   self.passwordIssuesCoordinator = nil;
 }
 
-- (BOOL)willHandlePasswordDeletion:
-    (const password_manager::CredentialUIEntry&)credential {
-  [self.mediator deleteCredential:credential];
-  return YES;
-}
-
 #pragma mark - PasswordCheckupCoordinatorDelegate
 
 - (void)passwordCheckupCoordinatorDidRemove:
@@ -341,21 +335,6 @@
   self.passwordDetailsCoordinator = nil;
 }
 
-- (void)passwordDetailsCoordinator:(PasswordDetailsCoordinator*)coordinator
-                  deleteCredential:
-                      (const password_manager::CredentialUIEntry&)credential
-                 shouldDismissView:(BOOL)shouldDismiss {
-  DCHECK_EQ(self.passwordDetailsCoordinator, coordinator);
-  [self.mediator deleteCredential:credential];
-
-  if (shouldDismiss) {
-    [self.baseNavigationController popViewControllerAnimated:YES];
-  } else {
-    [self.passwordDetailsCoordinator
-        removeCredentialFromCacheAndRefreshTableView:credential];
-  }
-}
-
 #pragma mark AddPasswordDetailsCoordinatorDelegate
 
 - (void)passwordDetailsTableViewControllerDidFinish:
diff --git a/ios/chrome/browser/ui/settings/password/passwords_mediator.h b/ios/chrome/browser/ui/settings/password/passwords_mediator.h
index 9258a23e..6d2be2a0 100644
--- a/ios/chrome/browser/ui/settings/password/passwords_mediator.h
+++ b/ios/chrome/browser/ui/settings/password/passwords_mediator.h
@@ -41,9 +41,6 @@
 
 - (instancetype)init NS_UNAVAILABLE;
 
-// Deletes 'form' and its duplicates.
-- (void)deleteCredential:(const password_manager::CredentialUIEntry&)credential;
-
 // Disconnect the observers.
 - (void)disconnect;
 
diff --git a/ios/chrome/browser/ui/settings/password/passwords_mediator.mm b/ios/chrome/browser/ui/settings/password/passwords_mediator.mm
index 45186aa4..43e2b6ed 100644
--- a/ios/chrome/browser/ui/settings/password/passwords_mediator.mm
+++ b/ios/chrome/browser/ui/settings/password/passwords_mediator.mm
@@ -143,11 +143,6 @@
   [self updateConsumerPasswordCheckState:_currentState];
 }
 
-- (void)deleteCredential:
-    (const password_manager::CredentialUIEntry&)credential {
-  _savedPasswordsPresenter->RemoveCredential(credential);
-}
-
 - (void)disconnect {
   _identityManagerObserver.reset();
   _syncObserver.reset();
diff --git a/ios/chrome/browser/ui/settings/password/passwords_mediator_unittest.mm b/ios/chrome/browser/ui/settings/password/passwords_mediator_unittest.mm
index d83374d9..2bc1d85 100644
--- a/ios/chrome/browser/ui/settings/password/passwords_mediator_unittest.mm
+++ b/ios/chrome/browser/ui/settings/password/passwords_mediator_unittest.mm
@@ -209,23 +209,6 @@
   EXPECT_THAT([consumer() passwords], testing::IsEmpty());
 }
 
-// Duplicates of a form should be removed as well.
-TEST_F(PasswordsMediatorTest, DeleteFormWithDuplicates) {
-  PasswordForm form = CreatePasswordForm();
-  PasswordForm duplicate = form;
-  duplicate.username_element = u"element";
-
-  store()->AddLogin(form);
-  store()->AddLogin(duplicate);
-  RunUntilIdle();
-  ASSERT_THAT([consumer() passwords],
-              testing::ElementsAre(password_manager::CredentialUIEntry(form)));
-
-  [mediator() deleteCredential:password_manager::CredentialUIEntry(form)];
-  RunUntilIdle();
-  EXPECT_THAT([consumer() passwords], testing::IsEmpty());
-}
-
 // Mediator should update consumer password autofill state.
 TEST_F(PasswordsMediatorTest, TestPasswordAutoFillDidChangeToStatusMethod) {
   ASSERT_EQ([consumer() detailedText], nil);
diff --git a/ios/chrome/browser/ui/settings/safety_check/safety_check_coordinator.mm b/ios/chrome/browser/ui/settings/safety_check/safety_check_coordinator.mm
index bdb4728..2fd1b9a 100644
--- a/ios/chrome/browser/ui/settings/safety_check/safety_check_coordinator.mm
+++ b/ios/chrome/browser/ui/settings/safety_check/safety_check_coordinator.mm
@@ -252,11 +252,6 @@
   self.passwordIssuesCoordinator = nil;
 }
 
-- (BOOL)willHandlePasswordDeletion:
-    (const password_manager::CredentialUIEntry&)credential {
-  return NO;
-}
-
 #pragma mark - PrivacySafeBrowsingCoordinatorDelegate
 
 - (void)privacySafeBrowsingCoordinatorDidRemove:
diff --git a/ios/chrome/browser/ui/tab_switcher/tab_grid/inactive_tabs/BUILD.gn b/ios/chrome/browser/ui/tab_switcher/tab_grid/inactive_tabs/BUILD.gn
index 2b67f27..1cd9894 100644
--- a/ios/chrome/browser/ui/tab_switcher/tab_grid/inactive_tabs/BUILD.gn
+++ b/ios/chrome/browser/ui/tab_switcher/tab_grid/inactive_tabs/BUILD.gn
@@ -13,14 +13,18 @@
   deps = [
     ":inactive_tabs_ui",
     "//base",
+    "//components/favicon/ios",
     "//ios/chrome/browser/main:public",
+    "//ios/chrome/browser/snapshots",
     "//ios/chrome/browser/tabs/inactive_tabs:features",
     "//ios/chrome/browser/ui/coordinators:chrome_coordinators",
     "//ios/chrome/browser/ui/tab_switcher",
     "//ios/chrome/browser/ui/tab_switcher:tab_utils",
     "//ios/chrome/browser/ui/tab_switcher/tab_grid/grid:grid_ui",
+    "//ios/chrome/browser/url",
     "//ios/chrome/browser/web_state_list",
     "//ios/web/public",
+    "//ui/gfx",
   ]
 }
 
diff --git a/ios/chrome/browser/ui/tab_switcher/tab_grid/inactive_tabs/inactive_tabs_coordinator.mm b/ios/chrome/browser/ui/tab_switcher/tab_grid/inactive_tabs/inactive_tabs_coordinator.mm
index eeeaaa3..33d81db 100644
--- a/ios/chrome/browser/ui/tab_switcher/tab_grid/inactive_tabs/inactive_tabs_coordinator.mm
+++ b/ios/chrome/browser/ui/tab_switcher/tab_grid/inactive_tabs/inactive_tabs_coordinator.mm
@@ -59,6 +59,7 @@
   self.mediator = [[InactiveTabsMediator alloc]
       initWithConsumer:self.viewController.gridViewController];
   self.mediator.inactiveBrowser = self.browser;
+  self.viewController.gridViewController.imageDataSource = self.mediator;
 
   // Add the view controller to the hierarchy.
   UIView* baseView = self.baseViewController.view;
diff --git a/ios/chrome/browser/ui/tab_switcher/tab_grid/inactive_tabs/inactive_tabs_mediator.h b/ios/chrome/browser/ui/tab_switcher/tab_grid/inactive_tabs/inactive_tabs_mediator.h
index fe1c255..6b8d97e 100644
--- a/ios/chrome/browser/ui/tab_switcher/tab_grid/inactive_tabs/inactive_tabs_mediator.h
+++ b/ios/chrome/browser/ui/tab_switcher/tab_grid/inactive_tabs/inactive_tabs_mediator.h
@@ -7,10 +7,12 @@
 
 #import <Foundation/Foundation.h>
 
+#import "ios/chrome/browser/ui/tab_switcher/tab_grid/grid/grid_image_data_source.h"
+
 class Browser;
 @protocol TabCollectionConsumer;
 
-@interface InactiveTabsMediator : NSObject
+@interface InactiveTabsMediator : NSObject <GridImageDataSource>
 
 // The inactive browser reference.
 @property(nonatomic, assign) Browser* inactiveBrowser;
diff --git a/ios/chrome/browser/ui/tab_switcher/tab_grid/inactive_tabs/inactive_tabs_mediator.mm b/ios/chrome/browser/ui/tab_switcher/tab_grid/inactive_tabs/inactive_tabs_mediator.mm
index 0c36e49e..f4b470b 100644
--- a/ios/chrome/browser/ui/tab_switcher/tab_grid/inactive_tabs/inactive_tabs_mediator.mm
+++ b/ios/chrome/browser/ui/tab_switcher/tab_grid/inactive_tabs/inactive_tabs_mediator.mm
@@ -6,10 +6,16 @@
 
 #import "base/notreached.h"
 #import "base/scoped_multi_source_observation.h"
+#import "components/favicon/ios/web_favicon_driver.h"
 #import "ios/chrome/browser/main/browser.h"
+#import "ios/chrome/browser/snapshots/snapshot_browser_agent.h"
+#import "ios/chrome/browser/snapshots/snapshot_cache.h"
+#import "ios/chrome/browser/snapshots/snapshot_cache_observer.h"
+#import "ios/chrome/browser/snapshots/snapshot_tab_helper.h"
 #import "ios/chrome/browser/tabs/inactive_tabs/features.h"
 #import "ios/chrome/browser/ui/tab_switcher/tab_collection_consumer.h"
 #import "ios/chrome/browser/ui/tab_switcher/tab_utils.h"
+#import "ios/chrome/browser/url/url_util.h"
 #import "ios/chrome/browser/web_state_list/web_state_list.h"
 #import "ios/chrome/browser/web_state_list/web_state_list_observer_bridge.h"
 #import "ios/web/public/web_state.h"
@@ -25,6 +31,7 @@
     base::ScopedMultiSourceObservation<web::WebState, web::WebStateObserver>;
 
 @interface InactiveTabsMediator () <CRWWebStateObserver,
+                                    SnapshotCacheObserver,
                                     WebStateListObserving> {
   // Observers for WebStateList.
   std::unique_ptr<WebStateListObserverBridge> _webStateListObserverBridge;
@@ -38,6 +45,11 @@
 @property(nonatomic, weak, readonly) id<TabCollectionConsumer> consumer;
 // The list of inactive tabs.
 @property(nonatomic, assign, readonly) WebStateList* webStateList;
+// The snapshot cache of `webStateList`.
+@property(nonatomic, weak, readonly) SnapshotCache* snapshotCache;
+// The short-term cache for grid thumbnails.
+@property(nonatomic, strong, readonly)
+    NSMutableDictionary<NSString*, UIImage*>* appearanceCache;
 
 @end
 
@@ -56,19 +68,26 @@
         std::make_unique<web::WebStateObserverBridge>(self);
     _scopedWebStateObservation = std::make_unique<ScopedWebStateObservation>(
         _webStateObserverBridge.get());
+    _appearanceCache = [[NSMutableDictionary alloc] init];
   }
   return self;
 }
 
+- (void)dealloc {
+  [_snapshotCache removeObserver:self];
+}
+
 #pragma mark - Public properties
 
 - (void)setInactiveBrowser:(Browser*)inactiveBrowser {
+  [_snapshotCache removeObserver:self];
   _scopedWebStateListObservation->RemoveAllObservations();
   _scopedWebStateObservation->RemoveAllObservations();
 
   _inactiveBrowser = inactiveBrowser;
   _webStateList = inactiveBrowser->GetWebStateList();
 
+  [_snapshotCache addObserver:self];
   if (_webStateList) {
     _scopedWebStateListObservation->AddObservation(_webStateList);
     [self addWebStateObservations];
@@ -95,6 +114,86 @@
                   withItem:GetTabSwitcherItem(webState)];
 }
 
+#pragma mark - GridImageDataSource
+
+- (void)snapshotForIdentifier:(NSString*)identifier
+                   completion:(void (^)(UIImage*))completion {
+  if (_appearanceCache[identifier]) {
+    completion(_appearanceCache[identifier]);
+    return;
+  }
+  web::WebState* webState =
+      GetWebState(_webStateList, identifier, /*pinned=*/NO);
+  if (webState) {
+    SnapshotTabHelper::FromWebState(webState)->RetrieveColorSnapshot(
+        ^(UIImage* image) {
+          completion(image);
+        });
+  }
+}
+
+- (void)faviconForIdentifier:(NSString*)identifier
+                  completion:(void (^)(UIImage*))completion {
+  web::WebState* webState =
+      GetWebState(_webStateList, identifier, /*pinned=*/NO);
+  if (!webState) {
+    return;
+  }
+  // NTP tabs get no favicon.
+  if (IsURLNtp(webState->GetVisibleURL())) {
+    return;
+  }
+  completion([UIImage imageNamed:@"default_world_favicon_regular"]);
+
+  favicon::FaviconDriver* faviconDriver =
+      favicon::WebFaviconDriver::FromWebState(webState);
+  if (faviconDriver) {
+    gfx::Image favicon = faviconDriver->GetFavicon();
+    if (!favicon.IsEmpty()) {
+      completion(favicon.ToUIImage());
+    }
+  }
+}
+
+- (void)preloadSnapshotsForVisibleGridItems:
+    (NSSet<NSString*>*)visibleGridItems {
+  for (int i = 0; i <= self.webStateList->count() - 1; i++) {
+    web::WebState* web_state = _webStateList->GetWebStateAt(i);
+    NSString* identifier = web_state->GetStableIdentifier();
+
+    BOOL isWebStateHidden = ![visibleGridItems containsObject:identifier];
+    if (isWebStateHidden) {
+      continue;
+    }
+
+    __weak __typeof(self) weakSelf = self;
+    auto cacheImage = ^(UIImage* image) {
+      weakSelf.appearanceCache[identifier] = image;
+    };
+
+    [self snapshotForIdentifier:identifier completion:cacheImage];
+  }
+}
+
+- (void)clearPreloadedSnapshots {
+  [_appearanceCache removeAllObjects];
+}
+
+#pragma mark - SnapshotCacheObserver
+
+- (void)snapshotCache:(SnapshotCache*)snapshotCache
+    didUpdateSnapshotForIdentifier:(NSString*)identifier {
+  [_appearanceCache removeObjectForKey:identifier];
+  web::WebState* webState =
+      GetWebState(_webStateList, identifier, /*pinned=*/NO);
+  if (webState) {
+    // It is possible to observe an updated snapshot for a WebState before
+    // observing that the WebState has been added to the WebStateList. It is the
+    // consumer's responsibility to ignore any updates before inserts.
+    [_consumer replaceItemID:identifier withItem:GetTabSwitcherItem(webState)];
+  }
+}
+
 #pragma mark - WebStateListObserving
 
 - (void)webStateList:(WebStateList*)webStateList
diff --git a/ios/web_view/BUILD.gn b/ios/web_view/BUILD.gn
index a1bbbcf..7be5eeee 100644
--- a/ios/web_view/BUILD.gn
+++ b/ios/web_view/BUILD.gn
@@ -333,6 +333,7 @@
     "//components/services/unzip:in_process",
     "//components/signin/core/browser",
     "//components/signin/ios/browser",
+    "//components/signin/public/base",
     "//components/signin/public/identity_manager",
     "//components/signin/public/identity_manager/ios",
     "//components/signin/public/webdata",
diff --git a/ios/web_view/internal/sync/cwv_trusted_vault_utils.mm b/ios/web_view/internal/sync/cwv_trusted_vault_utils.mm
index 34ac7e2..cbfd6ac 100644
--- a/ios/web_view/internal/sync/cwv_trusted_vault_utils.mm
+++ b/ios/web_view/internal/sync/cwv_trusted_vault_utils.mm
@@ -30,8 +30,9 @@
       return syncer::TrustedVaultDeviceRegistrationStateForUMA::
           kAttemptingRegistrationWithExistingKeyPair;
     case CWVTrustedVaultStateAttemptingRegistrationWithPersistentAuthError:
+      // TODO(crbug.com/1418027): remove CWV version of this bucket.
       return syncer::TrustedVaultDeviceRegistrationStateForUMA::
-          kAttemptingRegistrationWithPersistentAuthError;
+          kDeprecatedAttemptingRegistrationWithPersistentAuthError;
     case CWVTrustedVaultStateAlreadyRegisteredV1:
       return syncer::TrustedVaultDeviceRegistrationStateForUMA::
           kAlreadyRegisteredV1;
diff --git a/ios/web_view/internal/web_view_web_main_parts.mm b/ios/web_view/internal/web_view_web_main_parts.mm
index 714e16ba..03284c6 100644
--- a/ios/web_view/internal/web_view_web_main_parts.mm
+++ b/ios/web_view/internal/web_view_web_main_parts.mm
@@ -13,6 +13,7 @@
 #include "components/autofill/core/common/autofill_payments_features.h"
 #include "components/component_updater/installer_policies/safety_tips_component_installer.h"
 #include "components/password_manager/core/common/password_manager_features.h"
+#import "components/signin/public/base/signin_switches.h"
 #include "components/sync/base/features.h"
 #include "components/variations/variations_ids_provider.h"
 #include "ios/web/public/webui/web_ui_ios_controller_factory.h"
@@ -76,6 +77,7 @@
       ",");
   std::string disabled_features = base::JoinString(
       {
+          switches::kEnableFetchingAccountCapabilities.name,
       },
       ",");
   feature_list->InitializeFromCommandLine(
diff --git a/media/base/android/java/src/org/chromium/media/VideoEncodeAcceleratorUtil.java b/media/base/android/java/src/org/chromium/media/VideoEncodeAcceleratorUtil.java
index 4acee26..9dae41b 100644
--- a/media/base/android/java/src/org/chromium/media/VideoEncodeAcceleratorUtil.java
+++ b/media/base/android/java/src/org/chromium/media/VideoEncodeAcceleratorUtil.java
@@ -351,9 +351,7 @@
                             supportedProfiles.add(VideoCodecProfile.H264PROFILE_MAIN);
                             break;
                         case VideoCodec.AV1:
-                            if (isAtLeastQ) {
-                                supportedProfiles.add(VideoCodecProfile.AV1PROFILE_PROFILE_MAIN);
-                            }
+                            supportedProfiles.add(VideoCodecProfile.AV1PROFILE_PROFILE_MAIN);
                             break;
                     }
                 }
diff --git a/media/cast/logging/stats_event_subscriber.cc b/media/cast/logging/stats_event_subscriber.cc
index 3a6dd99..490294d 100644
--- a/media/cast/logging/stats_event_subscriber.cc
+++ b/media/cast/logging/stats_event_subscriber.cc
@@ -237,7 +237,9 @@
     stats.Set(CastStatToString(it->first), it->second->GetHistogram());
   }
 
-  ret.Set(event_media_type_ == AUDIO_EVENT ? "audio" : "video",
+  ret.Set(event_media_type_ == AUDIO_EVENT
+              ? StatsEventSubscriber::kAudioStatsDictKey
+              : StatsEventSubscriber::kVideoStatsDictKey,
           std::move(stats));
 
   return ret;
diff --git a/media/cast/logging/stats_event_subscriber.h b/media/cast/logging/stats_event_subscriber.h
index fd095b35..c5a7ff0 100644
--- a/media/cast/logging/stats_event_subscriber.h
+++ b/media/cast/logging/stats_event_subscriber.h
@@ -55,7 +55,12 @@
   // Resets stats in this object.
   void Reset();
 
+  static constexpr char kAudioStatsDictKey[] = "audio";
+  static constexpr char kVideoStatsDictKey[] = "video";
+
  private:
+  // TODO(b/268543775): Replace friend class declarations with public getters
+  // for tests.
   friend class StatsEventSubscriberTest;
   FRIEND_TEST_ALL_PREFIXES(StatsEventSubscriberTest, EmptyStats);
   FRIEND_TEST_ALL_PREFIXES(StatsEventSubscriberTest, CaptureEncode);
diff --git a/media/gpu/android/media_codec_video_decoder_unittest.cc b/media/gpu/android/media_codec_video_decoder_unittest.cc
index c058ecc7..4d2d2dc 100644
--- a/media/gpu/android/media_codec_video_decoder_unittest.cc
+++ b/media/gpu/android/media_codec_video_decoder_unittest.cc
@@ -31,6 +31,7 @@
 #include "media/gpu/android/fake_codec_allocator.h"
 #include "media/gpu/android/mock_android_video_surface_chooser.h"
 #include "media/gpu/android/mock_device_info.h"
+#include "media/gpu/android/video_accelerator_util.h"
 #include "media/gpu/android/video_frame_factory.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
@@ -57,6 +58,22 @@
   return nullptr;
 }
 
+// Tests require the presence of a software AV1 decoder, which isn't required
+// by Android at this time.
+bool HasAv1Decoder() {
+  if (!MediaCodecUtil::IsAv1DecoderAvailable()) {
+    return false;
+  }
+
+  for (const auto& info : GetDecoderInfoCache()) {
+    if (info.profile >= AV1PROFILE_MIN && info.profile <= AV1PROFILE_MAX) {
+      return true;
+    }
+  }
+
+  return false;
+}
+
 }  // namespace
 
 class MockVideoFrameFactory : public VideoFrameFactory {
@@ -294,9 +311,9 @@
   scoped_refptr<DecoderBuffer> fake_decoder_buffer_;
   std::unique_ptr<MockDeviceInfo> device_info_;
   std::unique_ptr<FakeCodecAllocator> codec_allocator_;
-  raw_ptr<MockAndroidVideoSurfaceChooser> surface_chooser_;
-  raw_ptr<gpu::MockTextureOwner> texture_owner_;
-  raw_ptr<MockVideoFrameFactory> video_frame_factory_;
+  raw_ptr<MockAndroidVideoSurfaceChooser> surface_chooser_ = nullptr;
+  raw_ptr<gpu::MockTextureOwner> texture_owner_ = nullptr;
+  raw_ptr<MockVideoFrameFactory> video_frame_factory_ = nullptr;
   NiceMock<base::MockCallback<VideoDecoder::DecodeCB>> decode_cb_;
   ProvideOverlayInfoCB provide_overlay_info_cb_;
   bool restart_for_transitions_;
@@ -339,6 +356,9 @@
 }
 
 TEST_P(MediaCodecVideoDecoderAV1Test, Av1IsSupported) {
+  if (!HasAv1Decoder()) {
+    return;
+  }
   EXPECT_CALL(*device_info_, IsAv1DecoderAvailable()).WillOnce(Return(true));
   ASSERT_TRUE(Initialize(TestVideoConfig::Normal(VideoCodec::kAV1)));
 }
@@ -1039,8 +1059,9 @@
     test_codecs.push_back(VideoCodec::kVP8);
   if (MediaCodecUtil::IsVp9DecoderAvailable())
     test_codecs.push_back(VideoCodec::kVP9);
-  if (MediaCodecUtil::IsAv1DecoderAvailable())
+  if (HasAv1Decoder()) {
     test_codecs.push_back(VideoCodec::kAV1);
+  }
   return test_codecs;
 }
 
@@ -1065,9 +1086,8 @@
 // }
 
 static std::vector<VideoCodec> GetAv1IfAvailable() {
-  return MediaCodecUtil::IsAv1DecoderAvailable()
-             ? std::vector<VideoCodec>(1, VideoCodec::kAV1)
-             : std::vector<VideoCodec>();
+  return HasAv1Decoder() ? std::vector<VideoCodec>(1, VideoCodec::kAV1)
+                         : std::vector<VideoCodec>();
 }
 
 INSTANTIATE_TEST_SUITE_P(MediaCodecVideoDecoderTest,
diff --git a/media/gpu/android/video_accelerator_util.cc b/media/gpu/android/video_accelerator_util.cc
index 970e6da2..a6a8431 100644
--- a/media/gpu/android/video_accelerator_util.cc
+++ b/media/gpu/android/video_accelerator_util.cc
@@ -77,8 +77,6 @@
     CHECK(env);
     auto java_profiles =
         Java_VideoEncodeAcceleratorUtil_getSupportedDecoderProfiles(env);
-    std::vector<MediaCodecDecoderInfo> cpp_infos;
-
     constexpr char kHasMediaCodecDecoderInfo[] =
         "Media.Android.MediaCodecInfo.HasDecoderInfo";
     if (!java_profiles) {
@@ -93,27 +91,19 @@
       constexpr auto kDefaultSize = gfx::Size(4096, 4096);
       constexpr auto kSoftwareCodec = true;
       constexpr auto kHardwareCodec = false;
-      cpp_infos.insert(
-          cpp_infos.end(),
-          {
-              {H264PROFILE_BASELINE, gfx::Size(), kDefaultSize, kHardwareCodec},
-              {H264PROFILE_MAIN, gfx::Size(), kDefaultSize, kHardwareCodec},
-              {H264PROFILE_HIGH, gfx::Size(), kDefaultSize, kHardwareCodec},
-              {HEVCPROFILE_MAIN, gfx::Size(), kDefaultSize, kHardwareCodec},
+      return std::vector<MediaCodecDecoderInfo>({
+          {H264PROFILE_BASELINE, gfx::Size(), kDefaultSize, kHardwareCodec},
+          {H264PROFILE_MAIN, gfx::Size(), kDefaultSize, kHardwareCodec},
+          {H264PROFILE_HIGH, gfx::Size(), kDefaultSize, kHardwareCodec},
+          {HEVCPROFILE_MAIN, gfx::Size(), kDefaultSize, kHardwareCodec},
 
-              // Report codecs as software where we have a bundled decoder.
-              {VP8PROFILE_ANY, gfx::Size(), kDefaultSize, kSoftwareCodec},
-              {VP9PROFILE_PROFILE0, gfx::Size(), kDefaultSize, kSoftwareCodec},
-          });
-
-      if (base::android::BuildInfo::GetInstance()->sdk_int() >=
-          base::android::SDK_VERSION_Q) {
-        cpp_infos.emplace_back(AV1PROFILE_PROFILE_MAIN, gfx::Size(),
-                               kDefaultSize, kSoftwareCodec);
-      }
-      return cpp_infos;
+          // Report codecs as software where we have a bundled decoder.
+          {VP8PROFILE_ANY, gfx::Size(), kDefaultSize, kSoftwareCodec},
+          {VP9PROFILE_PROFILE0, gfx::Size(), kDefaultSize, kSoftwareCodec},
+      });
     }
 
+    std::vector<MediaCodecDecoderInfo> cpp_infos;
     for (auto java_profile : java_profiles.ReadElements<jobject>()) {
       MediaCodecDecoderInfo info;
       info.profile = static_cast<VideoCodecProfile>(
diff --git a/media/gpu/video_encode_accelerator_perf_tests.cc b/media/gpu/video_encode_accelerator_perf_tests.cc
index b8ca24d..e8406a21 100644
--- a/media/gpu/video_encode_accelerator_perf_tests.cc
+++ b/media/gpu/video_encode_accelerator_perf_tests.cc
@@ -635,7 +635,7 @@
             get_model_frame_cb,
             /*corrupt_frame_processor=*/nullptr,
             VideoFrameValidator::ValidationMode::kAverage,
-            /*tolerance=*/0.0);
+            /*tolerance=*/100.0);
     LOG_ASSERT(log_likelihood_validator);
     quality_metrics_.push_back(BitstreamQualityMetrics(
         psnr_validator.get(), ssim_validator.get(),
diff --git a/net/base/features.cc b/net/base/features.cc
index 73d56b3..9a8a4ef 100644
--- a/net/base/features.cc
+++ b/net/base/features.cc
@@ -93,10 +93,6 @@
              "PartitionConnectionsByNetworkIsolationKey",
              base::FEATURE_DISABLED_BY_DEFAULT);
 
-BASE_FEATURE(kForceIsolationInfoFrameOriginToTopLevelFrame,
-             "ForceIsolationInfoFrameOriginToTopLevelFrame",
-             base::FEATURE_DISABLED_BY_DEFAULT);
-
 BASE_FEATURE(kPartitionHttpServerPropertiesByNetworkIsolationKey,
              "PartitionHttpServerPropertiesByNetworkIsolationKey",
              base::FEATURE_DISABLED_BY_DEFAULT);
diff --git a/net/base/features.h b/net/base/features.h
index 25f94ac9..3ee5b6c 100644
--- a/net/base/features.h
+++ b/net/base/features.h
@@ -114,11 +114,6 @@
 // request.
 NET_EXPORT BASE_DECLARE_FEATURE(kPartitionConnectionsByNetworkIsolationKey);
 
-// Forces the `frame_origin` value in IsolationInfo to the `top_level_origin`
-// value when an IsolationInfo instance is created. This is to enable
-// expirimenting with double keyed network partitions.
-NET_EXPORT BASE_DECLARE_FEATURE(kForceIsolationInfoFrameOriginToTopLevelFrame);
-
 // Partitions HttpServerProperties based on the NetworkIsolationKey associated
 // with a request.
 NET_EXPORT BASE_DECLARE_FEATURE(
diff --git a/net/base/isolation_info.cc b/net/base/isolation_info.cc
index 52cd760..668610a 100644
--- a/net/base/isolation_info.cc
+++ b/net/base/isolation_info.cc
@@ -155,18 +155,6 @@
       std::move(party_context), nullptr);
 }
 
-IsolationInfo IsolationInfo::CreateDoubleKey(
-    RequestType request_type,
-    const url::Origin& top_frame_origin,
-    const SiteForCookies& site_for_cookies,
-    absl::optional<std::set<SchemefulSite>> party_context,
-    const base::UnguessableToken* nonce) {
-  // This should only be used when the frame site is disabled for double keying.
-  DCHECK(!IsFrameSiteEnabled());
-  return IsolationInfo(request_type, top_frame_origin, absl::nullopt,
-                       site_for_cookies, nonce, std::move(party_context));
-}
-
 IsolationInfo IsolationInfo::Create(
     RequestType request_type,
     const url::Origin& top_frame_origin,
@@ -305,8 +293,9 @@
 }
 
 bool IsolationInfo::IsFrameSiteEnabled() {
-  return !base::FeatureList::IsEnabled(
-      net::features::kForceIsolationInfoFrameOriginToTopLevelFrame);
+  // NIKs, and thus IsolationInfo's, are currently always triple-keyed, but we
+  // will experiment with 2.5-keying in crbug.com/1414808.
+  return true;
 }
 
 std::string IsolationInfo::DebugString() const {
diff --git a/net/base/isolation_info.h b/net/base/isolation_info.h
index 6fa7c4a..f2c52b0 100644
--- a/net/base/isolation_info.h
+++ b/net/base/isolation_info.h
@@ -121,16 +121,6 @@
       absl::optional<std::set<SchemefulSite>> party_context = absl::nullopt,
       const base::UnguessableToken* nonce = nullptr);
 
-  // Create and IsolationInfo from the context of a double key. This should only
-  // be used when we don't have access to the frame_origin because the
-  // IsolationInfo is being created from an existing double keyed IsolationInfo.
-  static IsolationInfo CreateDoubleKey(
-      RequestType request_type,
-      const url::Origin& top_frame_origin,
-      const SiteForCookies& site_for_cookies,
-      absl::optional<std::set<SchemefulSite>> party_context = absl::nullopt,
-      const base::UnguessableToken* nonce = nullptr);
-
   // TODO(crbug/1372769): Remove this and create a safer way to ensure NIKs
   // created from NAKs aren't used by accident.
   static IsolationInfo DoNotUseCreatePartialFromNak(
@@ -195,9 +185,7 @@
   //          policy. It MUST NEVER be used for any kind of SECURITY check.
   const SiteForCookies& site_for_cookies() const { return site_for_cookies_; }
 
-  // Do not use outside of testing. Returns the `frame_origin_` if
-  // `kForceIsolationInfoFrameOriginToTopLevelFrame` is disabled. Else it
-  // returns the `top_frame_origin_` value.
+  // Do not use outside of testing. Returns the `frame_origin_`.
   const absl::optional<url::Origin>& frame_origin_for_testing() const;
 
   // Return |party_context| which exclude the top frame origin and the frame
diff --git a/net/base/isolation_info_unittest.cc b/net/base/isolation_info_unittest.cc
index d8802c3..3d26824 100644
--- a/net/base/isolation_info_unittest.cc
+++ b/net/base/isolation_info_unittest.cc
@@ -24,34 +24,20 @@
 namespace net {
 
 // `IsolationInfoEnabledFeatureFlagsTestingParam ` allows enabling and disabling
-// the feature flags that control the key schemes for IsolationInfo,
-// NetworkIsolationKey and Network AnonymizationKey. This allows us to test the
-// possible combinations of flags that will be allowed for experimentation.
-// Those possible combinations are outlined below. When a property is `true` the
-// flag that enables this scheme will be enabled for testing. When the bool
-// parameter is `false` the flag that enables the scheme will be disabled.
+// the feature flags that control the key schemes for NetworkAnonymizationKey.
+// This allows us to test the possible combinations of flags that will be
+// allowed for experimentation.
+//
+// Presently, only one flag is used, but future experiments will add more.
 struct IsolationInfoEnabledFeatureFlagsTestingParam {
-  const bool enableDoubleKeyIsolationInfo;
   const bool enableDoubleKeyAndCrossSiteBitNetworkAnonymizationKey;
 };
 
-// The three cases we need to account for:
-//    0. Double-keying is enabled for both IsolationInfo and
-//    NetworkAnonymizationKey.
-//    1. Triple-keying is enabled for IsolationInfo and double-keying is enabled
-//    for NetworkAnonymizationKey.
-//    2. Triple-keying is enabled for IsolationInfo and double-keying +
-//    cross-site-bit is enabled for NetworkAnonymizationKey.
-// Note: At the current time double-keyed IsolationInfo is only supported when
-// double-keying or double-keying + is cross site bit are enabled for
-// NetworkAnonymizationKey.
 const IsolationInfoEnabledFeatureFlagsTestingParam kFlagsParam[] = {
-    {/*enableDoubleKeyIsolationInfo=*/true,
-     /*enableDoubleKeyAndCrossSiteBitNetworkAnonymizationKey=*/false},
-    {/*enableDoubleKeyIsolationInfo=*/false,
-     /*enableDoubleKeyAndCrossSiteBitNetworkAnonymizationKey=*/false},
-    {/*enableDoubleKeyIsolationInfo=*/false,
-     /*enableDoubleKeyAndCrossSiteBitNetworkAnonymizationKey=*/true}};
+    // 0. Double-keying is enabled for NetworkAnonymizationKey.
+    {/*enableDoubleKeyAndCrossSiteBitNetworkAnonymizationKey=*/false},
+    // 1. Double-keying + cross-site-bit is enabled for NetworkAnonymizationKey.
+    {/*enableDoubleKeyAndCrossSiteBitNetworkAnonymizationKey=*/true}};
 
 namespace {
 
@@ -63,14 +49,6 @@
     std::vector<base::test::FeatureRef> enabled_features = {};
     std::vector<base::test::FeatureRef> disabled_features = {};
 
-    if (IsDoubleKeyIsolationInfoEnabled()) {
-      enabled_features.push_back(
-          net::features::kForceIsolationInfoFrameOriginToTopLevelFrame);
-    } else {
-      disabled_features.push_back(
-          net::features::kForceIsolationInfoFrameOriginToTopLevelFrame);
-    }
-
     if (IsDoubleKeyAndCrossSiteBitNetworkAnonymizationKeyEnabled()) {
       enabled_features.push_back(
           net::features::kEnableCrossSiteFlagNetworkAnonymizationKey);
@@ -82,10 +60,6 @@
     scoped_feature_list_.InitWithFeatures(enabled_features, disabled_features);
   }
 
-  static bool IsDoubleKeyIsolationInfoEnabled() {
-    return GetParam().enableDoubleKeyIsolationInfo;
-  }
-
   static bool IsDoubleKeyAndCrossSiteBitNetworkAnonymizationKeyEnabled() {
     return GetParam().enableDoubleKeyAndCrossSiteBitNetworkAnonymizationKey;
   }
@@ -139,11 +113,7 @@
 }
 
 TEST_P(IsolationInfoTest, IsFrameSiteEnabled) {
-  if (IsDoubleKeyIsolationInfoEnabled()) {
-    EXPECT_FALSE(IsolationInfo::IsFrameSiteEnabled());
-  } else {
-    EXPECT_TRUE(IsolationInfo::IsFrameSiteEnabled());
-  }
+  EXPECT_TRUE(IsolationInfo::IsFrameSiteEnabled());
 }
 
 TEST_P(IsolationInfoTest, DebugString) {
@@ -192,18 +162,10 @@
             kNonce1);
   EXPECT_EQ(isolation_info.nonce().value(), kNonce1);
 
-  if (!IsDoubleKeyAndCrossSiteBitNetworkAnonymizationKeyEnabled() &&
-      IsDoubleKeyIsolationInfoEnabled()) {
-    // Double-keyed IsolationInfo + double-keyed NetworkAnonymizationKey case.
-    EXPECT_EQ(kOrigin1, isolation_info.top_frame_origin());
-    EXPECT_DEATH_IF_SUPPORTED(isolation_info.frame_origin(), "");
-    EXPECT_EQ(absl::nullopt, isolation_info.frame_origin_for_testing());
-  } else if (!IsDoubleKeyIsolationInfoEnabled() &&
-             !IsDoubleKeyAndCrossSiteBitNetworkAnonymizationKeyEnabled()) {
+  if (!IsDoubleKeyAndCrossSiteBitNetworkAnonymizationKeyEnabled()) {
     // Triple-keyed IsolationInfo + double-keyed NetworkAnonymizationKey case.
     EXPECT_EQ(isolation_info.frame_origin(), kOrigin2);
-  } else if (!IsDoubleKeyIsolationInfoEnabled() &&
-             IsDoubleKeyAndCrossSiteBitNetworkAnonymizationKeyEnabled()) {
+  } else {
     // Triple-keyed IsolationInfo + double-keyed + cross site bit
     // NetworkAnonymizationKey case.
     EXPECT_EQ(isolation_info.frame_origin(), kOrigin2);
@@ -215,50 +177,6 @@
   }
 }
 
-TEST_P(IsolationInfoTest, CreateDoubleKey) {
-  IsolationInfo isolation_info = IsolationInfo::Create(
-      IsolationInfo::RequestType::kMainFrame, kOrigin1, kOrigin1,
-      SiteForCookies::FromOrigin(kOrigin1), kPartyContextEmpty);
-
-  if (IsDoubleKeyIsolationInfoEnabled()) {
-    IsolationInfo double_key_isolation_info = IsolationInfo::CreateDoubleKey(
-        IsolationInfo::RequestType::kMainFrame, kOrigin1,
-        SiteForCookies::FromOrigin(kOrigin1), kPartyContextEmpty);
-
-    EXPECT_EQ(IsolationInfo::RequestType::kMainFrame,
-              double_key_isolation_info.request_type());
-    EXPECT_EQ(kOrigin1, double_key_isolation_info.top_frame_origin());
-    EXPECT_DEATH_IF_SUPPORTED(double_key_isolation_info.frame_origin(), "");
-    EXPECT_EQ(absl::nullopt,
-              double_key_isolation_info.frame_origin_for_testing());
-    EXPECT_EQ(
-        "https://foo.test https://foo.test",
-        double_key_isolation_info.network_isolation_key().ToCacheKeyString());
-    EXPECT_EQ(kPartyContextEmpty, double_key_isolation_info.party_context());
-    EXPECT_FALSE(double_key_isolation_info.nonce().has_value());
-
-    // When double keying is enabled Create and CreateDoubleKey should
-    // create the same key.
-    EXPECT_EQ(isolation_info.top_frame_origin(),
-              double_key_isolation_info.top_frame_origin());
-    EXPECT_EQ(isolation_info.request_type(),
-              double_key_isolation_info.request_type());
-    EXPECT_EQ(isolation_info.network_isolation_key(),
-              double_key_isolation_info.network_isolation_key());
-    EXPECT_EQ(isolation_info.party_context(),
-              double_key_isolation_info.party_context());
-    EXPECT_EQ(isolation_info.nonce(), double_key_isolation_info.nonce());
-  } else {
-    // Creating double keyed IsolationInfos is not allowed when frame site is
-    // enabled.
-    EXPECT_DEATH_IF_SUPPORTED(
-        IsolationInfo::CreateDoubleKey(
-            IsolationInfo::RequestType::kMainFrame, kOrigin1,
-            SiteForCookies::FromOrigin(kOrigin1), kPartyContextEmpty),
-        "");
-  }
-}
-
 TEST_P(IsolationInfoTest, RequestTypeMainFrame) {
   IsolationInfo isolation_info = IsolationInfo::Create(
       IsolationInfo::RequestType::kMainFrame, kOrigin1, kOrigin1,
@@ -267,16 +185,9 @@
             isolation_info.request_type());
   EXPECT_EQ(kOrigin1, isolation_info.top_frame_origin());
 
-  if (IsDoubleKeyIsolationInfoEnabled()) {
-    EXPECT_DEATH_IF_SUPPORTED(isolation_info.frame_origin(), "");
-    EXPECT_EQ(absl::nullopt, isolation_info.frame_origin_for_testing());
-    EXPECT_EQ("https://foo.test https://foo.test",
-              isolation_info.network_isolation_key().ToCacheKeyString());
-  } else {
-    EXPECT_EQ(kOrigin1, isolation_info.frame_origin());
-    EXPECT_EQ("https://foo.test https://foo.test",
-              isolation_info.network_isolation_key().ToCacheKeyString());
-  }
+  EXPECT_EQ(kOrigin1, isolation_info.frame_origin());
+  EXPECT_EQ("https://foo.test https://foo.test",
+            isolation_info.network_isolation_key().ToCacheKeyString());
   EXPECT_TRUE(isolation_info.network_isolation_key().IsFullyPopulated());
   EXPECT_FALSE(isolation_info.network_isolation_key().IsTransient());
   EXPECT_TRUE(
@@ -291,25 +202,13 @@
   EXPECT_EQ(IsolationInfo::RequestType::kMainFrame,
             redirected_isolation_info.request_type());
   EXPECT_EQ(kOrigin3, redirected_isolation_info.top_frame_origin());
-  if (IsDoubleKeyIsolationInfoEnabled()) {
-    EXPECT_DEATH_IF_SUPPORTED(redirected_isolation_info.frame_origin(), "");
-    EXPECT_EQ(absl::nullopt,
-              redirected_isolation_info.frame_origin_for_testing());
-  } else {
-    EXPECT_EQ(kOrigin3, redirected_isolation_info.frame_origin());
-  }
+  EXPECT_EQ(kOrigin3, redirected_isolation_info.frame_origin());
   EXPECT_TRUE(
       redirected_isolation_info.network_isolation_key().IsFullyPopulated());
   EXPECT_FALSE(redirected_isolation_info.network_isolation_key().IsTransient());
-  if (IsDoubleKeyIsolationInfoEnabled()) {
-    EXPECT_EQ(
-        "https://baz.test https://baz.test",
-        redirected_isolation_info.network_isolation_key().ToCacheKeyString());
-  } else {
-    EXPECT_EQ(
-        "https://baz.test https://baz.test",
-        redirected_isolation_info.network_isolation_key().ToCacheKeyString());
-  }
+  EXPECT_EQ(
+      "https://baz.test https://baz.test",
+      redirected_isolation_info.network_isolation_key().ToCacheKeyString());
   EXPECT_TRUE(redirected_isolation_info.site_for_cookies().IsFirstParty(
       kOrigin3.GetURL()));
   EXPECT_EQ(kPartyContextEmpty, redirected_isolation_info.party_context());
@@ -323,16 +222,9 @@
   EXPECT_EQ(IsolationInfo::RequestType::kSubFrame,
             isolation_info.request_type());
   EXPECT_EQ(kOrigin1, isolation_info.top_frame_origin());
-  if (IsDoubleKeyIsolationInfoEnabled()) {
-    EXPECT_DEATH_IF_SUPPORTED(isolation_info.frame_origin(), "");
-    EXPECT_EQ(absl::nullopt, isolation_info.frame_origin_for_testing());
-    EXPECT_EQ("https://foo.test https://foo.test",
-              isolation_info.network_isolation_key().ToCacheKeyString());
-  } else {
-    EXPECT_EQ(kOrigin2, isolation_info.frame_origin());
-    EXPECT_EQ("https://foo.test https://bar.test",
-              isolation_info.network_isolation_key().ToCacheKeyString());
-  }
+  EXPECT_EQ(kOrigin2, isolation_info.frame_origin());
+  EXPECT_EQ("https://foo.test https://bar.test",
+            isolation_info.network_isolation_key().ToCacheKeyString());
   EXPECT_TRUE(isolation_info.network_isolation_key().IsFullyPopulated());
   EXPECT_FALSE(isolation_info.network_isolation_key().IsTransient());
   EXPECT_TRUE(
@@ -348,19 +240,10 @@
             redirected_isolation_info.request_type());
   EXPECT_EQ(kOrigin1, redirected_isolation_info.top_frame_origin());
 
-  if (IsDoubleKeyIsolationInfoEnabled()) {
-    EXPECT_DEATH_IF_SUPPORTED(redirected_isolation_info.frame_origin(), "");
-    EXPECT_EQ(absl::nullopt,
-              redirected_isolation_info.frame_origin_for_testing());
-    EXPECT_EQ(
-        "https://foo.test https://foo.test",
-        redirected_isolation_info.network_isolation_key().ToCacheKeyString());
-  } else {
-    EXPECT_EQ(kOrigin3, redirected_isolation_info.frame_origin());
-    EXPECT_EQ(
-        "https://foo.test https://baz.test",
-        redirected_isolation_info.network_isolation_key().ToCacheKeyString());
-  }
+  EXPECT_EQ(kOrigin3, redirected_isolation_info.frame_origin());
+  EXPECT_EQ(
+      "https://foo.test https://baz.test",
+      redirected_isolation_info.network_isolation_key().ToCacheKeyString());
   EXPECT_TRUE(
       redirected_isolation_info.network_isolation_key().IsFullyPopulated());
   EXPECT_FALSE(redirected_isolation_info.network_isolation_key().IsTransient());
@@ -377,12 +260,7 @@
   EXPECT_EQ(IsolationInfo::RequestType::kMainFrame,
             isolation_info.request_type());
   EXPECT_EQ(kOrigin1, isolation_info.top_frame_origin());
-  if (IsDoubleKeyIsolationInfoEnabled()) {
-    EXPECT_DEATH_IF_SUPPORTED(isolation_info.frame_origin(), "");
-    EXPECT_EQ(absl::nullopt, isolation_info.frame_origin_for_testing());
-  } else {
-    EXPECT_EQ(kOrigin1, isolation_info.frame_origin());
-  }
+  EXPECT_EQ(kOrigin1, isolation_info.frame_origin());
   EXPECT_TRUE(isolation_info.network_isolation_key().IsFullyPopulated());
   EXPECT_TRUE(isolation_info.network_isolation_key().IsTransient());
   EXPECT_EQ(absl::nullopt,
@@ -399,13 +277,7 @@
   EXPECT_EQ(IsolationInfo::RequestType::kMainFrame,
             redirected_isolation_info.request_type());
   EXPECT_EQ(kOrigin3, redirected_isolation_info.top_frame_origin());
-  if (IsDoubleKeyIsolationInfoEnabled()) {
-    EXPECT_DEATH_IF_SUPPORTED(redirected_isolation_info.frame_origin(), "");
-    EXPECT_EQ(absl::nullopt,
-              redirected_isolation_info.frame_origin_for_testing());
-  } else {
-    EXPECT_EQ(kOrigin3, redirected_isolation_info.frame_origin());
-  }
+  EXPECT_EQ(kOrigin3, redirected_isolation_info.frame_origin());
   EXPECT_TRUE(
       redirected_isolation_info.network_isolation_key().IsFullyPopulated());
   EXPECT_TRUE(redirected_isolation_info.network_isolation_key().IsTransient());
@@ -425,12 +297,7 @@
   EXPECT_EQ(IsolationInfo::RequestType::kSubFrame,
             isolation_info.request_type());
   EXPECT_EQ(kOrigin1, isolation_info.top_frame_origin());
-  if (IsDoubleKeyIsolationInfoEnabled()) {
-    EXPECT_DEATH_IF_SUPPORTED(isolation_info.frame_origin(), "");
-    EXPECT_EQ(absl::nullopt, isolation_info.frame_origin_for_testing());
-  } else {
-    EXPECT_EQ(kOrigin2, isolation_info.frame_origin());
-  }
+  EXPECT_EQ(kOrigin2, isolation_info.frame_origin());
   EXPECT_TRUE(isolation_info.network_isolation_key().IsFullyPopulated());
   EXPECT_TRUE(isolation_info.network_isolation_key().IsTransient());
   EXPECT_EQ(absl::nullopt,
@@ -447,13 +314,7 @@
   EXPECT_EQ(IsolationInfo::RequestType::kSubFrame,
             redirected_isolation_info.request_type());
   EXPECT_EQ(kOrigin1, redirected_isolation_info.top_frame_origin());
-  if (IsDoubleKeyIsolationInfoEnabled()) {
-    EXPECT_DEATH_IF_SUPPORTED(redirected_isolation_info.frame_origin(), "");
-    EXPECT_EQ(absl::nullopt,
-              redirected_isolation_info.frame_origin_for_testing());
-  } else {
-    EXPECT_EQ(kOrigin3, redirected_isolation_info.frame_origin());
-  }
+  EXPECT_EQ(kOrigin3, redirected_isolation_info.frame_origin());
   EXPECT_TRUE(
       redirected_isolation_info.network_isolation_key().IsFullyPopulated());
   EXPECT_TRUE(redirected_isolation_info.network_isolation_key().IsTransient());
@@ -470,12 +331,7 @@
   IsolationInfo isolation_info;
   EXPECT_EQ(IsolationInfo::RequestType::kOther, isolation_info.request_type());
   EXPECT_FALSE(isolation_info.top_frame_origin());
-  if (IsDoubleKeyIsolationInfoEnabled()) {
-    EXPECT_DEATH_IF_SUPPORTED(isolation_info.frame_origin(), "");
-    EXPECT_FALSE(isolation_info.frame_origin_for_testing());
-  } else {
-    EXPECT_FALSE(isolation_info.frame_origin());
-  }
+  EXPECT_FALSE(isolation_info.frame_origin());
   EXPECT_TRUE(isolation_info.network_isolation_key().IsEmpty());
   EXPECT_TRUE(isolation_info.site_for_cookies().IsNull());
   EXPECT_FALSE(isolation_info.party_context());
@@ -494,16 +350,9 @@
       SiteForCookies::FromOrigin(kOrigin1), kPartyContextEmpty);
   EXPECT_EQ(IsolationInfo::RequestType::kOther, isolation_info.request_type());
   EXPECT_EQ(kOrigin1, isolation_info.top_frame_origin());
-  if (IsDoubleKeyIsolationInfoEnabled()) {
-    EXPECT_DEATH_IF_SUPPORTED(isolation_info.frame_origin(), "");
-    EXPECT_EQ(absl::nullopt, isolation_info.frame_origin_for_testing());
-    EXPECT_EQ("https://foo.test https://foo.test",
-              isolation_info.network_isolation_key().ToCacheKeyString());
-  } else {
-    EXPECT_EQ(kOrigin1, isolation_info.frame_origin());
-    EXPECT_EQ("https://foo.test https://foo.test",
-              isolation_info.network_isolation_key().ToCacheKeyString());
-  }
+  EXPECT_EQ(kOrigin1, isolation_info.frame_origin());
+  EXPECT_EQ("https://foo.test https://foo.test",
+            isolation_info.network_isolation_key().ToCacheKeyString());
   EXPECT_TRUE(isolation_info.network_isolation_key().IsFullyPopulated());
   EXPECT_FALSE(isolation_info.network_isolation_key().IsTransient());
   EXPECT_TRUE(
@@ -526,16 +375,9 @@
                             kOrigin2, SiteForCookies(), kPartyContext2);
   EXPECT_EQ(IsolationInfo::RequestType::kOther, isolation_info.request_type());
   EXPECT_EQ(kOrigin1, isolation_info.top_frame_origin());
-  if (IsDoubleKeyIsolationInfoEnabled()) {
-    EXPECT_DEATH_IF_SUPPORTED(isolation_info.frame_origin(), "");
-    EXPECT_EQ(absl::nullopt, isolation_info.frame_origin_for_testing());
-    EXPECT_EQ("https://foo.test https://foo.test",
-              isolation_info.network_isolation_key().ToCacheKeyString());
-  } else {
-    EXPECT_EQ(kOrigin2, isolation_info.frame_origin());
-    EXPECT_EQ("https://foo.test https://bar.test",
-              isolation_info.network_isolation_key().ToCacheKeyString());
-  }
+  EXPECT_EQ(kOrigin2, isolation_info.frame_origin());
+  EXPECT_EQ("https://foo.test https://bar.test",
+            isolation_info.network_isolation_key().ToCacheKeyString());
   EXPECT_TRUE(isolation_info.network_isolation_key().IsFullyPopulated());
   EXPECT_FALSE(isolation_info.network_isolation_key().IsTransient());
   EXPECT_TRUE(isolation_info.site_for_cookies().IsNull());
@@ -553,12 +395,7 @@
   IsolationInfo isolation_info = IsolationInfo::CreateTransient();
   EXPECT_EQ(IsolationInfo::RequestType::kOther, isolation_info.request_type());
   EXPECT_TRUE(isolation_info.top_frame_origin()->opaque());
-  if (IsDoubleKeyIsolationInfoEnabled()) {
-    EXPECT_DEATH_IF_SUPPORTED(isolation_info.frame_origin(), "");
-    EXPECT_FALSE(isolation_info.frame_origin_for_testing().has_value());
-  } else {
-    EXPECT_TRUE(isolation_info.frame_origin()->opaque());
-  }
+  EXPECT_TRUE(isolation_info.frame_origin()->opaque());
   EXPECT_TRUE(isolation_info.network_isolation_key().IsFullyPopulated());
   EXPECT_TRUE(isolation_info.network_isolation_key().IsTransient());
   EXPECT_TRUE(isolation_info.site_for_cookies().IsNull());
@@ -577,16 +414,9 @@
       IsolationInfo::CreateForInternalRequest(kOrigin1);
   EXPECT_EQ(IsolationInfo::RequestType::kOther, isolation_info.request_type());
   EXPECT_EQ(kOrigin1, isolation_info.top_frame_origin());
-  if (IsDoubleKeyIsolationInfoEnabled()) {
-    EXPECT_DEATH_IF_SUPPORTED(isolation_info.frame_origin(), "");
-    EXPECT_EQ(absl::nullopt, isolation_info.frame_origin_for_testing());
-    EXPECT_EQ("https://foo.test https://foo.test",
-              isolation_info.network_isolation_key().ToCacheKeyString());
-  } else {
-    EXPECT_EQ(kOrigin1, isolation_info.frame_origin());
-    EXPECT_EQ("https://foo.test https://foo.test",
-              isolation_info.network_isolation_key().ToCacheKeyString());
-  }
+  EXPECT_EQ(kOrigin1, isolation_info.frame_origin());
+  EXPECT_EQ("https://foo.test https://foo.test",
+            isolation_info.network_isolation_key().ToCacheKeyString());
   EXPECT_TRUE(isolation_info.network_isolation_key().IsFullyPopulated());
   EXPECT_FALSE(isolation_info.network_isolation_key().IsTransient());
   EXPECT_TRUE(
@@ -617,16 +447,9 @@
       SiteForCookies::FromOrigin(kCustomOrigin), kPartyContext1);
   EXPECT_EQ(IsolationInfo::RequestType::kOther, isolation_info.request_type());
   EXPECT_EQ(kCustomOrigin, isolation_info.top_frame_origin());
-  if (IsDoubleKeyIsolationInfoEnabled()) {
-    EXPECT_DEATH_IF_SUPPORTED(isolation_info.frame_origin(), "");
-    EXPECT_EQ(absl::nullopt, isolation_info.frame_origin_for_testing());
-    EXPECT_EQ("foo://a.foo.com foo://a.foo.com",
-              isolation_info.network_isolation_key().ToCacheKeyString());
-  } else {
-    EXPECT_EQ(kOrigin1, isolation_info.frame_origin());
-    EXPECT_EQ("foo://a.foo.com https://foo.test",
-              isolation_info.network_isolation_key().ToCacheKeyString());
-  }
+  EXPECT_EQ(kOrigin1, isolation_info.frame_origin());
+  EXPECT_EQ("foo://a.foo.com https://foo.test",
+            isolation_info.network_isolation_key().ToCacheKeyString());
   EXPECT_TRUE(isolation_info.network_isolation_key().IsFullyPopulated());
   EXPECT_FALSE(isolation_info.network_isolation_key().IsTransient());
   EXPECT_TRUE(isolation_info.site_for_cookies().IsFirstParty(kCustomOriginUrl));
@@ -673,32 +496,20 @@
   EXPECT_FALSE(IsolationInfo::CreateIfConsistent(
       IsolationInfo::RequestType::kSubFrame, absl::nullopt, kOrigin2,
       SiteForCookies()));
-  // Empty frame origins are ok when double keying is enabled but incorrect
-  // when triple key is enabled.
-  if (IsDoubleKeyIsolationInfoEnabled()) {
-    EXPECT_TRUE(IsolationInfo::CreateIfConsistent(
-        IsolationInfo::RequestType::kOther, kOrigin1, absl::nullopt,
-        SiteForCookies()));
-    EXPECT_TRUE(IsolationInfo::CreateIfConsistent(
-        IsolationInfo::RequestType::kSubFrame, kOrigin1, absl::nullopt,
-        SiteForCookies()));
-    EXPECT_TRUE(IsolationInfo::CreateIfConsistent(
-        IsolationInfo::RequestType::kMainFrame, kOrigin1, absl::nullopt,
-        SiteForCookies::FromOrigin(kOrigin1)));
-  } else {
-    EXPECT_FALSE(IsolationInfo::CreateIfConsistent(
-        IsolationInfo::RequestType::kOther, kOrigin1, absl::nullopt,
-        SiteForCookies()));
-    EXPECT_FALSE(IsolationInfo::CreateIfConsistent(
-        IsolationInfo::RequestType::kSubFrame, kOrigin1, absl::nullopt,
-        SiteForCookies()));
-    EXPECT_FALSE(IsolationInfo::CreateIfConsistent(
-        IsolationInfo::RequestType::kMainFrame, kOrigin1, absl::nullopt,
-        SiteForCookies::FromOrigin(kOrigin1)));
-    EXPECT_FALSE(IsolationInfo::CreateIfConsistent(
-        IsolationInfo::RequestType::kOther, kOrigin1, kOrigin2,
-        SiteForCookies::FromOrigin(kOrigin1)));
-  }
+
+  // Empty frame origins are incorrect.
+  EXPECT_FALSE(IsolationInfo::CreateIfConsistent(
+      IsolationInfo::RequestType::kOther, kOrigin1, absl::nullopt,
+      SiteForCookies()));
+  EXPECT_FALSE(IsolationInfo::CreateIfConsistent(
+      IsolationInfo::RequestType::kSubFrame, kOrigin1, absl::nullopt,
+      SiteForCookies()));
+  EXPECT_FALSE(IsolationInfo::CreateIfConsistent(
+      IsolationInfo::RequestType::kMainFrame, kOrigin1, absl::nullopt,
+      SiteForCookies::FromOrigin(kOrigin1)));
+  EXPECT_FALSE(IsolationInfo::CreateIfConsistent(
+      IsolationInfo::RequestType::kOther, kOrigin1, kOrigin2,
+      SiteForCookies::FromOrigin(kOrigin1)));
 
   // No origins with non-null SiteForCookies.
   EXPECT_FALSE(IsolationInfo::CreateIfConsistent(
@@ -811,15 +622,8 @@
                             kOrigin2, SiteForCookies::FromOrigin(kOrigin1),
                             kPartyContext1, &kNonce1),
   };
-  if (IsDoubleKeyIsolationInfoEnabled()) {
-    for (const auto& info : kNegativeWhenDoubleKeyEnabledTestCases) {
-      EXPECT_TRUE(info.Serialize().empty());
-    }
-
-  } else {
-    for (const auto& info : kNegativeTestCases) {
-      EXPECT_TRUE(info.Serialize().empty());
-    }
+  for (const auto& info : kNegativeTestCases) {
+    EXPECT_TRUE(info.Serialize().empty());
   }
 }
 
diff --git a/net/base/network_anonymization_key_unittest.cc b/net/base/network_anonymization_key_unittest.cc
index f885273..bc35e88 100644
--- a/net/base/network_anonymization_key_unittest.cc
+++ b/net/base/network_anonymization_key_unittest.cc
@@ -18,25 +18,20 @@
 
 namespace net {
 
+// `EnabledFeatureFlagsTestingParam ` allows enabling and disabling
+// the feature flags that control the key schemes for NetworkAnonymizationKey.
+// This allows us to test the possible combinations of flags that will be
+// allowed for experimentation.
 struct EnabledFeatureFlagsTestingParam {
   // True = 2.5-keyed NAK, false = double-keyed NAK.
   const bool enableCrossSiteFlagNetworkAnonymizationKey;
-  const bool enableDoubleKeyNetworkIsolationKey;
 };
 
-//    0. Double-keying is enabled for both IsolationInfo and
-//    NetworkAnonymizationKey.
-//    1. Triple-keying is enabled for IsolationInfo and double-keying is enabled
-//    for NetworkAnonymizationKey.
-//    2. Triple-keying is enabled for IsolationInfo and double-keying +
-//    cross-site-bit is enabled for NetworkAnonymizationKey.
 const EnabledFeatureFlagsTestingParam kFlagsParam[] = {
-    {/*enableCrossSiteFlagNetworkAnonymizationKey=*/false,
-     /*enableDoubleKeyNetworkIsolationKey=*/true},
-    {/*enableCrossSiteFlagNetworkAnonymizationKey=*/false,
-     /*enableDoubleKeyNetworkIsolationKey=*/false},
-    {/*enableCrossSiteFlagNetworkAnonymizationKey=*/true,
-     /*enableDoubleKeyNetworkIsolationKey=*/false}};
+    // 0. Double-keying is enabled for NetworkAnonymizationKey.
+    {/*enableCrossSiteFlagNetworkAnonymizationKey=*/false},
+    // 1. Double-keying + cross-site-bit is enabled for NetworkAnonymizationKey.
+    {/*enableCrossSiteFlagNetworkAnonymizationKey=*/true}};
 
 class NetworkAnonymizationKeyTest
     : public testing::Test,
@@ -46,14 +41,6 @@
     std::vector<base::test::FeatureRef> enabled_features = {};
     std::vector<base::test::FeatureRef> disabled_features = {};
 
-    if (IsDoubleKeyNetworkIsolationKeyEnabled()) {
-      enabled_features.push_back(
-          net::features::kForceIsolationInfoFrameOriginToTopLevelFrame);
-    } else {
-      disabled_features.push_back(
-          net::features::kForceIsolationInfoFrameOriginToTopLevelFrame);
-    }
-
     if (IsCrossSiteFlagEnabled()) {
       enabled_features.push_back(
           net::features::kEnableCrossSiteFlagNetworkAnonymizationKey);
@@ -65,10 +52,6 @@
     scoped_feature_list_.InitWithFeatures(enabled_features, disabled_features);
   }
 
-  static bool IsDoubleKeyNetworkIsolationKeyEnabled() {
-    return GetParam().enableDoubleKeyNetworkIsolationKey;
-  }
-
   bool IsCrossSiteFlagEnabled() {
     return GetParam().enableCrossSiteFlagNetworkAnonymizationKey;
   }
@@ -128,25 +111,8 @@
   // empty NAK.
   EXPECT_TRUE(nak_from_empty_nik.IsEmpty());
 
-  // Double-keyed NetworkIsolationKey + double-keyed NetworkAnonymizationKey
-  // case.
-  if (IsDoubleKeyNetworkIsolationKeyEnabled() && !IsCrossSiteFlagEnabled()) {
-    // Top site should be populated.
-    EXPECT_EQ(nak_from_cross_site_nik.GetTopFrameSite(), site_a);
-    EXPECT_EQ(nak_from_same_site_nik.GetTopFrameSite(), site_a);
-
-    // Nonce should be populated.
-    EXPECT_EQ(nak_from_same_site_nik.GetNonce(), nik_nonce);
-    EXPECT_EQ(nak_from_cross_site_nik.GetNonce(), nik_nonce);
-
-    // Double-keyed NAKs created from different third party cross site contexts
-    // should be equal.
-    EXPECT_TRUE(nak_from_same_site_nik == nak_from_cross_site_nik);
-  }
-
-  // Triple-keyed NetworkIsolationKey + double-keyed NetworkAnonymizationKey
-  // case.
-  if (!IsDoubleKeyNetworkIsolationKeyEnabled() && !IsCrossSiteFlagEnabled()) {
+  // Double-keyed NetworkAnonymizationKey case.
+  if (!IsCrossSiteFlagEnabled()) {
     // Top site should be populated correctly.
     EXPECT_EQ(nak_from_cross_site_nik.GetTopFrameSite(), site_a);
     EXPECT_EQ(nak_from_same_site_nik.GetTopFrameSite(), site_a);
@@ -160,9 +126,8 @@
     EXPECT_TRUE(nak_from_same_site_nik == nak_from_cross_site_nik);
   }
 
-  // Triple-keyed NetworkIsolationKey + double-keyed + cross site bit
-  // NetworkAnonymizationKey case.
-  if (!IsDoubleKeyNetworkIsolationKeyEnabled() && IsCrossSiteFlagEnabled()) {
+  // Double-keyed + cross site bit NetworkAnonymizationKey case.
+  if (IsCrossSiteFlagEnabled()) {
     // Top site should be populated correctly.
     EXPECT_EQ(nak_from_cross_site_nik.GetTopFrameSite(), site_a);
     EXPECT_EQ(nak_from_same_site_nik.GetTopFrameSite(), site_a);
diff --git a/net/base/network_isolation_key.cc b/net/base/network_isolation_key.cc
index 0000f73..d0ff1ba 100644
--- a/net/base/network_isolation_key.cc
+++ b/net/base/network_isolation_key.cc
@@ -215,8 +215,9 @@
 }
 
 bool NetworkIsolationKey::IsFrameSiteEnabled() {
-  return !base::FeatureList::IsEnabled(
-      net::features::kForceIsolationInfoFrameOriginToTopLevelFrame);
+  // NIKs are currently always triple-keyed, but we will experiment with
+  // 2.5-keying in crbug.com/1414808.
+  return true;
 }
 
 bool NetworkIsolationKey::IsOpaque() const {
diff --git a/net/base/network_isolation_key.h b/net/base/network_isolation_key.h
index 049f4a3..77193f0 100644
--- a/net/base/network_isolation_key.h
+++ b/net/base/network_isolation_key.h
@@ -121,9 +121,7 @@
 
   const absl::optional<SchemefulSite>& GetFrameSite() const;
 
-  // Do not use outside of testing. Returns the `frame_site_` if
-  // `kForceIsolationInfoFrameOriginToTopLevelFrame` is disabled. Else it
-  // returns nullopt.
+  // Do not use outside of testing. Returns the `frame_site_`.
   const absl::optional<SchemefulSite>& GetFrameSiteForTesting() const {
     return frame_site_;
   }
diff --git a/net/base/network_isolation_key_unittest.cc b/net/base/network_isolation_key_unittest.cc
index 919720f..a764a73 100644
--- a/net/base/network_isolation_key_unittest.cc
+++ b/net/base/network_isolation_key_unittest.cc
@@ -4,7 +4,6 @@
 
 #include "net/base/network_isolation_key.h"
 
-#include "base/test/scoped_feature_list.h"
 #include "base/unguessable_token.h"
 #include "base/values.h"
 #include "net/base/features.h"
@@ -19,40 +18,11 @@
 namespace {
 const char kDataUrl[] = "data:text/html,<body>Hello World</body>";
 
-class NetworkIsolationKeyTest : public testing::Test,
-                                public testing::WithParamInterface<bool> {
- public:
-  void SetUp() override {
-    if (ForceIsolationInfoFrameOriginToTopLevelFrameEnabled()) {
-      scoped_feature_list_.InitAndEnableFeature(
-          net::features::kForceIsolationInfoFrameOriginToTopLevelFrame);
-    } else {
-      scoped_feature_list_.InitAndDisableFeature(
-          net::features::kForceIsolationInfoFrameOriginToTopLevelFrame);
-    }
-  }
-  static bool ForceIsolationInfoFrameOriginToTopLevelFrameEnabled() {
-    return GetParam();
-  }
-
- private:
-  base::test::ScopedFeatureList scoped_feature_list_;
-};
-
-INSTANTIATE_TEST_SUITE_P(
-    All,
-    NetworkIsolationKeyTest,
-    /*force_isolation_info_frame_origin_to_top_level_frame*/ testing::Bool());
-
-TEST_P(NetworkIsolationKeyTest, IsFrameSiteEnabled) {
-  if (ForceIsolationInfoFrameOriginToTopLevelFrameEnabled()) {
-    EXPECT_FALSE(NetworkIsolationKey::IsFrameSiteEnabled());
-  } else {
-    EXPECT_TRUE(NetworkIsolationKey::IsFrameSiteEnabled());
-  }
+TEST(NetworkIsolationKeyTest, IsFrameSiteEnabled) {
+  EXPECT_TRUE(NetworkIsolationKey::IsFrameSiteEnabled());
 }
 
-TEST_P(NetworkIsolationKeyTest, EmptyKey) {
+TEST(NetworkIsolationKeyTest, EmptyKey) {
   NetworkIsolationKey key;
   EXPECT_FALSE(key.IsFullyPopulated());
   EXPECT_EQ(absl::nullopt, key.ToCacheKeyString());
@@ -60,25 +30,19 @@
   EXPECT_EQ("null null", key.ToDebugString());
 }
 
-TEST_P(NetworkIsolationKeyTest, NonEmptyKey) {
+TEST(NetworkIsolationKeyTest, NonEmptyKey) {
   SchemefulSite site1 = SchemefulSite(GURL("http://a.test/"));
   SchemefulSite site2 = SchemefulSite(GURL("http://b.test/"));
   NetworkIsolationKey key(site1, site2);
   EXPECT_TRUE(key.IsFullyPopulated());
-  if (ForceIsolationInfoFrameOriginToTopLevelFrameEnabled()) {
-    EXPECT_EQ(site1.Serialize() + " " + site1.Serialize(),
-              key.ToCacheKeyString());
-    EXPECT_EQ(site1.GetDebugString() + " null", key.ToDebugString());
-  } else {
-    EXPECT_EQ(site1.Serialize() + " " + site2.Serialize(),
-              key.ToCacheKeyString());
-    EXPECT_EQ(site1.GetDebugString() + " " + site2.GetDebugString(),
-              key.ToDebugString());
-  }
+  EXPECT_EQ(site1.Serialize() + " " + site2.Serialize(),
+            key.ToCacheKeyString());
+  EXPECT_EQ(site1.GetDebugString() + " " + site2.GetDebugString(),
+            key.ToDebugString());
   EXPECT_FALSE(key.IsTransient());
 }
 
-TEST_P(NetworkIsolationKeyTest, KeyWithNonce) {
+TEST(NetworkIsolationKeyTest, KeyWithNonce) {
   SchemefulSite site1 = SchemefulSite(GURL("http://a.test/"));
   SchemefulSite site2 = SchemefulSite(GURL("http://b.test/"));
   base::UnguessableToken nonce = base::UnguessableToken::Create();
@@ -86,15 +50,9 @@
   EXPECT_TRUE(key.IsFullyPopulated());
   EXPECT_EQ(absl::nullopt, key.ToCacheKeyString());
   EXPECT_TRUE(key.IsTransient());
-  if (ForceIsolationInfoFrameOriginToTopLevelFrameEnabled()) {
-    EXPECT_EQ(site1.GetDebugString() + " null" + " (with nonce " +
-                  nonce.ToString() + ")",
-              key.ToDebugString());
-  } else {
-    EXPECT_EQ(site1.GetDebugString() + " " + site2.GetDebugString() +
-                  " (with nonce " + nonce.ToString() + ")",
-              key.ToDebugString());
-  }
+  EXPECT_EQ(site1.GetDebugString() + " " + site2.GetDebugString() +
+                " (with nonce " + nonce.ToString() + ")",
+            key.ToDebugString());
 
   // Create another NetworkIsolationKey with the same input parameters, and
   // check that it is equal.
@@ -109,7 +67,7 @@
   EXPECT_NE(key.ToDebugString(), key2.ToDebugString());
 }
 
-TEST_P(NetworkIsolationKeyTest, OpaqueOriginKey) {
+TEST(NetworkIsolationKeyTest, OpaqueOriginKey) {
   SchemefulSite site_data = SchemefulSite(GURL(kDataUrl));
   NetworkIsolationKey key(site_data, site_data);
   EXPECT_TRUE(key.IsFullyPopulated());
@@ -124,7 +82,7 @@
   EXPECT_NE(key.ToDebugString(), other_key.ToDebugString());
 }
 
-TEST_P(NetworkIsolationKeyTest, Operators) {
+TEST(NetworkIsolationKeyTest, Operators) {
   base::UnguessableToken nonce1 = base::UnguessableToken::Create();
   base::UnguessableToken nonce2 = base::UnguessableToken::Create();
   if (nonce2 < nonce1)
@@ -176,7 +134,7 @@
   }
 }
 
-TEST_P(NetworkIsolationKeyTest, UniqueOriginOperators) {
+TEST(NetworkIsolationKeyTest, UniqueOriginOperators) {
   const auto kSite1 = SchemefulSite(GURL(kDataUrl));
   const auto kSite2 = SchemefulSite(GURL(kDataUrl));
   NetworkIsolationKey key1(kSite1, kSite1);
@@ -197,38 +155,26 @@
   EXPECT_TRUE(!(key1 < key2) || !(key2 < key1));
 }
 
-TEST_P(NetworkIsolationKeyTest, KeyWithOneOpaqueOrigin) {
+TEST(NetworkIsolationKeyTest, KeyWithOneOpaqueOrigin) {
   SchemefulSite site = SchemefulSite(GURL("http://a.test"));
   SchemefulSite opaque_site = SchemefulSite(GURL(kDataUrl));
 
   NetworkIsolationKey key1(site, opaque_site);
   EXPECT_TRUE(key1.IsFullyPopulated());
-  if (ForceIsolationInfoFrameOriginToTopLevelFrameEnabled()) {
-    EXPECT_FALSE(key1.IsTransient());
-    EXPECT_EQ(site.GetDebugString() + " " + site.GetDebugString(),
-              key1.ToCacheKeyString());
-
-    EXPECT_EQ(site.GetDebugString() + " null", key1.ToDebugString());
-  } else {
-    EXPECT_TRUE(key1.IsTransient());
-    EXPECT_EQ(absl::nullopt, key1.ToCacheKeyString());
-    EXPECT_EQ(site.GetDebugString() + " " + opaque_site.GetDebugString(),
-              key1.ToDebugString());
-  }
+  EXPECT_TRUE(key1.IsTransient());
+  EXPECT_EQ(absl::nullopt, key1.ToCacheKeyString());
+  EXPECT_EQ(site.GetDebugString() + " " + opaque_site.GetDebugString(),
+            key1.ToDebugString());
 
   NetworkIsolationKey key2(opaque_site, site);
   EXPECT_TRUE(key2.IsFullyPopulated());
   EXPECT_TRUE(key2.IsTransient());
   EXPECT_EQ(absl::nullopt, key2.ToCacheKeyString());
-  if (ForceIsolationInfoFrameOriginToTopLevelFrameEnabled()) {
-    EXPECT_EQ(opaque_site.GetDebugString() + " null", key2.ToDebugString());
-  } else {
-    EXPECT_EQ(opaque_site.GetDebugString() + " " + site.GetDebugString(),
-              key2.ToDebugString());
-  }
+  EXPECT_EQ(opaque_site.GetDebugString() + " " + site.GetDebugString(),
+            key2.ToDebugString());
 }
 
-TEST_P(NetworkIsolationKeyTest, ValueRoundTripEmpty) {
+TEST(NetworkIsolationKeyTest, ValueRoundTripEmpty) {
   const SchemefulSite kJunkSite = SchemefulSite(GURL("data:text/html,junk"));
 
   // Convert empty key to value and back, expecting the same value.
@@ -242,7 +188,7 @@
   EXPECT_EQ(no_frame_site_key, out_key);
 }
 
-TEST_P(NetworkIsolationKeyTest, ValueRoundTripNonEmpty) {
+TEST(NetworkIsolationKeyTest, ValueRoundTripNonEmpty) {
   const SchemefulSite kJunkSite = SchemefulSite(GURL("data:text/html,junk"));
 
   NetworkIsolationKey key1(SchemefulSite(GURL("https://foo.test/")),
@@ -256,7 +202,7 @@
   EXPECT_EQ(key1, key2);
 }
 
-TEST_P(NetworkIsolationKeyTest, ToValueTransientSite) {
+TEST(NetworkIsolationKeyTest, ToValueTransientSite) {
   const SchemefulSite kSiteWithTransientOrigin =
       SchemefulSite(GURL("data:text/html,transient"));
   NetworkIsolationKey key(kSiteWithTransientOrigin, kSiteWithTransientOrigin);
@@ -265,7 +211,7 @@
   EXPECT_FALSE(key.ToValue(&value));
 }
 
-TEST_P(NetworkIsolationKeyTest, FromValueBadData) {
+TEST(NetworkIsolationKeyTest, FromValueBadData) {
   base::Value::List not_a_url_list;
   not_a_url_list.Append("not-a-url");
 
@@ -297,70 +243,47 @@
   NetworkIsolationKey key;
   base::Value triple_key_case(std::move(triple_key_list));
 
-  // When double key is enabled top_level_site must equal frame_site.
-  bool expect_fail_on_different_sites =
-      ForceIsolationInfoFrameOriginToTopLevelFrameEnabled();
-
-  if (expect_fail_on_different_sites) {
-    EXPECT_FALSE(NetworkIsolationKey::FromValue(triple_key_case, &key))
-        << triple_key_case;
-  }
+  EXPECT_FALSE(NetworkIsolationKey::FromValue(triple_key_case, &key))
+      << triple_key_case;
 }
 
-TEST_P(NetworkIsolationKeyTest, WithFrameSite) {
+TEST(NetworkIsolationKeyTest, WithFrameSite) {
   NetworkIsolationKey key(SchemefulSite(GURL("http://b.test")),
                           SchemefulSite(GURL("http://a.test/")));
   EXPECT_TRUE(key.IsFullyPopulated());
   EXPECT_FALSE(key.IsTransient());
-  if (ForceIsolationInfoFrameOriginToTopLevelFrameEnabled()) {
-    EXPECT_EQ("http://b.test http://b.test", key.ToCacheKeyString());
-    EXPECT_EQ("http://b.test null", key.ToDebugString());
-  } else {
-    EXPECT_EQ("http://b.test http://a.test", key.ToCacheKeyString());
-    EXPECT_EQ("http://b.test http://a.test", key.ToDebugString());
-  }
+  EXPECT_EQ("http://b.test http://a.test", key.ToCacheKeyString());
+  EXPECT_EQ("http://b.test http://a.test", key.ToDebugString());
   EXPECT_TRUE(key == key);
   EXPECT_FALSE(key != key);
   EXPECT_FALSE(key < key);
 }
 
-TEST_P(NetworkIsolationKeyTest, OpaqueSiteKey) {
+TEST(NetworkIsolationKeyTest, OpaqueSiteKey) {
   SchemefulSite site_data = SchemefulSite(GURL(kDataUrl));
   SchemefulSite site_data2 = SchemefulSite(GURL(kDataUrl));
   SchemefulSite site_a = SchemefulSite(GURL("http://a.test"));
 
   NetworkIsolationKey key1(site_a, site_data);
   EXPECT_TRUE(key1.IsFullyPopulated());
-  if (ForceIsolationInfoFrameOriginToTopLevelFrameEnabled()) {
-    EXPECT_FALSE(key1.IsTransient());
+  EXPECT_TRUE(key1.IsTransient());
 
-    EXPECT_EQ(NetworkIsolationKey(site_a, site_data2), key1);
-    EXPECT_EQ("http://a.test http://a.test", key1.ToCacheKeyString());
-    EXPECT_EQ("http://a.test null", key1.ToDebugString());
-  } else {
-    EXPECT_TRUE(key1.IsTransient());
-
-    EXPECT_EQ(absl::nullopt, key1.ToCacheKeyString());
-    EXPECT_EQ("http://a.test " + site_data.GetDebugString(),
-              key1.ToDebugString());
-    EXPECT_NE(NetworkIsolationKey(site_a, site_data2), key1);
-  }
+  EXPECT_EQ(absl::nullopt, key1.ToCacheKeyString());
+  EXPECT_EQ("http://a.test " + site_data.GetDebugString(),
+            key1.ToDebugString());
+  EXPECT_NE(NetworkIsolationKey(site_a, site_data2), key1);
 
   NetworkIsolationKey key2(site_data, site_a);
   EXPECT_TRUE(key2.IsFullyPopulated());
   EXPECT_TRUE(key2.IsTransient());
   EXPECT_EQ(absl::nullopt, key2.ToCacheKeyString());
-  if (ForceIsolationInfoFrameOriginToTopLevelFrameEnabled()) {
-    EXPECT_EQ(site_data.GetDebugString() + " null", key2.ToDebugString());
-  } else {
-    EXPECT_EQ(site_data.GetDebugString() + " http://a.test",
-              key2.ToDebugString());
-  }
+  EXPECT_EQ(site_data.GetDebugString() + " http://a.test",
+            key2.ToDebugString());
 
   EXPECT_NE(NetworkIsolationKey(site_data2, site_a), key2);
 }
 
-TEST_P(NetworkIsolationKeyTest, OpaqueSiteKeyBoth) {
+TEST(NetworkIsolationKeyTest, OpaqueSiteKeyBoth) {
   SchemefulSite site_data_1 = SchemefulSite(GURL(kDataUrl));
   SchemefulSite site_data_2 = SchemefulSite(GURL(kDataUrl));
   SchemefulSite site_data_3 = SchemefulSite(GURL(kDataUrl));
@@ -379,15 +302,9 @@
 
   // Test the equality/comparisons of the various keys
   EXPECT_TRUE(key1 == key2);
-  if (ForceIsolationInfoFrameOriginToTopLevelFrameEnabled()) {
-    EXPECT_TRUE(key1 == key3);
-    EXPECT_FALSE(key1 < key3 || key3 < key1);
-    EXPECT_EQ(key1.ToDebugString(), key3.ToDebugString());
-  } else {
-    EXPECT_FALSE(key1 == key3);
-    EXPECT_TRUE(key1 < key3 || key3 < key1);
-    EXPECT_NE(key1.ToDebugString(), key3.ToDebugString());
-  }
+  EXPECT_FALSE(key1 == key3);
+  EXPECT_TRUE(key1 < key3 || key3 < key1);
+  EXPECT_NE(key1.ToDebugString(), key3.ToDebugString());
   EXPECT_FALSE(key1 < key2 || key2 < key1);
 
   // Test the ToString and ToDebugString
@@ -399,7 +316,7 @@
 
 // Make sure that the logic to extract the registerable domain from an origin
 // does not affect the host when using a non-standard scheme.
-TEST_P(NetworkIsolationKeyTest, NonStandardScheme) {
+TEST(NetworkIsolationKeyTest, NonStandardScheme) {
   // Have to register the scheme, or SchemefulSite() will return an opaque
   // origin.
   url::ScopedSchemeRegistryForTests scoped_registry;
@@ -411,22 +328,18 @@
   EXPECT_EQ("foo://a.foo.com foo://a.foo.com", key.ToCacheKeyString());
 }
 
-TEST_P(NetworkIsolationKeyTest, CreateWithNewFrameSite) {
+TEST(NetworkIsolationKeyTest, CreateWithNewFrameSite) {
   SchemefulSite site_a = SchemefulSite(GURL("http://a.com"));
   SchemefulSite site_b = SchemefulSite(GURL("http://b.com"));
   SchemefulSite site_c = SchemefulSite(GURL("http://c.com"));
 
   net::NetworkIsolationKey key(site_a, site_b);
   NetworkIsolationKey key_c = key.CreateWithNewFrameSite(site_c);
-  if (ForceIsolationInfoFrameOriginToTopLevelFrameEnabled()) {
-    EXPECT_DEATH_IF_SUPPORTED(key_c.GetFrameSite(), "");
-  } else {
-    EXPECT_EQ(site_c, key_c.GetFrameSite());
-  }
+  EXPECT_EQ(site_c, key_c.GetFrameSite());
   EXPECT_EQ(site_a, key_c.GetTopFrameSite());
 }
 
-TEST_P(NetworkIsolationKeyTest, CreateTransient) {
+TEST(NetworkIsolationKeyTest, CreateTransient) {
   NetworkIsolationKey transient_key = NetworkIsolationKey::CreateTransient();
   EXPECT_TRUE(transient_key.IsFullyPopulated());
   EXPECT_TRUE(transient_key.IsTransient());
@@ -443,13 +356,9 @@
   }
 }
 
-TEST(NetworkIsolationKeyFeatureShiftTest, ValueRoundTripDoubleToTriple) {
-  base::test::ScopedFeatureList scoped_feature_list_;
+TEST(NetworkIsolationKeyFeatureShiftTest, ValueRoundTrip) {
   const SchemefulSite kJunkSite = SchemefulSite(GURL("data:text/html,junk"));
 
-  // Turn double keying off.
-  scoped_feature_list_.InitAndDisableFeature(
-      net::features::kForceIsolationInfoFrameOriginToTopLevelFrame);
   // Create a triple key.
   NetworkIsolationKey created_triple_key(
       SchemefulSite(GURL("https://foo.test/")),
@@ -468,37 +377,6 @@
   // Serialize a triple key value with frame site enabled.
   base::Value created_triple_key_value2;
   ASSERT_TRUE(created_triple_key.ToValue(&created_triple_key_value2));
-
-  // Turn double keying on.
-  scoped_feature_list_.Reset();
-  scoped_feature_list_.InitAndEnableFeature(
-      net::features::kForceIsolationInfoFrameOriginToTopLevelFrame);
-
-  // Create a key and confirm the frame site is correctly set to nullopt rather
-  // than https://bar.test/.
-  NetworkIsolationKey created_double_key(
-      SchemefulSite(GURL("https://foo.test/")),
-      SchemefulSite(GURL("https://bar.test/")));
-  EXPECT_DEATH_IF_SUPPORTED(created_double_key.GetFrameSite(), "");
-
-  // Test round trip of key created when frame site was disabled.
-  base::Value created_double_key_value;
-  ASSERT_TRUE(created_double_key.ToValue(&created_double_key_value));
-  // Fill initial value with junk data, to make sure it's overwritten.
-  NetworkIsolationKey created_double_key2(kJunkSite, kJunkSite);
-  EXPECT_TRUE(NetworkIsolationKey::FromValue(created_double_key_value,
-                                             &created_double_key2));
-  EXPECT_EQ(created_double_key, created_double_key2);
-
-  // Test round trip of key created with frame site enabled is now formed
-  // correctly as a double key. This key was serialized to value when frame site
-  // was enabled and should be able to be created from value without error.
-  NetworkIsolationKey created_triple_key3(kJunkSite, kJunkSite);
-  EXPECT_TRUE(NetworkIsolationKey::FromValue(created_triple_key_value2,
-                                             &created_triple_key3));
-  // Triple key should be in a double key form with the frame site an empty
-  // optional.
-  EXPECT_EQ(created_double_key, created_triple_key3);
 }
 
 }  // namespace
diff --git a/net/http/http_request_info_unittest.cc b/net/http/http_request_info_unittest.cc
index bdffaa0..a144364 100644
--- a/net/http/http_request_info_unittest.cc
+++ b/net/http/http_request_info_unittest.cc
@@ -25,27 +25,8 @@
   // Triple key NIK and double key NAK.
   base::test::ScopedFeatureList scoped_feature_list_;
   scoped_feature_list_.Reset();
-  std::vector<base::test::FeatureRef> enabled_features_3 = {};
-  std::vector<base::test::FeatureRef> disabled_features_3 = {};
-  disabled_features_3.push_back(
-      net::features::kForceIsolationInfoFrameOriginToTopLevelFrame);
-  disabled_features_3.push_back(
+  scoped_feature_list_.InitAndDisableFeature(
       net::features::kEnableCrossSiteFlagNetworkAnonymizationKey);
-  scoped_feature_list_.InitWithFeatures(enabled_features_3,
-                                        disabled_features_3);
-
-  EXPECT_FALSE(triple_nik_double_nak_request_info.IsConsistent());
-
-  // Triple key NIK and double key with cross site flag NAK.
-  scoped_feature_list_.Reset();
-  std::vector<base::test::FeatureRef> enabled_features_4 = {};
-  std::vector<base::test::FeatureRef> disabled_features_4 = {};
-  disabled_features_3.push_back(
-      net::features::kForceIsolationInfoFrameOriginToTopLevelFrame);
-  enabled_features_4.push_back(
-      net::features::kEnableCrossSiteFlagNetworkAnonymizationKey);
-  scoped_feature_list_.InitWithFeatures(enabled_features_4,
-                                        disabled_features_4);
 
   EXPECT_FALSE(triple_nik_double_nak_request_info.IsConsistent());
 
diff --git a/net/url_request/url_request_unittest.cc b/net/url_request/url_request_unittest.cc
index f601bc32..d8a684f 100644
--- a/net/url_request/url_request_unittest.cc
+++ b/net/url_request/url_request_unittest.cc
@@ -12886,11 +12886,8 @@
 TEST_F(URLRequestTest,
        SetIsolationInfoFromNakTripleNikDoublePlusCrossSiteBitNak) {
   base::test::ScopedFeatureList scoped_feature_list_;
-  std::vector<base::test::FeatureRef> enabled_features = {
-      net::features::kEnableCrossSiteFlagNetworkAnonymizationKey};
-  std::vector<base::test::FeatureRef> disabled_features = {
-      net::features::kForceIsolationInfoFrameOriginToTopLevelFrame};
-  scoped_feature_list_.InitWithFeatures(enabled_features, disabled_features);
+  scoped_feature_list_.InitAndEnableFeature(
+      net::features::kEnableCrossSiteFlagNetworkAnonymizationKey);
 
   TestDelegate d;
   SchemefulSite site_a = SchemefulSite(GURL("https://a.com/"));
@@ -12948,70 +12945,11 @@
   r->Start();
   d.RunUntilComplete();
 }
-TEST_F(URLRequestTest, SetIsolationInfoFromNakDoubleNikDoubleNak) {
-  base::test::ScopedFeatureList scoped_feature_list_;
-  std::vector<base::test::FeatureRef> enabled_features = {
-      net::features::kForceIsolationInfoFrameOriginToTopLevelFrame};
-  std::vector<base::test::FeatureRef> disabled_features = {
-      net::features::kEnableCrossSiteFlagNetworkAnonymizationKey};
-  scoped_feature_list_.InitWithFeatures(enabled_features, disabled_features);
-
-  TestDelegate d;
-  SchemefulSite site_a = SchemefulSite(GURL("https://a.com/"));
-  SchemefulSite site_b = SchemefulSite(GURL("https://b.com/"));
-  base::UnguessableToken nak_nonce = base::UnguessableToken::Create();
-  NetworkAnonymizationKey populated_cross_site_nak(site_a, site_b, true,
-                                                   nak_nonce);
-  NetworkAnonymizationKey populated_same_site_nak(site_a, site_a, false,
-                                                  nak_nonce);
-  // Frame site should be set to the top level sites value even though NAK is
-  // double keyed.
-  IsolationInfo expected_isolation_info_populated_same_site_nak =
-      IsolationInfo::Create(IsolationInfo::RequestType::kOther,
-                            url::Origin::Create(GURL("https://a.com/")),
-                            url::Origin::Create(GURL("https://a.com/")),
-                            SiteForCookies(),
-                            /*party_context=*/absl::nullopt, &nak_nonce);
-  NetworkAnonymizationKey empty_nak;
-
-  GURL original_url("http://localhost");
-  std::unique_ptr<URLRequest> r(default_context().CreateRequest(
-      original_url, DEFAULT_PRIORITY, &d, TRAFFIC_ANNOTATION_FOR_TESTS));
-
-  r->set_isolation_info_from_network_anonymization_key(
-      populated_cross_site_nak);
-  r->SetLoadFlags(LOAD_DISABLE_CACHE);
-  r->set_allow_credentials(false);
-  EXPECT_TRUE(r->is_created_from_network_anonymization_key());
-  EXPECT_EQ(r->isolation_info().network_anonymization_key(),
-            populated_cross_site_nak);
-  // We do not know the frame_site other than that it will be cross site.
-  EXPECT_EQ(r->isolation_info().top_frame_origin(),
-            url::Origin::Create(GURL("https://a.com/")));
-
-  r->set_isolation_info_from_network_anonymization_key(populated_same_site_nak);
-  EXPECT_TRUE(r->is_created_from_network_anonymization_key());
-  EXPECT_EQ(r->isolation_info().network_anonymization_key(),
-            populated_same_site_nak);
-  EXPECT_TRUE(r->isolation_info().IsEqualForTesting(
-      expected_isolation_info_populated_same_site_nak));
-
-  r->set_isolation_info_from_network_anonymization_key(empty_nak);
-  EXPECT_TRUE(r->is_created_from_network_anonymization_key());
-  EXPECT_TRUE(r->load_flags() & LOAD_DISABLE_CACHE);
-  EXPECT_EQ(r->isolation_info().network_anonymization_key(), empty_nak);
-  EXPECT_TRUE(r->isolation_info().IsEqualForTesting(net::IsolationInfo()));
-  r->Start();
-  d.RunUntilComplete();
-}
 
 TEST_F(URLRequestTest, SetIsolationInfoFromNakTripleNikDoubleNak) {
   base::test::ScopedFeatureList scoped_feature_list_;
-  std::vector<base::test::FeatureRef> enabled_features = {};
-  std::vector<base::test::FeatureRef> disabled_features = {
-      net::features::kForceIsolationInfoFrameOriginToTopLevelFrame,
-      net::features::kEnableCrossSiteFlagNetworkAnonymizationKey};
-  scoped_feature_list_.InitWithFeatures(enabled_features, disabled_features);
+  scoped_feature_list_.InitAndDisableFeature(
+      net::features::kEnableCrossSiteFlagNetworkAnonymizationKey);
 
   TestDelegate d;
   SchemefulSite site_a = SchemefulSite(GURL("https://a.com/"));
@@ -13068,11 +13006,8 @@
 TEST_F(URLRequestTest,
        SetIsolationInfoFromNakTripleNikDoubleWithCrossSiteFlagNak) {
   base::test::ScopedFeatureList scoped_feature_list_;
-  std::vector<base::test::FeatureRef> enabled_features = {
-      net::features::kEnableCrossSiteFlagNetworkAnonymizationKey};
-  std::vector<base::test::FeatureRef> disabled_features = {
-      net::features::kForceIsolationInfoFrameOriginToTopLevelFrame};
-  scoped_feature_list_.InitWithFeatures(enabled_features, disabled_features);
+  scoped_feature_list_.InitAndEnableFeature(
+      net::features::kEnableCrossSiteFlagNetworkAnonymizationKey);
 
   TestDelegate d;
   SchemefulSite site_a = SchemefulSite(GURL("https://a.com/"));
@@ -13239,26 +13174,23 @@
 
 namespace {
 
+// `EnabledFeatureFlagsTestingParam ` allows enabling and disabling
+// the feature flags that control the key schemes for NetworkAnonymizationKey.
+// This allows us to test the possible combinations of flags that will be
+// allowed for experimentation.
+//
+// Presently, only one flag is used, but future experiments will add more.
 struct EnabledFeatureFlagsTestingParam {
+  // True = 2.5-keyed NAK, false = double-keyed NAK.
   const bool enable_cross_site_flag_network_anonymization_key;
-  const bool enable_double_key_network_isolation_key;
 };
 
 const EnabledFeatureFlagsTestingParam kFlagsParam[] = {
-    // 0. Double-keying is enabled for both IsolationInfo and
-    // NetworkAnonymizationKey.
-    {/*enable_cross_site_flag_network_anonymization_key=*/false,
-     /*enable_double_key_network_isolation_key=*/true},
+    // 0. Double-keying is enabled for NetworkAnonymizationKey.
+    {/*enable_cross_site_flag_network_anonymization_key=*/false},
 
-    // 1. Triple-keying is enabled for IsolationInfo and double-keying is
-    // enabled for NetworkAnonymizationKey.
-    {/*enable_cross_site_flag_network_anonymization_key=*/false,
-     /*enable_double_key_network_isolation_key=*/false},
-
-    // 2. Triple-keying is enabled for IsolationInfo and double-keying +
-    // cross-site-bit is enabled for NetworkAnonymizationKey.
-    {/*enable_cross_site_flag_network_anonymization_key=*/true,
-     /*enable_double_key_network_isolation_key=*/false}};
+    // 1. Double-keying + cross-site-bit is enabled for NetworkAnonymizationKey.
+    {/*enable_cross_site_flag_network_anonymization_key=*/true}};
 
 }  // namespace
 
@@ -13272,14 +13204,6 @@
         net::features::kPartitionSSLSessionsByNetworkIsolationKey};
     std::vector<base::test::FeatureRef> disabled_features = {};
 
-    if (IsDoubleKeyNetworkIsolationKeyEnabled()) {
-      enabled_features.push_back(
-          net::features::kForceIsolationInfoFrameOriginToTopLevelFrame);
-    } else {
-      disabled_features.push_back(
-          net::features::kForceIsolationInfoFrameOriginToTopLevelFrame);
-    }
-
     if (IsCrossSiteFlagEnabled()) {
       enabled_features.push_back(
           net::features::kEnableCrossSiteFlagNetworkAnonymizationKey);
@@ -13291,10 +13215,6 @@
     scoped_feature_list_.InitWithFeatures(enabled_features, disabled_features);
   }
 
-  bool IsDoubleKeyNetworkIsolationKeyEnabled() const {
-    return GetParam().enable_double_key_network_isolation_key;
-  }
-
   bool IsCrossSiteFlagEnabled() const {
     return GetParam().enable_cross_site_flag_network_anonymization_key;
   }
diff --git a/remoting/base/BUILD.gn b/remoting/base/BUILD.gn
index d31748c..e390acc5 100644
--- a/remoting/base/BUILD.gn
+++ b/remoting/base/BUILD.gn
@@ -166,6 +166,7 @@
   if (is_mac) {
     sources += [ "breakpad_mac.mm" ]
     deps += [ "//third_party/breakpad" ]
+    frameworks = [ "Foundation.framework" ]
   }
 
   if (is_win) {
diff --git a/sandbox/policy/features.cc b/sandbox/policy/features.cc
index 00bf6861..b060d9d 100644
--- a/sandbox/policy/features.cc
+++ b/sandbox/policy/features.cc
@@ -74,7 +74,7 @@
 // as controlled by CanCacheSandboxPolicy().
 BASE_FEATURE(kCacheMacSandboxProfiles,
              "CacheMacSandboxProfiles",
-             base::FEATURE_DISABLED_BY_DEFAULT);
+             base::FEATURE_ENABLED_BY_DEFAULT);
 #endif  // BUILDFLAG(IS_MAC)
 
 bool IsNetworkSandboxEnabled() {
diff --git a/services/network/public/cpp/isolation_info_mojom_traits.h b/services/network/public/cpp/isolation_info_mojom_traits.h
index a3173d52..376c5cea 100644
--- a/services/network/public/cpp/isolation_info_mojom_traits.h
+++ b/services/network/public/cpp/isolation_info_mojom_traits.h
@@ -44,10 +44,10 @@
 
   static const absl::optional<url::Origin>& frame_origin(
       const net::IsolationInfo& input) {
-    return base::FeatureList::IsEnabled(
-               net::features::kForceIsolationInfoFrameOriginToTopLevelFrame)
-               ? input.top_frame_origin()
-               : input.frame_origin();
+    // This is trivially true right now, but crbug.com/1414808 will involve
+    // an experiment where this may be false.
+    CHECK(net::IsolationInfo::IsFrameSiteEnabled());
+    return input.frame_origin();
   }
 
   static const absl::optional<base::UnguessableToken>& nonce(
diff --git a/services/network/public/cpp/network_switches.cc b/services/network/public/cpp/network_switches.cc
index f774c06a8..718d5eb 100644
--- a/services/network/public/cpp/network_switches.cc
+++ b/services/network/public/cpp/network_switches.cc
@@ -17,6 +17,8 @@
 // causing them to attempt an unauthenticated SSL/TLS session. This is intended
 // for use when testing various service URLs (eg: kPromoServerURL, kSbURLPrefix,
 // kSyncServiceURL, etc).
+// TODO(crbug.com/1417189): Remove this flag if the alternative solution
+// implemented for crbug.com/1221565 covers all needs.
 const char kIgnoreUrlFetcherCertRequests[] = "ignore-urlfetcher-cert-requests";
 
 // A set of public key hashes for which to ignore certificate-related errors.
diff --git a/sql/README.md b/sql/README.md
index 999ad05..1423bee3 100644
--- a/sql/README.md
+++ b/sql/README.md
@@ -151,7 +151,7 @@
 storage costs. This is because indexes for `WITHOUT ROWID` tables enjoy
 [a space optimization](https://sqlite.org/fileformat2.html#representation_of_sql_indices)
 where columns in both the primary key and the index key are not stored twice in
-B-tree nodes.
+B-tree nodes. Note that data in such tables cannot be recovered by `sql::Recovery`.
 
 
 ### Statement execution model {#query-model}
diff --git a/testing/buildbot/chromium.chromiumos.json b/testing/buildbot/chromium.chromiumos.json
index 76b4a0a..676de0d9 100644
--- a/testing/buildbot/chromium.chromiumos.json
+++ b/testing/buildbot/chromium.chromiumos.json
@@ -5818,9 +5818,9 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.interactive_ui_tests.filter;../../testing/buildbot/filters/linux-lacros.interactive_ui_tests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v112.0.5609.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v112.0.5610.0/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 112.0.5609.0",
+        "description": "Run with ash-chrome version 112.0.5610.0",
         "isolate_profile_data": true,
         "merge": {
           "args": [],
@@ -5832,8 +5832,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v112.0.5609.0",
-              "revision": "version:112.0.5609.0"
+              "location": "lacros_version_skew_tests_v112.0.5610.0",
+              "revision": "version:112.0.5610.0"
             }
           ],
           "dimension_sets": [
@@ -5989,9 +5989,9 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v112.0.5609.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v112.0.5610.0/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 112.0.5609.0",
+        "description": "Run with ash-chrome version 112.0.5610.0",
         "isolate_profile_data": true,
         "merge": {
           "args": [],
@@ -6003,8 +6003,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v112.0.5609.0",
-              "revision": "version:112.0.5609.0"
+              "location": "lacros_version_skew_tests_v112.0.5610.0",
+              "revision": "version:112.0.5610.0"
             }
           ],
           "dimension_sets": [
@@ -6141,9 +6141,9 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v112.0.5609.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v112.0.5610.0/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 112.0.5609.0",
+        "description": "Run with ash-chrome version 112.0.5610.0",
         "isolate_profile_data": true,
         "merge": {
           "args": [],
@@ -6155,8 +6155,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v112.0.5609.0",
-              "revision": "version:112.0.5609.0"
+              "location": "lacros_version_skew_tests_v112.0.5610.0",
+              "revision": "version:112.0.5610.0"
             }
           ],
           "dimension_sets": [
diff --git a/testing/buildbot/chromium.coverage.json b/testing/buildbot/chromium.coverage.json
index 256dabe..890832a 100644
--- a/testing/buildbot/chromium.coverage.json
+++ b/testing/buildbot/chromium.coverage.json
@@ -20507,9 +20507,9 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.interactive_ui_tests.filter;../../testing/buildbot/filters/linux-lacros.interactive_ui_tests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v112.0.5609.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v112.0.5610.0/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 112.0.5609.0",
+        "description": "Run with ash-chrome version 112.0.5610.0",
         "isolate_profile_data": true,
         "merge": {
           "args": [],
@@ -20521,8 +20521,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v112.0.5609.0",
-              "revision": "version:112.0.5609.0"
+              "location": "lacros_version_skew_tests_v112.0.5610.0",
+              "revision": "version:112.0.5610.0"
             }
           ],
           "dimension_sets": [
@@ -20678,9 +20678,9 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v112.0.5609.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v112.0.5610.0/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 112.0.5609.0",
+        "description": "Run with ash-chrome version 112.0.5610.0",
         "isolate_profile_data": true,
         "merge": {
           "args": [],
@@ -20692,8 +20692,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v112.0.5609.0",
-              "revision": "version:112.0.5609.0"
+              "location": "lacros_version_skew_tests_v112.0.5610.0",
+              "revision": "version:112.0.5610.0"
             }
           ],
           "dimension_sets": [
@@ -20830,9 +20830,9 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v112.0.5609.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v112.0.5610.0/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 112.0.5609.0",
+        "description": "Run with ash-chrome version 112.0.5610.0",
         "isolate_profile_data": true,
         "merge": {
           "args": [],
@@ -20844,8 +20844,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v112.0.5609.0",
-              "revision": "version:112.0.5609.0"
+              "location": "lacros_version_skew_tests_v112.0.5610.0",
+              "revision": "version:112.0.5610.0"
             }
           ],
           "dimension_sets": [
diff --git a/testing/buildbot/chromium.fyi.json b/testing/buildbot/chromium.fyi.json
index 28ddee73..304d551 100644
--- a/testing/buildbot/chromium.fyi.json
+++ b/testing/buildbot/chromium.fyi.json
@@ -57176,9 +57176,9 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.interactive_ui_tests.filter;../../testing/buildbot/filters/linux-lacros.interactive_ui_tests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v112.0.5609.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v112.0.5610.0/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 112.0.5609.0",
+        "description": "Run with ash-chrome version 112.0.5610.0",
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -57189,8 +57189,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v112.0.5609.0",
-              "revision": "version:112.0.5609.0"
+              "location": "lacros_version_skew_tests_v112.0.5610.0",
+              "revision": "version:112.0.5610.0"
             }
           ],
           "dimension_sets": [
@@ -57347,9 +57347,9 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v112.0.5609.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v112.0.5610.0/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 112.0.5609.0",
+        "description": "Run with ash-chrome version 112.0.5610.0",
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -57360,8 +57360,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v112.0.5609.0",
-              "revision": "version:112.0.5609.0"
+              "location": "lacros_version_skew_tests_v112.0.5610.0",
+              "revision": "version:112.0.5610.0"
             }
           ],
           "dimension_sets": [
@@ -57499,9 +57499,9 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v112.0.5609.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v112.0.5610.0/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 112.0.5609.0",
+        "description": "Run with ash-chrome version 112.0.5610.0",
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -57512,8 +57512,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v112.0.5609.0",
-              "revision": "version:112.0.5609.0"
+              "location": "lacros_version_skew_tests_v112.0.5610.0",
+              "revision": "version:112.0.5610.0"
             }
           ],
           "dimension_sets": [
@@ -59037,9 +59037,9 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.interactive_ui_tests.filter;../../testing/buildbot/filters/linux-lacros.interactive_ui_tests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v112.0.5609.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v112.0.5610.0/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 112.0.5609.0",
+        "description": "Run with ash-chrome version 112.0.5610.0",
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -59050,8 +59050,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v112.0.5609.0",
-              "revision": "version:112.0.5609.0"
+              "location": "lacros_version_skew_tests_v112.0.5610.0",
+              "revision": "version:112.0.5610.0"
             }
           ],
           "dimension_sets": [
@@ -59208,9 +59208,9 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v112.0.5609.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v112.0.5610.0/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 112.0.5609.0",
+        "description": "Run with ash-chrome version 112.0.5610.0",
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -59221,8 +59221,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v112.0.5609.0",
-              "revision": "version:112.0.5609.0"
+              "location": "lacros_version_skew_tests_v112.0.5610.0",
+              "revision": "version:112.0.5610.0"
             }
           ],
           "dimension_sets": [
@@ -59360,9 +59360,9 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v112.0.5609.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v112.0.5610.0/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 112.0.5609.0",
+        "description": "Run with ash-chrome version 112.0.5610.0",
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -59373,8 +59373,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v112.0.5609.0",
-              "revision": "version:112.0.5609.0"
+              "location": "lacros_version_skew_tests_v112.0.5610.0",
+              "revision": "version:112.0.5610.0"
             }
           ],
           "dimension_sets": [
@@ -60146,9 +60146,9 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.interactive_ui_tests.filter;../../testing/buildbot/filters/linux-lacros.interactive_ui_tests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v112.0.5609.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v112.0.5610.0/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 112.0.5609.0",
+        "description": "Run with ash-chrome version 112.0.5610.0",
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -60159,8 +60159,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v112.0.5609.0",
-              "revision": "version:112.0.5609.0"
+              "location": "lacros_version_skew_tests_v112.0.5610.0",
+              "revision": "version:112.0.5610.0"
             }
           ],
           "dimension_sets": [
diff --git a/testing/buildbot/chromium.memory.json b/testing/buildbot/chromium.memory.json
index 445204e..121f628 100644
--- a/testing/buildbot/chromium.memory.json
+++ b/testing/buildbot/chromium.memory.json
@@ -18533,12 +18533,12 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.interactive_ui_tests.filter;../../testing/buildbot/filters/linux-lacros.interactive_ui_tests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v112.0.5609.0/test_ash_chrome",
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v112.0.5610.0/test_ash_chrome",
           "--test-launcher-print-test-stdio=always",
           "--combine-ash-logs-on-bots",
           "--asan-symbolize-output"
         ],
-        "description": "Run with ash-chrome version 112.0.5609.0",
+        "description": "Run with ash-chrome version 112.0.5610.0",
         "isolate_profile_data": true,
         "merge": {
           "args": [],
@@ -18550,8 +18550,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v112.0.5609.0",
-              "revision": "version:112.0.5609.0"
+              "location": "lacros_version_skew_tests_v112.0.5610.0",
+              "revision": "version:112.0.5610.0"
             }
           ],
           "dimension_sets": [
@@ -18724,12 +18724,12 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v112.0.5609.0/test_ash_chrome",
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v112.0.5610.0/test_ash_chrome",
           "--test-launcher-print-test-stdio=always",
           "--combine-ash-logs-on-bots",
           "--asan-symbolize-output"
         ],
-        "description": "Run with ash-chrome version 112.0.5609.0",
+        "description": "Run with ash-chrome version 112.0.5610.0",
         "isolate_profile_data": true,
         "merge": {
           "args": [],
@@ -18741,8 +18741,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v112.0.5609.0",
-              "revision": "version:112.0.5609.0"
+              "location": "lacros_version_skew_tests_v112.0.5610.0",
+              "revision": "version:112.0.5610.0"
             }
           ],
           "dimension_sets": [
@@ -18891,12 +18891,12 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v112.0.5609.0/test_ash_chrome",
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v112.0.5610.0/test_ash_chrome",
           "--test-launcher-print-test-stdio=always",
           "--combine-ash-logs-on-bots",
           "--asan-symbolize-output"
         ],
-        "description": "Run with ash-chrome version 112.0.5609.0",
+        "description": "Run with ash-chrome version 112.0.5610.0",
         "isolate_profile_data": true,
         "merge": {
           "args": [],
@@ -18908,8 +18908,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v112.0.5609.0",
-              "revision": "version:112.0.5609.0"
+              "location": "lacros_version_skew_tests_v112.0.5610.0",
+              "revision": "version:112.0.5610.0"
             }
           ],
           "dimension_sets": [
diff --git a/testing/buildbot/variants.pyl b/testing/buildbot/variants.pyl
index be7c7d3..9bde9c7 100644
--- a/testing/buildbot/variants.pyl
+++ b/testing/buildbot/variants.pyl
@@ -22,16 +22,16 @@
   },
   'LACROS_VERSION_SKEW_CANARY': {
     'args': [
-      '--ash-chrome-path-override=../../lacros_version_skew_tests_v112.0.5609.0/test_ash_chrome',
+      '--ash-chrome-path-override=../../lacros_version_skew_tests_v112.0.5610.0/test_ash_chrome',
     ],
-    'description': 'Run with ash-chrome version 112.0.5609.0',
+    'description': 'Run with ash-chrome version 112.0.5610.0',
     'identifier': 'Lacros version skew testing ash canary',
     'swarming': {
       'cipd_packages': [
         {
           'cipd_package': 'chromium/testing/linux-ash-chromium/x86_64/ash.zip',
-          'location': 'lacros_version_skew_tests_v112.0.5609.0',
-          'revision': 'version:112.0.5609.0',
+          'location': 'lacros_version_skew_tests_v112.0.5610.0',
+          'revision': 'version:112.0.5610.0',
         },
       ],
     },
diff --git a/testing/variations/fieldtrial_testing_config.json b/testing/variations/fieldtrial_testing_config.json
index a2941ac..83748b8 100644
--- a/testing/variations/fieldtrial_testing_config.json
+++ b/testing/variations/fieldtrial_testing_config.json
@@ -8690,9 +8690,7 @@
                         "PartitionSSLSessionsByNetworkIsolationKey",
                         "SplitHostCacheByNetworkIsolationKey"
                     ],
-                    "disable_features": [
-                        "ForceIsolationInfoFrameOriginToTopLevelFrame"
-                    ]
+                    "disable_features": []
                 }
             ]
         }
diff --git a/third_party/blink/tools/blinkpy/tool/commands/update_metadata.py b/third_party/blink/tools/blinkpy/tool/commands/update_metadata.py
index d25361100..58956b7 100644
--- a/third_party/blink/tools/blinkpy/tool/commands/update_metadata.py
+++ b/third_party/blink/tools/blinkpy/tool/commands/update_metadata.py
@@ -48,6 +48,7 @@
 path_finder.bootstrap_wpt_imports()
 from manifest import manifest as wptmanifest
 from wptrunner import manifestupdate, metadata, testloader, wpttest
+from wptrunner.wptmanifest import node as wptnode
 from wptrunner.wptmanifest.backends import conditional
 from wptrunner.wptmanifest.parser import ParseError
 
@@ -738,6 +739,7 @@
         if modified:
             if self._bug:
                 self._add_bug_url(expected)
+            sort_metadata_ast(expected.node)
             if not self._dry_run:
                 metadata.write_new_expected(test_file.metadata_path, expected)
         return modified
@@ -748,6 +750,29 @@
                 test_id_section.set('bug', 'crbug.com/%d' % self._bug)
 
 
+def sort_metadata_ast(node: wptnode.DataNode) -> None:
+    """Sort the metadata abstract syntax tree to create a stable rendering.
+
+    Since keys/sections are identified by unique names within their block, their
+    ordering within the file do not matter. Sorting avoids creating spurious
+    diffs after serialization.
+
+    Note:
+        This mutates the given node. Create a copy with `node.copy()` if you
+        wish to keep the original.
+    """
+    assert all(
+        isinstance(child, (wptnode.DataNode, wptnode.KeyValueNode))
+        for child in node.children), node
+    # Put keys first, then child sections. Keys and child sections are sorted
+    # alphabetically within their respective groups.
+    node.children.sort(key=lambda child: (bool(
+        isinstance(child, wptnode.DataNode)), child.data or ''))
+    for child in node.children:
+        if isinstance(child, wptnode.DataNode):
+            sort_metadata_ast(child)
+
+
 def _compose(f, g):
     return lambda *args, **kwargs: f(g(*args, **kwargs))
 
diff --git a/third_party/blink/tools/blinkpy/tool/commands/update_metadata_unittest.py b/third_party/blink/tools/blinkpy/tool/commands/update_metadata_unittest.py
index 776ccc2..5d24ab6 100644
--- a/third_party/blink/tools/blinkpy/tool/commands/update_metadata_unittest.py
+++ b/third_party/blink/tools/blinkpy/tool/commands/update_metadata_unittest.py
@@ -19,12 +19,13 @@
     UpdateMetadata,
     MetadataUpdater,
     load_and_update_manifests,
+    sort_metadata_ast,
 )
 from blinkpy.web_tests.builder_list import BuilderList
 
 path_finder.bootstrap_wpt_imports()
 from manifest.manifest import Manifest
-from wptrunner import metadata
+from wptrunner import metadata, wptmanifest
 
 
 class BaseUpdateMetadataTest(LoggingTestCase):
@@ -1107,6 +1108,43 @@
                 expected: FAIL
             """)
 
+    def test_stable_rendering(self):
+        buf = io.BytesIO(
+            textwrap.dedent("""\
+                [variant.html?foo=baz]
+                  [subtest 2]
+                    expected:
+                      if os == "win": FAIL
+                      if os == "mac": FAIL
+                    disabled: @False
+                  [subtest 1]
+                  expected: [OK, CRASH]
+
+                bug: crbug.com/123
+
+                [variant.html?foo=bar/abc]
+                """).encode())
+        ast = wptmanifest.parse(buf)
+        sort_metadata_ast(ast)
+        # Unlike keys/sections, the ordering of conditions is significant, so
+        # they should not be sorted.
+        self.assertEqual(
+            wptmanifest.serialize(ast),
+            textwrap.dedent("""\
+                bug: crbug.com/123
+                [variant.html?foo=bar/abc]
+
+                [variant.html?foo=baz]
+                  expected: [OK, CRASH]
+                  [subtest 1]
+
+                  [subtest 2]
+                    disabled: @False
+                    expected:
+                      if os == "win": FAIL
+                      if os == "mac": FAIL
+                """))
+
 
 class UpdateMetadataArgumentParsingTest(unittest.TestCase):
     def setUp(self):
diff --git a/third_party/blink/tools/run_wpt_tests.py b/third_party/blink/tools/run_wpt_tests.py
index dcf2f340f..470c209 100755
--- a/third_party/blink/tools/run_wpt_tests.py
+++ b/third_party/blink/tools/run_wpt_tests.py
@@ -15,6 +15,7 @@
 import re
 import shutil
 import sys
+import warnings
 from typing import List, Optional, Tuple
 
 from blinkpy.common import exit_codes
@@ -32,9 +33,9 @@
 
 import mozlog
 from scripts import common
-from wptrunner import wptcommandline
+from wptrunner import wptcommandline, wptlogging
 
-logger = logging.getLogger(__name__)
+logger = logging.getLogger('run_wpt_tests')
 
 UPSTREAM_GIT_URL = 'https://github.com/web-platform-tests/wpt.git'
 
@@ -50,10 +51,44 @@
     from pylib.local.emulator import avd
     _ANDROID_ENABLED = True
 except ImportError:
-    logger.warning('Android tools not found')
     _ANDROID_ENABLED = False
 
 
+def _make_log_enabled_grouping_formatter():
+    # Make a grouping log formatter that shows regular log messages:
+    #   WARNING Unsupported test type wdspec for product content_shell
+    #
+    # Activating logs dynamically with:
+    #   StructuredLogger.send_message('show_logs', 'on')
+    # appears buggy. This factory exists as a workaround.
+    grouping_formatter = mozlog.formatters.GroupingFormatter()
+    grouping_formatter.message_handler.handle_message('show_logs', 'on')
+    return grouping_formatter
+
+
+mozlog.commandline.log_formatters['grouped'] = (
+    _make_log_enabled_grouping_formatter,
+    mozlog.commandline.log_formatters['grouped'][1],
+)
+
+
+class StructuredLogAdapter(logging.Handler):
+    def __init__(self, logger, *args, **kwargs):
+        super().__init__(*args, **kwargs)
+        self._logger = logger
+        self._fallback_handler = logging.StreamHandler()
+        self._fallback_handler.setFormatter(
+            logging.Formatter('%(name)s %(levelname)s %(message)s'))
+
+    def emit(self, record):
+        log = getattr(self._logger, record.levelname.lower(),
+                      self._logger.debug)
+        try:
+            log(record.getMessage(), component=record.name)
+        except mozlog.structuredlog.LoggerShutdownError:
+            self._fallback_handler.emit(record)
+
+
 PARAMETER_DENYLIST = {
     # Parameters specific to non-Chromium vendors.
     '--prefs-root',
@@ -184,6 +219,8 @@
         self.add_configuration_arguments(parser)
         if _ANDROID_ENABLED:
             self.add_android_arguments(parser)
+        else:
+            warnings.warn('Android tools not found')
         # Nightly installation is not supported, so just add defaults.
         parser.set_defaults(
             prompt=False,
@@ -196,8 +233,9 @@
 
     def _check_and_update_options(self, options):
         """Postprocess options, some of which can depend on each other."""
-        self._check_and_update_upstream_options(options)
+        # Set up logging as early as possible.
         self._check_and_update_output_options(options)
+        self._check_and_update_upstream_options(options)
         self._check_and_update_config_options(options)
         self._check_and_update_sharding_options(options)
         # TODO(crbug/1316055): Enable tombstone with '--stackwalk-binary' and
@@ -213,25 +251,17 @@
         options.manifest_download = False
 
     def _check_and_update_output_options(self, options):
-        if options.verbose >= 2:
+        if options.verbose >= 1:
             options.log_mach = '-'
             options.log_mach_level = 'info'
             options.log_mach_verbose = True
-        if options.verbose >= 3:
+        if options.verbose >= 2:
             options.log_mach_level = 'debug'
-        if options.verbose >= 4:
+        if options.verbose >= 3:
             options.webdriver_args.extend([
                 '--verbose',
                 '--log-path=-',
             ])
-        # Set up logging within `run_wpt_tests` as soon as possible.
-        # TODO(crbug.com/1356318): Pipe stdlib `logging` records into `mozlog`
-        # for a unified output format.
-        logging.basicConfig(
-            level=self.log_level(options),
-            # Align level name for easier reading.
-            format='%(asctime)s [%(levelname)-8s] %(name)s: %(message)s',
-            force=True)
 
         output_dir = self.path_from_output_dir(options.target)
         if not self.fs.isdir(output_dir):
@@ -252,6 +282,11 @@
                 filename = self.fs.abspath(filename)
                 setattr(options, dest, [mozlog.commandline.log_file(filename)])
 
+        options.log = wptlogging.setup(dict(vars(options)),
+                                       {'grouped': sys.stdout})
+        logging.root.handlers.clear()
+        logging.root.addHandler(StructuredLogAdapter(options.log))
+
     def _check_and_update_config_options(self, options: argparse.Namespace):
         options.webdriver_args.extend([
             '--enable-chrome-logs',
@@ -269,8 +304,15 @@
             '--force-fieldtrial-params='
             'DownloadServiceStudy.Enabled:start_up_delay_ms/0',
         ])
-        if _has_explicit_tests(options):
-            options.retry_unexpected = 0
+        if options.retry_unexpected is None:
+            if _has_explicit_tests(options):
+                options.retry_unexpected = 0
+                logger.warning('Tests explicitly specified; disabling retries')
+            else:
+                options.retry_unexpected = 3
+                logger.warning(
+                    'Tests not explicitly specified; '
+                    'using %d retries', options.retry_unexpected)
         if not options.mojojs_path:
             options.mojojs_path = self.path_from_output_dir(
                 options.target, 'gen')
@@ -280,10 +322,19 @@
         if options.flag_specific:
             configs = self.port.flag_specific_configs()
             args, smoke_file_name = configs[options.flag_specific]
+            logger.info('Running with flag-specific arguments: "%s"',
+                        ' '.join(args))
             options.binary_args.extend(args)
             if smoke_file_name and not _has_explicit_tests(options):
                 options.include_file = self.path_finder.path_from_web_tests(
                     smoke_file_name)
+                logger.info(
+                    'Tests not explicitly specified; '
+                    'running tests from web_tests/%s', smoke_file_name)
+            elif smoke_file_name:
+                logger.warning(
+                    'Tests explicitly specified; '
+                    'not running tests from web_tests/%s', smoke_file_name)
 
     def _check_and_update_upstream_options(self, options: argparse.Namespace):
         if options.use_upstream_wpt:
@@ -312,9 +363,8 @@
             options.this_chunk = self._shard_index + 1
         if self._total_shards is not None:
             options.total_chunks = self._total_shards
-        if options.this_chunk and options.total_chunks:
-            logger.info('Selecting tests for shard %d/%d', options.this_chunk,
-                        options.total_chunks)
+        logger.info('Selecting tests for shard %d/%d', options.this_chunk,
+                    options.total_chunks)
         # The default sharding strategy is to shard by directory. But
         # we want to hash each test to determine which shard runs it.
         # This allows running individual directories that have few
@@ -324,13 +374,6 @@
     def path_from_output_dir(self, *parts):
         return self.path_finder.path_from_chromium_base('out', *parts)
 
-    def log_level(self, options):
-        if options.verbose >= 2:
-            return logging.DEBUG
-        if options.verbose >= 1:
-            return logging.INFO
-        return logging.WARNING
-
     def run_tests(self, options: argparse.Namespace) -> int:
         with contextlib.ExitStack() as stack:
             tmp_dir = stack.enter_context(self.fs.mkdtemp())
@@ -500,7 +543,7 @@
             '--isolated-script-test-launcher-retry-limit',
             metavar='RETRIES',
             type=lambda value: max(0, int(value)),
-            default=3,
+            default=None,
             help=(
                 'Maximum number of times to rerun unexpectedly failed tests. '
                 'Defaults to 3 unless given an explicit list of tests to run.'
diff --git a/third_party/blink/web_tests/TestExpectations b/third_party/blink/web_tests/TestExpectations
index d588c9da..35b157e3 100644
--- a/third_party/blink/web_tests/TestExpectations
+++ b/third_party/blink/web_tests/TestExpectations
@@ -6742,6 +6742,8 @@
 # Test is flaky because of test_driver.bless, see crbug.com/1066891
 crbug.com/1382865 external/wpt/storage-access-api/requestStorageAccess-cross-origin-iframe.sub.https.window.html [ Failure Pass ]
 crbug.com/1382865 external/wpt/storage-access-api/requestStorageAccess-nested-cross-origin-iframe.sub.https.window.html [ Failure Pass ]
+crbug.com/1382865 external/wpt/storage-access-api/requestStorageAccess-cross-site-iframe.sub.https.window.html [ Failure Pass ]
+crbug.com/1382865 external/wpt/storage-access-api/requestStorageAccess-nested-cross-site-iframe.sub.https.window.html [ Failure Pass ]
 
 # Test is not yet supported in Chrome.
 crbug.com/1382865 external/wpt/storage-access-api/requestStorageAccess-cross-origin-sibling-iframes.sub.https.window.html [ Failure Pass ]
diff --git a/third_party/blink/web_tests/external/WPT_BASE_MANIFEST_8.json b/third_party/blink/web_tests/external/WPT_BASE_MANIFEST_8.json
index 9b54939..aafc75b 100644
--- a/third_party/blink/web_tests/external/WPT_BASE_MANIFEST_8.json
+++ b/third_party/blink/web_tests/external/WPT_BASE_MANIFEST_8.json
@@ -264645,16 +264645,6 @@
    }
   },
   "support": {
-   ".cache": {
-    "gitignore2.json": [
-     "24658fcbb8b2f7366ccd91bb977cd3be4719da28",
-     []
-    ],
-    "mtime.json": [
-     "9e6b268c357c24b113ae363db36beb76cb116c89",
-     []
-    ]
-   },
    ".gitignore": [
     "d93e645d547894b50149d3726de2654957b6e06f",
     []
@@ -299309,43 +299299,11 @@
      ],
      "image-set": {
       "image-set-computed.sub-expected.txt": [
-       "78e0027cb8765b6d52250a4a0700b59c112c12b2",
-       []
-      ],
-      "image-set-computed.sub.html.ini": [
-       "f98353bade16d7c70dcaff09ac065b2ec1a928c5",
-       []
-      ],
-      "image-set-conic-gradient-rendering.html.ini": [
-       "95d9cf7bd8d5a0a27d177bc66e8c7c684deabb3d",
-       []
-      ],
-      "image-set-linear-gradient-rendering.html.ini": [
-       "b58c2976a5926a55ad3f1b4c57155c33405f8b0e",
+       "dfc5f32b1eb39996c178765b273c0c6a496dc9c7",
        []
       ],
       "image-set-parsing-expected.txt": [
-       "d2f52d3a8f54a0348c315c04a131391304492c8d",
-       []
-      ],
-      "image-set-parsing.html.ini": [
-       "936aabcf464f334c39966c22ce9863502b5fb42e",
-       []
-      ],
-      "image-set-radial-gradient-rendering.html.ini": [
-       "3fcaf7fed895cbcb82a8a8a3082a86283464619e",
-       []
-      ],
-      "image-set-repeating-conic-gradient-rendering.html.ini": [
-       "f18e1ef9a009dd1d0eb1c76f73654ab469925435",
-       []
-      ],
-      "image-set-repeating-linear-gradient-rendering.html.ini": [
-       "be70642abe0de6d1f01e5cea728d0475998f7177",
-       []
-      ],
-      "image-set-repeating-radial-gradient-rendering.html.ini": [
-       "cfb95cb1d12971431322dce97fd519cbf8c3d8b1",
+       "52390b2af587cee70e05b57e9800bf3e21c8bd3b",
        []
       ],
       "image-set-resolution-001-ref.html": [
@@ -370882,7 +370840,7 @@
       []
      ],
      "mock-direct-sockets.js": [
-      "0752850c8a383353c39320da4dc0d3b3df257129",
+      "e2e02453f7f4d446c528051fc44974878150b809",
       []
      ],
      "mock-facedetection.js": [
@@ -659100,7 +659058,7 @@
      },
      "get_active_element": {
       "get.py": [
-       "2b79ebd58485156573d5369372bfe6a8a1587a9f",
+       "1d2960c88c5f587bf2dea86f5b97ad1e5fae9b5c",
        [
         null,
         {}
diff --git a/third_party/blink/web_tests/external/wpt/cookies/resources/set-cookie.py b/third_party/blink/web_tests/external/wpt/cookies/resources/set-cookie.py
index 839f350c..b184087 100644
--- a/third_party/blink/web_tests/external/wpt/cookies/resources/set-cookie.py
+++ b/third_party/blink/web_tests/external/wpt/cookies/resources/set-cookie.py
@@ -21,8 +21,14 @@
 
     name = request.GET[b'name']
     path = request.GET[b'path']
+    samesite = request.GET.get(b'samesite')
+    secure = b'secure' in request.GET
     expiry_year = date.today().year + 1
     cookie = b"%s=1; Path=%s; Expires=09 Jun %d 10:18:14 GMT" % (name, path, expiry_year)
+    if samesite:
+        cookie += b";SameSite=%s" % samesite
+    if secure:
+        cookie += b";Secure"
 
     headers = [
         (b"Content-Type", b"application/json"),
diff --git a/third_party/blink/web_tests/external/wpt/css/css-anchor-position/__dir__.ini b/third_party/blink/web_tests/external/wpt/css/css-anchor-position/__dir__.ini
deleted file mode 100644
index ff88b1e..0000000
--- a/third_party/blink/web_tests/external/wpt/css/css-anchor-position/__dir__.ini
+++ /dev/null
@@ -1,3 +0,0 @@
-bug: crbug.com/1309178
-disabled:
-  if flag_specific == "disable-layout-ng": CSS Anchor Positioning is only implemented in ng.
diff --git a/third_party/blink/web_tests/external/wpt/css/css-contain/container-queries/__dir__.ini b/third_party/blink/web_tests/external/wpt/css/css-contain/container-queries/__dir__.ini
deleted file mode 100644
index 298ed18..0000000
--- a/third_party/blink/web_tests/external/wpt/css/css-contain/container-queries/__dir__.ini
+++ /dev/null
@@ -1,3 +0,0 @@
-bug: crbug.com/1145970
-disabled:
-  if flag_specific == "disable-layout-ng": Container Queries tests require enabled LayoutNG
diff --git a/third_party/blink/web_tests/external/wpt/css/css-grid/subgrid/__dir__.ini b/third_party/blink/web_tests/external/wpt/css/css-grid/subgrid/__dir__.ini
deleted file mode 100644
index 2e8253ea..0000000
--- a/third_party/blink/web_tests/external/wpt/css/css-grid/subgrid/__dir__.ini
+++ /dev/null
@@ -1,3 +0,0 @@
-bug: crbug.com/618969
-disabled:
-  if flag_specific == "disable-layout-ng": Subgrid will not be implemented in legacy.
diff --git a/third_party/blink/web_tests/external/wpt/css/css-layout-api/__dir__.ini b/third_party/blink/web_tests/external/wpt/css/css-layout-api/__dir__.ini
deleted file mode 100644
index 4a9d009..0000000
--- a/third_party/blink/web_tests/external/wpt/css/css-layout-api/__dir__.ini
+++ /dev/null
@@ -1,3 +0,0 @@
-bug: crbug.com/591099
-disabled:
-  if flag_specific == "disable-layout-ng": external/wpt/css/css-layout-api/
diff --git a/third_party/blink/web_tests/external/wpt/html/semantics/popovers/popover-attribute-basic.html b/third_party/blink/web_tests/external/wpt/html/semantics/popovers/popover-attribute-basic.html
index 0abba9c4..e9167aae 100644
--- a/third_party/blink/web_tests/external/wpt/html/semantics/popovers/popover-attribute-basic.html
+++ b/third_party/blink/web_tests/external/wpt/html/semantics/popovers/popover-attribute-basic.html
@@ -237,15 +237,6 @@
     assert_false(popover.matches(':open'),'From "auto" to "invalid" (which is interpreted as "manual") should close the popover');
   },'Changing attribute values should close open popovers');
 
-  function modalPseudoSupported() {
-    try {
-      document.createElement('dialog').matches(':modal');
-      return true; // No exception means :modal is supported.
-    } catch(e) {
-      return false;
-    }
-  }
-
   const validTypes = ["auto","manual"];
   validTypes.forEach(type => {
     test((t) => {
@@ -259,16 +250,14 @@
       assert_false(popover.matches(':open'));
     },`Removing a visible popover=${type} element from the document should close the popover`);
 
-    if (modalPseudoSupported()) {
-      test((t) => {
-        const popover = createPopover(t);
-        popover.setAttribute('popover',type);
-        popover.showPopover();
-        assert_true(popover.matches(':open'));
-        assert_false(popover.matches(':modal'));
-        popover.hidePopover();
-      },`A showing popover=${type} does not match :modal`);
-    }
+    test((t) => {
+      const popover = createPopover(t);
+      popover.setAttribute('popover',type);
+      popover.showPopover();
+      assert_true(popover.matches(':open'));
+      assert_false(popover.matches(':modal'));
+      popover.hidePopover();
+    },`A showing popover=${type} does not match :modal`);
   });
 
   test((t) => {
diff --git a/third_party/blink/web_tests/external/wpt/mathml/__dir__.ini b/third_party/blink/web_tests/external/wpt/mathml/__dir__.ini
deleted file mode 100644
index d5dc646..0000000
--- a/third_party/blink/web_tests/external/wpt/mathml/__dir__.ini
+++ /dev/null
@@ -1,3 +0,0 @@
-bug: crbug.com/6606
-disabled:
-  if flag_specific == "disable-layout-ng": MathML depends on LayoutNG.
diff --git a/third_party/blink/web_tests/external/wpt/storage-access-api/requestStorageAccess-cross-site-iframe.sub.https.window-expected.txt b/third_party/blink/web_tests/external/wpt/storage-access-api/requestStorageAccess-cross-site-iframe.sub.https.window-expected.txt
new file mode 100644
index 0000000..6046805
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/storage-access-api/requestStorageAccess-cross-site-iframe.sub.https.window-expected.txt
@@ -0,0 +1,8 @@
+This is a testharness.js-based test.
+PASS [cross-site-frame] document.requestStorageAccess() should exist on the document interface
+PASS [cross-site-frame] document.requestStorageAccess() should resolve in top-level frame or otherwise reject with a NotAllowedError with no user gesture
+FAIL [cross-site-frame] document.requestStorageAccess() should be resolved when called properly with a user gesture, and should allow cookie access assert_true: After obtaining storage access, subresource requests from the frame should send and set cookies. expected true got false
+FAIL [cross-site-frame] document.requestStorageAccess() should be rejected with a NotAllowedError without permission grant assert_unreached: Should have rejected: document.requestStorageAccess() call without permission Reached unreachable code
+PASS [cross-site-frame] document.requestStorageAccess() should be rejected with a NotAllowedError with denied permission
+Harness: the test ran to completion.
+
diff --git a/third_party/blink/web_tests/external/wpt/storage-access-api/requestStorageAccess-cross-site-iframe.sub.https.window.js b/third_party/blink/web_tests/external/wpt/storage-access-api/requestStorageAccess-cross-site-iframe.sub.https.window.js
new file mode 100644
index 0000000..f7c35ca2
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/storage-access-api/requestStorageAccess-cross-site-iframe.sub.https.window.js
@@ -0,0 +1,9 @@
+// META: script=helpers.js
+// META: script=/resources/testdriver.js
+// META: script=/resources/testdriver-vendor.js
+'use strict';
+
+(async function() {
+  // Create a test with a single-child cross-site iframe.
+  RunTestsInIFrame('https://{{hosts[alt][www]}}:{{ports[https][0]}}/storage-access-api/resources/requestStorageAccess-iframe.https.html?testCase=cross-site-frame&rootdocument=false');
+})();
diff --git a/third_party/blink/web_tests/external/wpt/storage-access-api/requestStorageAccess-nested-cross-site-iframe.sub.https.window-expected.txt b/third_party/blink/web_tests/external/wpt/storage-access-api/requestStorageAccess-nested-cross-site-iframe.sub.https.window-expected.txt
new file mode 100644
index 0000000..dd76957
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/storage-access-api/requestStorageAccess-nested-cross-site-iframe.sub.https.window-expected.txt
@@ -0,0 +1,8 @@
+This is a testharness.js-based test.
+PASS [nested-cross-site-frame] document.requestStorageAccess() should exist on the document interface
+PASS [nested-cross-site-frame] document.requestStorageAccess() should resolve in top-level frame or otherwise reject with a NotAllowedError with no user gesture
+FAIL [nested-cross-site-frame] document.requestStorageAccess() should be resolved when called properly with a user gesture, and should allow cookie access assert_true: After obtaining storage access, subresource requests from the frame should send and set cookies. expected true got false
+FAIL [nested-cross-site-frame] document.requestStorageAccess() should be rejected with a NotAllowedError without permission grant assert_unreached: Should have rejected: document.requestStorageAccess() call without permission Reached unreachable code
+PASS [nested-cross-site-frame] document.requestStorageAccess() should be rejected with a NotAllowedError with denied permission
+Harness: the test ran to completion.
+
diff --git a/third_party/blink/web_tests/external/wpt/storage-access-api/requestStorageAccess-nested-cross-site-iframe.sub.https.window.js b/third_party/blink/web_tests/external/wpt/storage-access-api/requestStorageAccess-nested-cross-site-iframe.sub.https.window.js
new file mode 100644
index 0000000..f3ac0e8
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/storage-access-api/requestStorageAccess-nested-cross-site-iframe.sub.https.window.js
@@ -0,0 +1,10 @@
+// META: script=helpers.js
+// META: script=/resources/testdriver.js
+// META: script=/resources/testdriver-vendor.js
+'use strict';
+
+(async function() {
+  // Validate the nested-iframe scenario where the cross-site frame
+  // containing the tests is not the first child.
+  RunTestsInNestedIFrame('https://{{hosts[alt][www]}}:{{ports[https][0]}}/storage-access-api/resources/requestStorageAccess-iframe.https.html?testCase=nested-cross-site-frame&rootdocument=false');
+})();
diff --git a/third_party/blink/web_tests/external/wpt/storage-access-api/requestStorageAccess.sub.https.window.js b/third_party/blink/web_tests/external/wpt/storage-access-api/requestStorageAccess.sub.https.window.js
index d673ed3..07fa29d 100644
--- a/third_party/blink/web_tests/external/wpt/storage-access-api/requestStorageAccess.sub.https.window.js
+++ b/third_party/blink/web_tests/external/wpt/storage-access-api/requestStorageAccess.sub.https.window.js
@@ -40,16 +40,27 @@
 }, "[" + testPrefix + "] document.requestStorageAccess() should resolve in top-level frame or otherwise reject with a NotAllowedError with no user gesture");
 
 promise_test(
-    async () => {
-      await test_driver.set_permission(
-          {name: 'storage-access'}, 'granted');
+    async (t) => {
+      await MaybeSetStorageAccess("*", "*", "blocked");
+      await test_driver.set_permission({name: 'storage-access'}, 'granted');
+      t.add_cleanup(async () => {
+        await test_driver.delete_all_cookies();
+      });
 
       await RunCallbackWithGesture(() => document.requestStorageAccess());
+
+      await fetch(`${window.location.origin}/cookies/resources/set-cookie.py?name=cookie&path=/&samesite=None&secure=`)
+          .then((resp) => resp.text());
+      const httpCookies = await fetch(`${window.location.origin}/storage-access-api/resources/echo-cookie-header.py`)
+          .then((resp) => resp.text());
+      assert_true(httpCookies.includes('cookie=1'),
+          'After obtaining storage access, subresource requests from the frame should send and set cookies.');
     },
     '[' + testPrefix +
-        '] document.requestStorageAccess() should be resolved when called properly with a user gesture');
+        '] document.requestStorageAccess() should be resolved when called properly with a user gesture, and ' +
+        'should allow cookie access');
 
-if (testPrefix == 'cross-origin-frame' || testPrefix == 'nested-cross-origin-frame') {
+if (!topLevelDocument && !testPrefix.includes('same-origin')) {
   promise_test(
       async t => {
         await RunCallbackWithGesture(() => {
diff --git a/third_party/blink/web_tests/external/wpt/storage-access-api/resources/echo-cookie-header.py b/third_party/blink/web_tests/external/wpt/storage-access-api/resources/echo-cookie-header.py
new file mode 100644
index 0000000..546e76e
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/storage-access-api/resources/echo-cookie-header.py
@@ -0,0 +1,4 @@
+def main(request, response):
+  cookie_header = request.headers.get(b"Cookie", b"")
+
+  return (200, [], cookie_header)
diff --git a/third_party/blink/web_tests/external/wpt/storage-access-api/resources/embedded_responder.js b/third_party/blink/web_tests/external/wpt/storage-access-api/resources/embedded_responder.js
index 8d27dd39..86319b9d 100644
--- a/third_party/blink/web_tests/external/wpt/storage-access-api/resources/embedded_responder.js
+++ b/third_party/blink/web_tests/external/wpt/storage-access-api/resources/embedded_responder.js
@@ -23,6 +23,7 @@
     case "write document.cookie":
       document.cookie = event.data.cookie;
       reply(undefined);
+      break;
     case "document.cookie":
       reply(document.cookie);
       break;
diff --git a/third_party/blink/web_tests/external/wpt/webdriver/tests/get_active_element/get.py b/third_party/blink/web_tests/external/wpt/webdriver/tests/get_active_element/get.py
index 2b79ebd..1d2960c8 100644
--- a/third_party/blink/web_tests/external/wpt/webdriver/tests/get_active_element/get.py
+++ b/third_party/blink/web_tests/external/wpt/webdriver/tests/get_active_element/get.py
@@ -57,6 +57,17 @@
             <p>Another element</p>
         </body>""")
 
+    # Per spec, autofocus candidates will be
+    # flushed by next paint, so we use rAF here to
+    # ensure the candidates are flushed.
+    session.execute_async_script(
+        """
+        const resolve = arguments[0];
+        window.requestAnimationFrame(function() {
+            window.requestAnimationFrame(resolve);
+        });
+        """
+    )
     response = get_active_element(session)
     element = assert_success(response)
     assert_is_active_element(session, element)
@@ -71,6 +82,17 @@
             <p>Another element</p>
         </body>""")
 
+    # Per spec, autofocus candidates will be
+    # flushed by next paint, so we use rAF here to
+    # ensure the candidates are flushed.
+    session.execute_async_script(
+        """
+        const resolve = arguments[0];
+        window.requestAnimationFrame(function() {
+            window.requestAnimationFrame(resolve);
+        });
+        """
+    )
     response = get_active_element(session)
     element = assert_success(response)
     assert_is_active_element(session, element)
diff --git a/third_party/blink/web_tests/external/wpt/webrtc/__dir__.ini b/third_party/blink/web_tests/external/wpt/webrtc/__dir__.ini
deleted file mode 100644
index 8a6d4ac2..0000000
--- a/third_party/blink/web_tests/external/wpt/webrtc/__dir__.ini
+++ /dev/null
@@ -1,3 +0,0 @@
-bug: crbug.com/840659
-disabled:
-  if flag_specific == "disable-layout-ng": No H.264 decoder support in bot - just skip all webrtc tests.
diff --git a/third_party/blink/web_tests/resources/gesture-util.js b/third_party/blink/web_tests/resources/gesture-util.js
index 5fc7bc4..61a99fff 100644
--- a/third_party/blink/web_tests/resources/gesture-util.js
+++ b/third_party/blink/web_tests/resources/gesture-util.js
@@ -766,9 +766,11 @@
         scroller.scrollLeft == 0) {
       resolve();
     } else {
+      const eventTarget =
+        scroller == document.scrollingElement ? document : scroller;
       scroller.scrollTop = 0;
       scroller.scrollLeft = 0;
-      waitForScrollendEvent(document).then(resolve());
+      waitForScrollendEvent(eventTarget).then(resolve);
     }
   });
 }
diff --git a/third_party/blink/web_tests/wpt_internal/formatted-text/__dir__.ini b/third_party/blink/web_tests/wpt_internal/formatted-text/__dir__.ini
deleted file mode 100644
index 58a48d5..0000000
--- a/third_party/blink/web_tests/wpt_internal/formatted-text/__dir__.ini
+++ /dev/null
@@ -1,3 +0,0 @@
-bug: crbug.com/1176933
-disabled:
-  if flag_specific == "disable-layout-ng": was skipped in 'FlagExpectations/disable-layout-ng'
diff --git a/third_party/dom_distiller_js/protoc_plugins/json_values_converter.py b/third_party/dom_distiller_js/protoc_plugins/json_values_converter.py
index b09240d4..86b9c96 100755
--- a/third_party/dom_distiller_js/protoc_plugins/json_values_converter.py
+++ b/third_party/dom_distiller_js/protoc_plugins/json_values_converter.py
@@ -65,8 +65,9 @@
       # Nothing to write for enums.
 
       self.Output(
-          'static bool ReadFromValue(const base::Value& dict, {generated_class_name}* message) {{\n'
-          '  if (!dict.is_dict()) goto error;\n'
+          'static bool ReadFromValue(const base::Value& dict_value, {generated_class_name}* message) {{\n'
+          '  const base::Value::Dict* dict = dict_value.GetIfDict();\n'
+          '  if (!dict) goto error;\n'
           '',
           generated_class_name=generated_class_name)
 
@@ -82,7 +83,7 @@
           '}}\n'
           '\n'
           'static base::Value WriteToValue(const {generated_class_name}& message) {{\n'
-          '  base::Value dict(base::Value::Type::DICT);\n'
+          '  base::Value::Dict dict;\n'
           '',
           generated_class_name=generated_class_name)
 
@@ -91,7 +92,7 @@
           self.FieldWriteToValue(field_proto)
 
       self.Output(
-          '  return dict;\n'
+          '  return base::Value(std::move(dict));\n'
           '',
           generated_class_name=generated_class_name)
       self.Output('}}')
@@ -123,8 +124,7 @@
           '  field_list.Append(\n'
           '      {inner_class_converter}::WriteToValue(element));\n'
           '}}\n'
-          'dict.SetKey("{field_number}",\n'
-          '            base::Value(std::move(field_list)));\n',
+          'dict.Set("{field_number}", std::move(field_list));\n',
           field_number=field.JavascriptIndex(),
           field_name=field.name,
           inner_class_converter=field.CppConverterType()
@@ -137,8 +137,7 @@
           'for (const auto& element : repeated_field) {{\n'
           '  field_list.Append(element);\n'
           '}}\n'
-          'dict.SetKey("{field_number}",\n'
-          '            base::Value(std::move(field_list)));\n',
+          'dict.Set("{field_number}", std::move(field_list));\n',
           field_number=field.JavascriptIndex(),
           field_name=field.name
       )
@@ -146,23 +145,23 @@
   def OptionalMemberFieldWriteToValue(self, field):
     if field.IsClassType():
       self.Output(
-          'dict.SetKey("{field_number}",\n'
-          '            {inner_class_converter}::WriteToValue(\n'
-          '                message.{field_name}()));\n',
+          'dict.Set("{field_number}",\n'
+          '         {inner_class_converter}::WriteToValue(\n'
+          '             message.{field_name}()));\n',
           field_number=field.JavascriptIndex(),
           field_name=field.name,
           inner_class_converter=field.CppConverterType()
       )
     else:
       self.Output(
-          'dict.Set{value_type}Key("{field_number}", message.{field_name}());\n',
+          'dict.Set("{field_number}", message.{field_name}());\n',
           field_number=field.JavascriptIndex(),
           field_name=field.name,
           value_type=field.CppValueType()
       )
 
   def WriteFieldRead(self, field):
-    self.Output('if (const auto* value = dict.FindKey("{field_number}")) {{',
+    self.Output('if (const auto* value = dict->Find("{field_number}")) {{',
                 field_number=field.JavascriptIndex())
 
     with self.AddIndent():
diff --git a/third_party/dom_distiller_js/test_sample_json_converter.h.golden b/third_party/dom_distiller_js/test_sample_json_converter.h.golden
index 1c44aac..9ea16e3 100644
--- a/third_party/dom_distiller_js/test_sample_json_converter.h.golden
+++ b/third_party/dom_distiller_js/test_sample_json_converter.h.golden
@@ -17,9 +17,10 @@
          public:
           class Message {
            public:
-            static bool ReadFromValue(const base::Value& dict, dom_distiller::test_sample::proto::TypeTest::Message* message) {
-              if (!dict.is_dict()) goto error;
-              if (const auto* value = dict.FindKey("1")) {
+            static bool ReadFromValue(const base::Value& dict_value, dom_distiller::test_sample::proto::TypeTest::Message* message) {
+              const base::Value::Dict* dict = dict_value.GetIfDict();
+              if (!dict) goto error;
+              if (const auto* value = dict->Find("1")) {
                 if (!(*value).is_bool()) {
                   goto error;
                 }
@@ -32,47 +33,48 @@
             }
 
             static base::Value WriteToValue(const dom_distiller::test_sample::proto::TypeTest::Message& message) {
-              base::Value dict(base::Value::Type::DICT);
+              base::Value::Dict dict;
               if (message.has_dummy()) {
-                dict.SetBoolKey("1", message.dummy());
+                dict.Set("1", message.dummy());
               }
-              return dict;
+              return base::Value(std::move(dict));
             }
           };
 
-          static bool ReadFromValue(const base::Value& dict, dom_distiller::test_sample::proto::TypeTest* message) {
-            if (!dict.is_dict()) goto error;
-            if (const auto* value = dict.FindKey("1")) {
+          static bool ReadFromValue(const base::Value& dict_value, dom_distiller::test_sample::proto::TypeTest* message) {
+            const base::Value::Dict* dict = dict_value.GetIfDict();
+            if (!dict) goto error;
+            if (const auto* value = dict->Find("1")) {
               if (!((*value).is_int() || (*value).is_double())) {
                 goto error;
               }
               message->set_float_value(value->GetDouble());
             }
-            if (const auto* value = dict.FindKey("2")) {
+            if (const auto* value = dict->Find("2")) {
               if (!((*value).is_int() || (*value).is_double())) {
                 goto error;
               }
               message->set_double_value(value->GetDouble());
             }
-            if (const auto* value = dict.FindKey("3")) {
+            if (const auto* value = dict->Find("3")) {
               if (!(*value).is_int()) {
                 goto error;
               }
               message->set_int32_value(value->GetInt());
             }
-            if (const auto* value = dict.FindKey("4")) {
+            if (const auto* value = dict->Find("4")) {
               if (!(*value).is_bool()) {
                 goto error;
               }
               message->set_bool_value(value->GetBool());
             }
-            if (const auto* value = dict.FindKey("5")) {
+            if (const auto* value = dict->Find("5")) {
               if (!(*value).is_string()) {
                 goto error;
               }
               message->set_string_value(value->GetString());
             }
-            if (const auto* value = dict.FindKey("6")) {
+            if (const auto* value = dict->Find("6")) {
               if (!dom_distiller::test_sample::proto::json::TypeTest::Message::ReadFromValue(*value, message->mutable_message_value())) {
                 goto error;
               }
@@ -84,28 +86,28 @@
           }
 
           static base::Value WriteToValue(const dom_distiller::test_sample::proto::TypeTest& message) {
-            base::Value dict(base::Value::Type::DICT);
+            base::Value::Dict dict;
             if (message.has_float_value()) {
-              dict.SetDoubleKey("1", message.float_value());
+              dict.Set("1", message.float_value());
             }
             if (message.has_double_value()) {
-              dict.SetDoubleKey("2", message.double_value());
+              dict.Set("2", message.double_value());
             }
             if (message.has_int32_value()) {
-              dict.SetIntKey("3", message.int32_value());
+              dict.Set("3", message.int32_value());
             }
             if (message.has_bool_value()) {
-              dict.SetBoolKey("4", message.bool_value());
+              dict.Set("4", message.bool_value());
             }
             if (message.has_string_value()) {
-              dict.SetStringKey("5", message.string_value());
+              dict.Set("5", message.string_value());
             }
             if (message.has_message_value()) {
-              dict.SetKey("6",
-                          dom_distiller::test_sample::proto::json::TypeTest::Message::WriteToValue(
-                              message.message_value()));
+              dict.Set("6",
+                       dom_distiller::test_sample::proto::json::TypeTest::Message::WriteToValue(
+                           message.message_value()));
             }
-            return dict;
+            return base::Value(std::move(dict));
           }
         };
 
@@ -113,9 +115,10 @@
          public:
           class Message {
            public:
-            static bool ReadFromValue(const base::Value& dict, dom_distiller::test_sample::proto::Repeated::Message* message) {
-              if (!dict.is_dict()) goto error;
-              if (const auto* value = dict.FindKey("1")) {
+            static bool ReadFromValue(const base::Value& dict_value, dom_distiller::test_sample::proto::Repeated::Message* message) {
+              const base::Value::Dict* dict = dict_value.GetIfDict();
+              if (!dict) goto error;
+              if (const auto* value = dict->Find("1")) {
                 if (!value->is_list()) {
                   goto error;
                 }
@@ -133,7 +136,7 @@
             }
 
             static base::Value WriteToValue(const dom_distiller::test_sample::proto::Repeated::Message& message) {
-              base::Value dict(base::Value::Type::DICT);
+              base::Value::Dict dict;
               {
                 const auto& repeated_field = message.dummy();
                 base::Value::List field_list;
@@ -141,16 +144,16 @@
                 for (const auto& element : repeated_field) {
                   field_list.Append(element);
                 }
-                dict.SetKey("1",
-                            base::Value(std::move(field_list)));
+                dict.Set("1", std::move(field_list));
               }
-              return dict;
+              return base::Value(std::move(dict));
             }
           };
 
-          static bool ReadFromValue(const base::Value& dict, dom_distiller::test_sample::proto::Repeated* message) {
-            if (!dict.is_dict()) goto error;
-            if (const auto* value = dict.FindKey("1")) {
+          static bool ReadFromValue(const base::Value& dict_value, dom_distiller::test_sample::proto::Repeated* message) {
+            const base::Value::Dict* dict = dict_value.GetIfDict();
+            if (!dict) goto error;
+            if (const auto* value = dict->Find("1")) {
               if (!value->is_list()) {
                 goto error;
               }
@@ -161,7 +164,7 @@
                 message->add_float_value(element.GetDouble());
               }
             }
-            if (const auto* value = dict.FindKey("2")) {
+            if (const auto* value = dict->Find("2")) {
               if (!value->is_list()) {
                 goto error;
               }
@@ -172,7 +175,7 @@
                 message->add_double_value(element.GetDouble());
               }
             }
-            if (const auto* value = dict.FindKey("3")) {
+            if (const auto* value = dict->Find("3")) {
               if (!value->is_list()) {
                 goto error;
               }
@@ -183,7 +186,7 @@
                 message->add_int32_value(element.GetInt());
               }
             }
-            if (const auto* value = dict.FindKey("4")) {
+            if (const auto* value = dict->Find("4")) {
               if (!value->is_list()) {
                 goto error;
               }
@@ -194,7 +197,7 @@
                 message->add_bool_value(element.GetBool());
               }
             }
-            if (const auto* value = dict.FindKey("5")) {
+            if (const auto* value = dict->Find("5")) {
               if (!value->is_list()) {
                 goto error;
               }
@@ -205,7 +208,7 @@
                 message->add_string_value(element.GetString());
               }
             }
-            if (const auto* value = dict.FindKey("6")) {
+            if (const auto* value = dict->Find("6")) {
               if (!value->is_list()) {
                 goto error;
               }
@@ -222,7 +225,7 @@
           }
 
           static base::Value WriteToValue(const dom_distiller::test_sample::proto::Repeated& message) {
-            base::Value dict(base::Value::Type::DICT);
+            base::Value::Dict dict;
             {
               const auto& repeated_field = message.float_value();
               base::Value::List field_list;
@@ -230,8 +233,7 @@
               for (const auto& element : repeated_field) {
                 field_list.Append(element);
               }
-              dict.SetKey("1",
-                          base::Value(std::move(field_list)));
+              dict.Set("1", std::move(field_list));
             }
             {
               const auto& repeated_field = message.double_value();
@@ -240,8 +242,7 @@
               for (const auto& element : repeated_field) {
                 field_list.Append(element);
               }
-              dict.SetKey("2",
-                          base::Value(std::move(field_list)));
+              dict.Set("2", std::move(field_list));
             }
             {
               const auto& repeated_field = message.int32_value();
@@ -250,8 +251,7 @@
               for (const auto& element : repeated_field) {
                 field_list.Append(element);
               }
-              dict.SetKey("3",
-                          base::Value(std::move(field_list)));
+              dict.Set("3", std::move(field_list));
             }
             {
               const auto& repeated_field = message.bool_value();
@@ -260,8 +260,7 @@
               for (const auto& element : repeated_field) {
                 field_list.Append(element);
               }
-              dict.SetKey("4",
-                          base::Value(std::move(field_list)));
+              dict.Set("4", std::move(field_list));
             }
             {
               const auto& repeated_field = message.string_value();
@@ -270,8 +269,7 @@
               for (const auto& element : repeated_field) {
                 field_list.Append(element);
               }
-              dict.SetKey("5",
-                          base::Value(std::move(field_list)));
+              dict.Set("5", std::move(field_list));
             }
             {
               const auto& repeated_field = message.message_value();
@@ -281,10 +279,9 @@
                 field_list.Append(
                     dom_distiller::test_sample::proto::json::Repeated::Message::WriteToValue(element));
               }
-              dict.SetKey("6",
-                          base::Value(std::move(field_list)));
+              dict.Set("6", std::move(field_list));
             }
-            return dict;
+            return base::Value(std::move(dict));
           }
         };
 
diff --git a/third_party/nearby/README.chromium b/third_party/nearby/README.chromium
index e90c961..75a3a247 100644
--- a/third_party/nearby/README.chromium
+++ b/third_party/nearby/README.chromium
@@ -1,7 +1,7 @@
 Name: Nearby Connections Library
 Short Name: Nearby
 URL: https://github.com/google/nearby
-Version: a977a6bd7c85b417919111bf98c139be782ce6cc
+Version: b59ed0b70f86c0dc1496c3145fa114bc38965f51
 License: Apache 2.0
 License File: LICENSE
 Security Critical: yes
diff --git a/tools/boilerplate.py b/tools/boilerplate.py
index 5808c75..cdf09b5 100755
--- a/tools/boilerplate.py
+++ b/tools/boilerplate.py
@@ -28,18 +28,19 @@
 ]
 
 EXTENSIONS_TO_COMMENTS = {
-    'h': '//',
     'cc': '//',
-    'nc': '//',
-    'mm': '//',
-    'js': '//',
-    'py': '#',
     'gn': '#',
     'gni': '#',
+    'h': '//',
+    'js': '//',
+    'mm': '//',
     'mojom': '//',
+    'nc': '//',
+    'proto': '//',
+    'py': '#',
+    'swift': '//',
     'ts': '//',
     'typemap': '#',
-    "swift": "//",
 }
 
 
diff --git a/tools/grit/preprocess_if_expr.gni b/tools/grit/preprocess_if_expr.gni
index 450071f8..850bff9a 100644
--- a/tools/grit/preprocess_if_expr.gni
+++ b/tools/grit/preprocess_if_expr.gni
@@ -37,6 +37,14 @@
     foreach(in_file, invoker.in_files) {
       inputs += [ "$in_folder/" + in_file ]
       outputs += [ "$out_folder/" + in_file ]
+
+      if (defined(invoker.enable_removal_comments) &&
+          invoker.enable_removal_comments) {
+        extension = get_path_info(in_file, "extension")
+        assert(
+            extension == "js" || extension == "ts",
+            "enable_removal_comments requires .js or .ts input files. Found ${in_file}")
+      }
     }
 
     args = [
diff --git a/tools/metrics/histograms/enums.xml b/tools/metrics/histograms/enums.xml
index 61596d56..b0a82fd 100644
--- a/tools/metrics/histograms/enums.xml
+++ b/tools/metrics/histograms/enums.xml
@@ -34194,6 +34194,7 @@
       label="SMART_CARD_PROVIDER_PRIVATE_ON_LIST_READERS_REQUESTED"/>
   <int value="503"
       label="SMART_CARD_PROVIDER_PRIVATE_ON_GET_STATUS_CHANGE_REQUESTED"/>
+  <int value="504" label="PDF_VIEWER_PRIVATE_ON_PDF_OCR_PREF_CHANGED"/>
 </enum>
 
 <enum name="ExtensionFileWriteResult">
@@ -103050,7 +103051,7 @@
   <int value="4" label="Attempting registration with preexisting keys"/>
   <int value="5"
       label="Attempting registration while having persistent authentication
-             error"/>
+             error (deprecated)"/>
   <int value="6" label="Already registered V1"/>
 </enum>
 
diff --git a/tools/metrics/histograms/metadata/arc/histograms.xml b/tools/metrics/histograms/metadata/arc/histograms.xml
index b8679e8..f61e615 100644
--- a/tools/metrics/histograms/metadata/arc/histograms.xml
+++ b/tools/metrics/histograms/metadata/arc/histograms.xml
@@ -259,7 +259,7 @@
   </summary>
 </histogram>
 
-<histogram name="Arc.AndroidBootTime" units="ms" expires_after="2023-07-02">
+<histogram name="Arc.AndroidBootTime" units="ms" expires_after="2023-09-01">
   <owner>khmel@google.com</owner>
   <owner>arc-performance@google.com</owner>
   <summary>The time elapsed for booting up the ARC instance.</summary>
@@ -329,7 +329,7 @@
   </summary>
 </histogram>
 
-<histogram name="Arc.Anr.{AnrPeriod}" units="ANRs" expires_after="2023-03-01">
+<histogram name="Arc.Anr.{AnrPeriod}" units="ANRs" expires_after="2023-09-01">
   <owner>khmel@google.com</owner>
   <owner>alanding@google.com</owner>
   <owner>arc-performance@google.com</owner>
@@ -340,7 +340,7 @@
 </histogram>
 
 <histogram name="Arc.Anr.{AnrPeriod}.{ArcBootType}" units="ANRs"
-    expires_after="2023-03-01">
+    expires_after="2023-09-01">
   <owner>khmel@google.com</owner>
   <owner>arc-performance@google.com</owner>
   <summary>
@@ -351,7 +351,7 @@
   <token key="ArcBootType" variants="ArcBootTypes"/>
 </histogram>
 
-<histogram name="Arc.Anr.{AnrSource}" enum="ArcAnr" expires_after="2023-03-01">
+<histogram name="Arc.Anr.{AnrSource}" enum="ArcAnr" expires_after="2023-09-01">
   <owner>khmel@google.com</owner>
   <owner>alanding@google.com</owner>
   <owner>arc-performance@google.com</owner>
@@ -981,7 +981,7 @@
 </histogram>
 
 <histogram name="Arc.CpuRestrictionDisabled{ArcThrottleObservers}" units="ms"
-    expires_after="2023-03-01">
+    expires_after="2023-09-01">
   <owner>khmel@google.com</owner>
   <owner>alanding@google.com</owner>
   <owner>arc-performance@google.com</owner>
@@ -1008,7 +1008,7 @@
 </histogram>
 
 <histogram name="Arc.CpuRestrictionVmResult" enum="ArcCpuRestrictionVmResult"
-    expires_after="2023-03-01">
+    expires_after="2023-09-01">
   <owner>khmel@google.com</owner>
   <owner>alanding@google.com</owner>
   <owner>arc-performance@google.com</owner>
@@ -1206,7 +1206,7 @@
 </histogram>
 
 <histogram name="Arc.FirstAppLaunchDelay.TimeDelta" units="ms"
-    expires_after="2023-07-02">
+    expires_after="2023-09-01">
   <owner>khmel@google.com</owner>
   <owner>arc-performance@google.com</owner>
   <summary>
@@ -1220,7 +1220,7 @@
 </histogram>
 
 <histogram name="Arc.FirstAppLaunchDelay.TimeDeltaUntilAppLaunch" units="ms"
-    expires_after="2023-07-02">
+    expires_after="2023-09-01">
   <owner>khmel@google.com</owner>
   <owner>alanding@google.com</owner>
   <summary>
@@ -1233,7 +1233,7 @@
 </histogram>
 
 <histogram name="Arc.FirstAppLaunchRequest.TimeDelta" units="ms"
-    expires_after="2023-03-01">
+    expires_after="2023-09-01">
   <owner>khmel@google.com</owner>
   <owner>arc-performance@google.com</owner>
   <summary>
@@ -1780,7 +1780,7 @@
 </histogram>
 
 <histogram name="Arc.PlayStoreLaunch.TimeDelta" units="ms"
-    expires_after="2023-03-01">
+    expires_after="2023-09-01">
   <owner>khmel@google.com</owner>
   <owner>arc-performance@google.com</owner>
   <summary>
@@ -1857,7 +1857,7 @@
 </histogram>
 
 <histogram name="Arc.PlayStoreShown.TimeDelta{ArcUserTypes}" units="ms"
-    expires_after="2023-03-01">
+    expires_after="2023-09-01">
   <owner>khmel@google.com</owner>
   <owner>arc-performance@google.com</owner>
   <summary>
@@ -2021,7 +2021,7 @@
 
 <histogram
     name="Arc.Runtime.Performance.CommitDeviation2{ArcPerformanceAppCategories}"
-    units="microseconds" expires_after="2023-03-01">
+    units="microseconds" expires_after="2023-09-01">
   <owner>khmel@google.com</owner>
   <owner>alanding@google.com</owner>
   <summary>
@@ -2036,7 +2036,7 @@
 </histogram>
 
 <histogram name="Arc.Runtime.Performance.FPS2{ArcPerformanceAppCategories}"
-    units="fps" expires_after="2023-03-01">
+    units="fps" expires_after="2023-09-01">
   <owner>khmel@google.com</owner>
   <owner>alanding@google.com</owner>
   <summary>
@@ -2050,7 +2050,7 @@
 </histogram>
 
 <histogram name="Arc.Runtime.Performance.Generic.FirstFrameRendered" units="ms"
-    expires_after="2023-07-02">
+    expires_after="2023-09-01">
   <owner>khmel@google.com</owner>
   <owner>alanding@google.com</owner>
   <summary>
@@ -2061,7 +2061,7 @@
 </histogram>
 
 <histogram name="Arc.Runtime.Performance.Generic.FrameTime" units="ms"
-    expires_after="2023-04-30">
+    expires_after="2023-09-01">
   <owner>khmel@google.com</owner>
   <owner>alanding@google.com</owner>
   <summary>
@@ -2071,7 +2071,7 @@
 </histogram>
 
 <histogram name="Arc.Runtime.Performance.Generic.Jankiness" units="%"
-    expires_after="2023-07-02">
+    expires_after="2023-09-01">
   <owner>khmel@google.com</owner>
   <owner>alanding@google.com</owner>
   <summary>
@@ -2082,7 +2082,7 @@
 
 <histogram
     name="Arc.Runtime.Performance.RenderQuality2{ArcPerformanceAppCategories}"
-    units="%" expires_after="2023-03-01">
+    units="%" expires_after="2023-09-01">
   <owner>khmel@google.com</owner>
   <owner>alanding@google.com</owner>
   <summary>
@@ -2249,7 +2249,7 @@
 </histogram>
 
 <histogram name="Arc.UiAvailable.AlreadyProvisioned.TimeDelta{ArcUserTypes}"
-    units="ms" expires_after="2023-03-01">
+    units="ms" expires_after="2023-09-01">
   <owner>khmel@google.com</owner>
   <owner>arc-performance@google.com</owner>
   <summary>
@@ -2262,7 +2262,7 @@
 </histogram>
 
 <histogram name="Arc.UiAvailable.InSessionProvisioning.TimeDelta{ArcUserTypes}"
-    units="ms" expires_after="2023-03-01">
+    units="ms" expires_after="2023-09-01">
   <owner>khmel@google.com</owner>
   <owner>arc-performance@google.com</owner>
   <summary>
@@ -2275,7 +2275,7 @@
 </histogram>
 
 <histogram name="Arc.UiAvailable.OobeProvisioning.TimeDelta{ArcUserTypes}"
-    units="ms" expires_after="2023-03-01">
+    units="ms" expires_after="2023-09-01">
   <owner>khmel@google.com</owner>
   <owner>arc-performance@google.com</owner>
   <summary>
diff --git a/tools/metrics/histograms/metadata/ash/histograms.xml b/tools/metrics/histograms/metadata/ash/histograms.xml
index 18936ca..b3ec20e 100644
--- a/tools/metrics/histograms/metadata/ash/histograms.xml
+++ b/tools/metrics/histograms/metadata/ash/histograms.xml
@@ -488,6 +488,16 @@
   </token>
 </histogram>
 
+<histogram name="Ash.BrowserDataBackMigrator.SuccessfulMigrationTime"
+    units="ms" expires_after="2023-12-01">
+  <owner>janagrill@google.com</owner>
+  <owner>artyomchen@google.com</owner>
+  <summary>
+    How long the backward migration took to complete in ms. Only recorded if
+    migration succeeds.
+  </summary>
+</histogram>
+
 <histogram name="Ash.BrowserDataMigrator.AshDataSizeMB" units="MB"
     expires_after="M121">
   <owner>ythjkt@chromium.org</owner>
diff --git a/tools/metrics/histograms/metadata/blink/histograms.xml b/tools/metrics/histograms/metadata/blink/histograms.xml
index 12808ad8..7af4929 100644
--- a/tools/metrics/histograms/metadata/blink/histograms.xml
+++ b/tools/metrics/histograms/metadata/blink/histograms.xml
@@ -916,7 +916,7 @@
   <owner>web-identity-eng@google.com</owner>
   <summary>
     Records whether the FedCM auto re-authn call is blocked because the auto
-    re-auth content settings permission is disabled. Records at most one sample
+    re-authn content settings permission is disabled. Records at most one sample
     per FedCM API with auto re-authn enabled: some failures could occur before
     this metric is recorded.
   </summary>
@@ -964,6 +964,21 @@
   </summary>
 </histogram>
 
+<histogram name="Blink.FedCm.AutoReauthn.TimeFromEmbargoWhenBlocked" units="ms"
+    expires_after="2023-08-09">
+  <owner>npm@chromium.org</owner>
+  <owner>yigu@chromium.org</owner>
+  <owner>web-identity-eng@google.com</owner>
+  <summary>
+    Records the amount of time that has passed from the time the FedCM auto
+    re-authn API was embargoed to the time in which the next call occurs. Only
+    records a sample when there is an auto re-authn FedCM API call which is
+    blocked due to embargo. Samples are exponentially bucketed, with a max
+    bucket of 10 minutes, the embargo duration (see
+    `kFederatedIdentityAutoReauthnEmbargoDuration`).
+  </summary>
+</histogram>
+
 <histogram name="Blink.FedCm.CancelReason" enum="FedCmCancelReason"
     expires_after="2023-07-23">
   <owner>pkotwicz@chromium.org</owner>
diff --git a/tools/metrics/histograms/metadata/holding_space/histograms.xml b/tools/metrics/histograms/metadata/holding_space/histograms.xml
index 3deb8a66..9b81a87b 100644
--- a/tools/metrics/histograms/metadata/holding_space/histograms.xml
+++ b/tools/metrics/histograms/metadata/holding_space/histograms.xml
@@ -62,7 +62,7 @@
 </variants>
 
 <histogram name="HoldingSpace.Animation.BubbleResize.Smoothness" units="%"
-    expires_after="2023-04-01">
+    expires_after="2023-08-08">
   <owner>dmblack@google.com</owner>
   <owner>gzadina@google.com</owner>
   <summary>
@@ -74,7 +74,7 @@
 </histogram>
 
 <histogram name="HoldingSpace.Animation.PodResize.Smoothness" units="%"
-    expires_after="2023-04-01">
+    expires_after="2023-08-08">
   <owner>dmblack@google.com</owner>
   <owner>gzadina@google.com</owner>
   <summary>
@@ -96,7 +96,7 @@
 </histogram>
 
 <histogram name="HoldingSpace.FilesAppChip.Action.All"
-    enum="HoldingSpaceFilesAppChipAction" expires_after="2023-04-01">
+    enum="HoldingSpaceFilesAppChipAction" expires_after="2023-08-08">
   <owner>dmblack@google.com</owner>
   <owner>gzadina@google.com</owner>
   <summary>
@@ -106,7 +106,7 @@
 </histogram>
 
 <histogram name="HoldingSpace.Item.Action.All" enum="HoldingSpaceItemAction"
-    expires_after="2023-08-06">
+    expires_after="2023-08-08">
   <owner>dmblack@google.com</owner>
   <owner>gzadina@google.com</owner>
   <summary>
@@ -116,7 +116,7 @@
 </histogram>
 
 <histogram name="HoldingSpace.Item.Action.{action}" enum="HoldingSpaceItemType"
-    expires_after="2023-04-01">
+    expires_after="2023-08-08">
   <owner>dmblack@google.com</owner>
   <owner>gzadina@google.com</owner>
   <summary>
@@ -127,7 +127,7 @@
 </histogram>
 
 <histogram name="HoldingSpace.Item.Action.{action}.Extension"
-    enum="HoldingSpaceExtension" expires_after="2023-04-01">
+    enum="HoldingSpaceExtension" expires_after="2023-08-08">
   <owner>dmblack@google.com</owner>
   <owner>gzadina@google.com</owner>
   <summary>
@@ -138,7 +138,7 @@
 </histogram>
 
 <histogram name="HoldingSpace.Item.Count.{type}" units="items"
-    expires_after="2023-04-01">
+    expires_after="2023-08-08">
   <owner>dmblack@google.com</owner>
   <owner>gzadina@google.com</owner>
   <summary>
@@ -149,7 +149,7 @@
 </histogram>
 
 <histogram name="HoldingSpace.Item.FailureToLaunch" enum="HoldingSpaceItemType"
-    expires_after="2023-04-01">
+    expires_after="2023-08-08">
   <owner>dmblack@google.com</owner>
   <owner>gzadina@google.com</owner>
   <summary>
@@ -159,7 +159,7 @@
 </histogram>
 
 <histogram name="HoldingSpace.Item.FailureToLaunch.Extension"
-    enum="HoldingSpaceExtension" expires_after="2023-04-01">
+    enum="HoldingSpaceExtension" expires_after="2023-08-08">
   <owner>dmblack@google.com</owner>
   <owner>gzadina@google.com</owner>
   <summary>
@@ -169,7 +169,7 @@
 </histogram>
 
 <histogram name="HoldingSpace.Item.FailureToLaunch.Reason"
-    enum="HoldingSpaceItemFailureToLaunchReason" expires_after="2023-04-01">
+    enum="HoldingSpaceItemFailureToLaunchReason" expires_after="2023-08-08">
   <owner>dmblack@google.com</owner>
   <owner>gzadina@google.com</owner>
   <summary>
@@ -179,7 +179,7 @@
 </histogram>
 
 <histogram name="HoldingSpace.Item.TotalCount.{type}" units="items"
-    expires_after="2023-04-01">
+    expires_after="2023-08-08">
   <owner>dmblack@google.com</owner>
   <owner>gzadina@google.com</owner>
   <summary>
@@ -191,7 +191,7 @@
 </histogram>
 
 <histogram name="HoldingSpace.Pod.Action.All" enum="HoldingSpacePodAction"
-    expires_after="2023-08-06">
+    expires_after="2023-08-08">
   <owner>dmblack@google.com</owner>
   <owner>gzadina@google.com</owner>
   <summary>
@@ -211,7 +211,7 @@
 </histogram>
 
 <histogram name="HoldingSpace.TimeFromFirstAvailabilityToFirstAdd" units="ms"
-    expires_after="2023-04-01">
+    expires_after="2023-08-08">
   <owner>dmblack@google.com</owner>
   <owner>gzadina@google.com</owner>
   <summary>
@@ -242,7 +242,7 @@
 </histogram>
 
 <histogram name="HoldingSpace.UserPreferences.PreviewsEnabled"
-    enum="BooleanEnabled" expires_after="2023-08-06">
+    enum="BooleanEnabled" expires_after="2023-08-08">
   <owner>dmblack@google.com</owner>
   <owner>gzadina@google.com</owner>
   <summary>
diff --git a/tools/metrics/histograms/metadata/webapps/histograms.xml b/tools/metrics/histograms/metadata/webapps/histograms.xml
index 8a55e95b..d313bf48d 100644
--- a/tools/metrics/histograms/metadata/webapps/histograms.xml
+++ b/tools/metrics/histograms/metadata/webapps/histograms.xml
@@ -1109,6 +1109,31 @@
   </summary>
 </histogram>
 
+<histogram name="WebApp.RunOnOsLogin.Registration.Result" enum="BooleanSuccess"
+    expires_after="2024-03-01">
+  <owner>dibyapal@chromium.org</owner>
+  <owner>desktop-pwas-team@google.com</owner>
+  <summary>
+    Records the result of registering Run On OS Login with the OS. This is
+    triggered during OS integration whenever an app is being installed or the
+    run on OS login state is being updated by the user from various UI surfaces
+    like the chrome://apps context menu or from the app settings page for an
+    app.
+  </summary>
+</histogram>
+
+<histogram name="WebApp.RunOnOsLogin.Unregistration.Result"
+    enum="BooleanSuccess" expires_after="2024-03-01">
+  <owner>dibyapal@chromium.org</owner>
+  <owner>desktop-pwas-team@google.com</owner>
+  <summary>
+    Records the result of unregistering Run On OS Login with the OS. This is
+    triggered during OS integration whenever an app is being uninstalled or the
+    user is modifying the app preferences to not have it run on startup from the
+    chrome://apps context menu or app settings page.
+  </summary>
+</histogram>
+
 <histogram name="WebApp.Shortcuts.Creation.Result"
     enum="ShortcutsCreationResult" expires_after="2023-08-20">
   <owner>phillis@chromium.org</owner>
diff --git a/tools/metrics/ukm/ukm.xml b/tools/metrics/ukm/ukm.xml
index 59090b8..b71151bd 100644
--- a/tools/metrics/ukm/ukm.xml
+++ b/tools/metrics/ukm/ukm.xml
@@ -2070,6 +2070,36 @@
   </metric>
 </event>
 
+<event name="Autofill.FastCheckoutFormStatus">
+  <owner>bwolfgang@google.com</owner>
+  <owner>jkeitel@google.com</owner>
+  <owner>vizcay@google.com</owner>
+  <summary>
+    Recorded at the end of a Fast Checkout run.
+  </summary>
+  <metric name="Filled" enum="Boolean">
+    <summary>
+      1 if the form was filled by Fast Checkout, 0 otherwise.
+    </summary>
+  </metric>
+  <metric name="FormSignature">
+    <summary>
+      An approximately 10-bit hash of the form's actual signature.
+    </summary>
+  </metric>
+  <metric name="FormTypes">
+    <summary>
+      Type of form. Stored as bitvector. A set i-th bit implies enum FormType's
+      i-th type was detected. Multiple types are possible.
+    </summary>
+  </metric>
+  <metric name="RunId">
+    <summary>
+      Unique run ID for linking to other UKM events.
+    </summary>
+  </metric>
+</event>
+
 <event name="Autofill.FastCheckoutRunOutcome">
   <owner>bwolfgang@google.com</owner>
   <owner>jkeitel@google.com</owner>
@@ -3258,6 +3288,22 @@
   <summary>
     Records performance metrics for FedCM(Federated Credential Management) API.
   </summary>
+  <metric name="AutoReauthn.BlockedByContentSettings" enum="Boolean">
+    <summary>
+      Records whether the FedCM auto re-authn call is blocked because the auto
+      re-authn content settings permission is disabled. Records at most one
+      sample per FedCM API with auto re-authn enabled: some failures could occur
+      before this metric is recorded.
+    </summary>
+  </metric>
+  <metric name="AutoReauthn.BlockedByEmbargo" enum="Boolean">
+    <summary>
+      Records whether the FedCM auto re-authn call is blocked because the auto
+      re-authn is under embargo, i.e. due to cooldown. Records at most one
+      sample per FedCM API with auto re-authn enabled: some failures could occur
+      before this metric is recorded.
+    </summary>
+  </metric>
   <metric name="AutoReauthn.ReturningAccounts" enum="FedCmReturningAccounts">
     <summary>
       Records whether there was zero, one, or multiple returning accounts when a
@@ -3277,6 +3323,16 @@
       occur before this metric is recorded.
     </summary>
   </metric>
+  <metric name="AutoReauthn.TimeFromEmbargoWhenBlocked">
+    <summary>
+      Records the amount of time that has passed from the time the FedCM auto
+      re-authn API was embargoed to the time in which the next call occurs. Only
+      records a sample when there is an auto re-authn FedCM API call which is
+      blocked due to embargo. Samples are exponentially bucketed, with a max
+      bucket of 10 minutes, the embargo duration (see
+      `kFederatedIdentityAutoReauthnEmbargoDuration`).
+    </summary>
+  </metric>
   <metric name="FedCmSessionID">
     <summary>
       Records the session ID associated to the FedCM call for which this event
diff --git a/tools/perf/benchmark.csv b/tools/perf/benchmark.csv
index 1e19649..647593c 100644
--- a/tools/perf/benchmark.csv
+++ b/tools/perf/benchmark.csv
@@ -67,7 +67,6 @@
 speedometer,"cbruni@chromium.org, vahl@chromium.org",Blink>JavaScript,,all
 speedometer-future,"cbruni@chromium.org, vahl@chromium.org",Blink>JavaScript,,all
 speedometer2,"cbruni@chromium.org, vahl@chromium.org",Blink>JavaScript,,all
-speedometer2-chrome-health,"cbruni@chromium.org, vahl@chromium.org",Blink>JavaScript,,all
 speedometer2-future,"cbruni@chromium.org, vahl@chromium.org",Blink>JavaScript,,all
 speedometer2-minormc,omerkatz@chromium.org,Blink>JavaScript>GarbageCollection,,all
 speedometer2-pcscan,tmrts@chromium.org,Blink>JavaScript,,all
diff --git a/tools/perf/benchmarks/speedometer2.py b/tools/perf/benchmarks/speedometer2.py
index 15afd1a..2fdf3a3 100644
--- a/tools/perf/benchmarks/speedometer2.py
+++ b/tools/perf/benchmarks/speedometer2.py
@@ -173,20 +173,3 @@
 
   def SetExtraBrowserOptions(self, options):
     options.AppendExtraBrowserArgs('--js-flags=--minor-mc')
-
-
-@benchmark.Info(emails=['cbruni@chromium.org', 'vahl@chromium.org'],
-                component='Blink>JavaScript')
-class Speedometer2ChromeHealth(Speedometer2):
-  """Speedometer2 benchmark, but run for only one iteration.
-
-  For use with the Chrome Health Project.
-  """
-
-  @classmethod
-  def Name(cls):
-    return 'speedometer2-chrome-health'
-
-  def CreateStorySet(self, options):
-    self.iteration_count = 1
-    return super(Speedometer2ChromeHealth, self).CreateStorySet(options)
diff --git a/tools/perf/chrome-health-presets.yaml b/tools/perf/chrome-health-presets.yaml
index dde6251..a576def 100644
--- a/tools/perf/chrome-health-presets.yaml
+++ b/tools/perf/chrome-health-presets.yaml
@@ -7,7 +7,7 @@
 presets:
   chrome_health:
     telemetry_batch_experiment:
-      - benchmark: speedometer2-chrome-health
+      - benchmark: speedometer2
         configs:
           - mac-laptop_low_end-perf
           - mac-m1_mini_2020-perf
@@ -69,7 +69,7 @@
           - JetStream2
   chrome_health_pgo:
     telemetry_batch_experiment:
-      - benchmark: speedometer2-chrome-health
+      - benchmark: speedometer2
         configs:
           - mac-laptop_low_end-perf-pgo
           - mac-m1_mini_2020-perf-pgo
@@ -140,7 +140,7 @@
           - Speedometer2
   speedometer2_pgo:
     telemetry_batch_experiment:
-      - benchmark: speedometer2-chrome-health
+      - benchmark: speedometer2
         configs:
           - mac-laptop_low_end-perf-pgo
           - mac-m1_mini_2020-perf-pgo
diff --git a/tools/perf/core/bot_platforms.py b/tools/perf/core/bot_platforms.py
index e503a96..fd0dc20 100644
--- a/tools/perf/core/bot_platforms.py
+++ b/tools/perf/core/bot_platforms.py
@@ -224,11 +224,9 @@
     [_GetBenchmarkConfig(b.Name()) for b in OFFICIAL_BENCHMARKS])
 # power.mobile requires special hardware.
 # only run blink_perf.sanitizer-api on linux-perf.
-# speedometer2-chrome-health is only for use with the Chrome Health pipeline
 OFFICIAL_BENCHMARK_CONFIGS = OFFICIAL_BENCHMARK_CONFIGS.Remove([
     'power.mobile',
     'blink_perf.sanitizer-api',
-    'speedometer2-chrome-health',
     'speedometer2-minormc',
 ])
 # TODO(crbug.com/965158): Remove OFFICIAL_BENCHMARK_NAMES once sharding
diff --git a/tools/perf/core/perfetto_binary_roller/binary_deps.json b/tools/perf/core/perfetto_binary_roller/binary_deps.json
index 9df6486..e26c888 100644
--- a/tools/perf/core/perfetto_binary_roller/binary_deps.json
+++ b/tools/perf/core/perfetto_binary_roller/binary_deps.json
@@ -22,7 +22,7 @@
         },
         "linux": {
             "hash": "53030fbc3d6d2485ec35eb072492d32edf0cde5a",
-            "full_remote_path": "chromium-telemetry/perfetto_binaries/trace_processor_shell/linux/f396a983fe100dec36e2c7d9285ca8a74e0ade91/trace_processor_shell"
+            "full_remote_path": "chromium-telemetry/perfetto_binaries/trace_processor_shell/linux/e85c60ec550a5b4f09e5e8efcad6b355f5a7c52d/trace_processor_shell"
         }
     },
     "power_profile.sql": {
diff --git a/tools/perf/core/undocumented_benchmarks.py b/tools/perf/core/undocumented_benchmarks.py
index 3bbb5f0..5564cd5 100644
--- a/tools/perf/core/undocumented_benchmarks.py
+++ b/tools/perf/core/undocumented_benchmarks.py
@@ -18,7 +18,6 @@
     'speedometer',
     'speedometer-future',
     'speedometer2',
-    'speedometer2-chrome-health',
     'speedometer2-future',
     'speedometer2-minormc',
     'speedometer2-pcscan',
diff --git a/tools/run-swarmed.py b/tools/run-swarmed.py
index e9b697a..565c2a9 100755
--- a/tools/run-swarmed.py
+++ b/tools/run-swarmed.py
@@ -285,9 +285,7 @@
     elif args.target_os == 'android':
       args.arch = gn_args.get('target_cpu', 'detect')
 
-  # TODO(crbug.com/1268955): Use sys.executable and remove os-specific logic
-  # once mb.py is in python3
-  mb_cmd = ['tools/mb/mb', 'isolate']
+  mb_cmd = [sys.executable, 'tools/mb/mb.py', 'isolate']
   if not args.build:
     mb_cmd.append('--no-build')
   if args.isolate_map_file:
@@ -310,9 +308,9 @@
   with open(archive_json) as f:
     cas_digest = json.load(f).get(args.target_name)
 
-  # TODO(crbug.com/1268955): Use sys.executable and remove os-specific logic
-  # once mb.py is in python3
-  mb_cmd = ['tools/mb/mb', 'get-swarming-command', '--as-list']
+  mb_cmd = [
+      sys.executable, 'tools/mb/mb.py', 'get-swarming-command', '--as-list'
+  ]
   if not args.build:
     mb_cmd.append('--no-build')
   if args.isolate_map_file:
diff --git a/tools/typescript/definitions/i18n.d.ts b/tools/typescript/definitions/i18n.d.ts
deleted file mode 100644
index 22d7d11..0000000
--- a/tools/typescript/definitions/i18n.d.ts
+++ /dev/null
@@ -1,30 +0,0 @@
-// Copyright 2023 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-/** @fileoverview Definitions for chrome.i18n API */
-// TODO(crbug.com/1203307): Auto-generate this file.
-declare namespace chrome {
-  export namespace i18n {
-
-    export function getAcceptLanguages(callback: (languages: string[]) => void):
-        void;
-
-    export function getMessage(
-        messageName: string, args?: string|string[],
-        options?: {escapeLt: boolean}): string;
-
-    export function getUILanguage(): string;
-
-    interface DetectLanguageResult {
-      isReliable: boolean;
-      languages: Array<{
-        language: string,
-        percentage: number,
-      }>;
-    }
-
-    export function detectLanguage(
-        text: string, callback: (result: DetectLanguageResult) => void): void;
-  }
-}
diff --git a/tools/typescript/definitions/pdf_viewer_private.d.ts b/tools/typescript/definitions/pdf_viewer_private.d.ts
index 746c6430..0f0668c 100644
--- a/tools/typescript/definitions/pdf_viewer_private.d.ts
+++ b/tools/typescript/definitions/pdf_viewer_private.d.ts
@@ -5,14 +5,21 @@
 /** @fileoverview Definitions for chrome.pdfViewerPrivate API. */
 // TODO(crbug.com/1203307): Auto-generate this file.
 
-declare namespace chrome {
-  export namespace pdfViewerPrivate {
+import {ChromeEvent} from './chrome_event.js';
 
-    export function isAllowedLocalFileAccess(
-        url: string, callback: (isAllowed: boolean) => void): void;
-    export function isPdfOcrAlwaysActive(
-        callback: (isAlwaysActive: boolean) => void): void;
-    export function setPdfOcrPref(
-        isAlwaysActive: boolean, callback: (isSet: boolean) => void): void;
+declare global {
+  export namespace chrome {
+    export namespace pdfViewerPrivate {
+
+      export function isAllowedLocalFileAccess(
+          url: string, callback: (isAllowed: boolean) => void): void;
+      export function isPdfOcrAlwaysActive(
+          callback: (isAlwaysActive: boolean) => void): void;
+      export function setPdfOcrPref(
+          isAlwaysActive: boolean, callback: (isSet: boolean) => void): void;
+
+      type PdfOcrPrefCallback = ((isPdfOcrAlwaysActive: boolean) => void)|null;
+      export const onPdfOcrPrefChanged: ChromeEvent<PdfOcrPrefCallback>;
+    }
   }
 }
diff --git a/tools/typescript/definitions/settings_private_mv2.d.ts b/tools/typescript/definitions/settings_private_mv2.d.ts
deleted file mode 100644
index 142a5c2..0000000
--- a/tools/typescript/definitions/settings_private_mv2.d.ts
+++ /dev/null
@@ -1,84 +0,0 @@
-// Copyright 2023 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-/** @fileoverview Definitions for chrome.settingsPrivate API in Manifest V2 */
-// This file exists because MV3 supports promises and MV2 does not.
-// TODO(b/260590502): Delete this after MV3 migration.
-// TODO(crbug.com/1203307): Auto-generate this file.
-
-import {ChromeEvent} from './chrome_event.js';
-
-declare global {
-  export namespace chrome {
-    export namespace settingsPrivate {
-      export enum PrefType {
-        BOOLEAN = 'BOOLEAN',
-        NUMBER = 'NUMBER',
-        STRING = 'STRING',
-        URL = 'URL',
-        LIST = 'LIST',
-        DICTIONARY = 'DICTIONARY',
-      }
-
-      export enum ControlledBy {
-        DEVICE_POLICY = 'DEVICE_POLICY',
-        USER_POLICY = 'USER_POLICY',
-        OWNER = 'OWNER',
-        PRIMARY_USER = 'PRIMARY_USER',
-        EXTENSION = 'EXTENSION',
-        PARENT = 'PARENT',
-        CHILD_RESTRICTION = 'CHILD_RESTRICTION',
-      }
-
-      export enum Enforcement {
-        ENFORCED = 'ENFORCED',
-        RECOMMENDED = 'RECOMMENDED',
-        PARENT_SUPERVISED = 'PARENT_SUPERVISED',
-      }
-
-      // Callback Types
-      type GetAllPrefsCallback = (prefs: PrefObject[]) => void;
-      type OnPrefSetCallback = (success: boolean) => void;
-      type GetPrefCallback = (pref: PrefObject) => void;
-      // TODO(crbug/1373934) Update existing usages of PrefObject to be typed,
-      // removing the need to use any here.
-      export interface PrefObject<T = any> {
-        key: string;
-        type:
-            // clang-format off
-            T extends boolean ? PrefType.BOOLEAN :
-            T extends number ? PrefType.NUMBER :
-            T extends string ? PrefType.STRING | PrefType.URL :
-            T extends unknown[] ? PrefType.LIST :
-            T extends Record<string|number, unknown> ? PrefType.DICTIONARY :
-            never;
-        // clang-format on
-        value: T;
-        controlledBy?: ControlledBy;
-        controlledByName?: string;
-        enforcement?: Enforcement;
-        recommendedValue?: T;
-        userSelectableValues?: T[];
-        userControlDisabled?: boolean;
-        extensionId?: string;
-        extensionCanBeDisabled?: boolean;
-      }
-
-      export function getAllPrefs(callback: GetAllPrefsCallback): void;
-      export function getPref(name: string, callback: GetPrefCallback): void;
-
-      export function setPref(
-          name: string, value: any, pageId?: string,
-          callback?: OnPrefSetCallback): void;
-
-      export function getDefaultZoom(callback: (arg: number) => void): void;
-      export function setDefaultZoom(
-          zoom: number, callback?: (arg: boolean) => void): void;
-
-      type PrefsCallback = (prefs: PrefObject[]) => void;
-
-      export const onPrefsChanged: ChromeEvent<PrefsCallback>;
-    }
-  }
-}
diff --git a/tools/typescript/definitions/storage_mv2.d.ts b/tools/typescript/definitions/storage_mv2.d.ts
deleted file mode 100644
index eea979c..0000000
--- a/tools/typescript/definitions/storage_mv2.d.ts
+++ /dev/null
@@ -1,47 +0,0 @@
-// Copyright 2023 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-/** @fileoverview Definitions for chrome.storage API in Manifest V2 */
-// This file exists because MV3 supports promises and MV2 does not.
-// TODO(b/260590502): Delete this after MV3 migration.
-// TODO(crbug.com/1203307): Auto-generate this file.
-
-import {ChromeEvent} from './chrome_event';
-
-declare global {
-  export namespace chrome {
-    export namespace storage {
-      export const sync: StorageArea;
-      export const local: StorageArea;
-      export const managed: StorageArea;
-      export const onChanged: StorageChangeEvent;
-
-      export type StorageChangeEvent = ChromeEvent<
-          (changes: {[x: string]: StorageChange}, areaName: string) => void>;
-
-      export type StorageAreaChangeEvent =
-          ChromeEvent<(changes: {[x: string]: StorageChange}) => void>;
-
-      export interface StorageChange {
-        oldValue?: any;
-        newValue?: any;
-      }
-
-      export class StorageArea {
-        public get(
-            keysOrCallback?: string|string[]|object|((obj: any) => void),
-            callback?: (obj: any) => void): void;
-
-        public getBytesInUse(
-            keysOrCallback?: string|string[]|((obj: any) => void),
-            callback?: (num: number) => void): void;
-
-        public set(items: {[x: string]: any}, callback?: () => void): void;
-        public remove(keys: string|string[], callback?: () => void): void;
-        public clear(callback?: () => void): void;
-        public onChanged: StorageAreaChangeEvent;
-      }
-    }
-  }
-}
diff --git a/tools/typescript/definitions/tts.d.ts b/tools/typescript/definitions/tts.d.ts
deleted file mode 100644
index 4192d28..0000000
--- a/tools/typescript/definitions/tts.d.ts
+++ /dev/null
@@ -1,72 +0,0 @@
-// Copyright 2023 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-import {ChromeEvent} from './chrome_event.js';
-
-// TODO(crbug.com/1203307): Auto-generate this file.
-
-declare global {
-  namespace chrome {
-    export namespace tts {
-
-      export enum EventType {
-        START = 'start',
-        END = 'end',
-        WORD = 'word',
-        SENTENCE = 'sentence',
-        MARKER = 'marker',
-        INTERRUPTED = 'interrupted',
-        CANCELLED = 'cancelled',
-        ERROR = 'error',
-        PAUSE = 'pause',
-        RESUME = 'resume',
-      }
-
-      export class TtsOptions {
-        enqueue?: boolean;
-        voiceName?: string;
-        extensionId?: string;
-        lang?: string;
-        rate: number;
-        pitch?: number;
-        volume?: number;
-        requiredEventTypes?: string[];
-        desiredEventTypes?: string[];
-        onEvent(event: TtsEvent): void;
-      }
-
-      export interface TtsEvent {
-        type: EventType;
-        charIndex?: number;
-        errorMessage?: string;
-        srcId?: number;
-        isFinalEvent?: boolean;
-        length?: number;
-      }
-
-      export interface TtsVoice {
-        voiceName?: string;
-        lang?: string;
-        remote?: boolean;
-        extensionId?: string;
-        eventTypes?: EventType[];
-      }
-
-      export function speak(
-          utterance: string, options: TtsOptions, callback?: () => void): void;
-
-      export function stop(): void;
-
-      export function pause(): void;
-
-      export function resume(): void;
-
-      export function isSpeaking(callback?: (param: boolean) => void): void;
-
-      export function getVoices(callback?: (param: TtsVoice[]) => void): void;
-
-      export const onEvent: ChromeEvent<(event: TtsEvent) => void>;
-    }
-  }
-}
diff --git a/ui/gl/test/gl_surface_test_support.cc b/ui/gl/test/gl_surface_test_support.cc
index 4674dad..998fb7a 100644
--- a/ui/gl/test/gl_surface_test_support.cc
+++ b/ui/gl/test/gl_surface_test_support.cc
@@ -49,8 +49,8 @@
     use_software_gl = false;
   }
 
-#if BUILDFLAG(IS_ANDROID)
-  // On Android we always use hardware GL.
+#if BUILDFLAG(IS_ANDROID) || BUILDFLAG(IS_IOS)
+  // On Android and iOS we always use hardware GL.
   use_software_gl = false;
 #endif
 
diff --git a/ui/views/controls/button/md_text_button.cc b/ui/views/controls/button/md_text_button.cc
index 3c24862..bb470a4 100644
--- a/ui/views/controls/button/md_text_button.cc
+++ b/ui/views/controls/button/md_text_button.cc
@@ -53,6 +53,9 @@
   if (features::IsChromeRefresh2023()) {
     constexpr int kImageSpacing = 8;
     SetImageLabelSpacing(kImageSpacing);
+  } else {
+    SetCornerRadius(LayoutProvider::Get()->GetCornerRadiusMetric(
+        ShapeContextTokens::kButtonRadius));
   }
 
   SetHorizontalAlignment(gfx::ALIGN_CENTER);
diff --git a/ui/views/examples/checkbox_example.cc b/ui/views/examples/checkbox_example.cc
index 4ca56414..51e0d480 100644
--- a/ui/views/examples/checkbox_example.cc
+++ b/ui/views/examples/checkbox_example.cc
@@ -4,14 +4,10 @@
 
 #include "ui/views/examples/checkbox_example.h"
 
-#include <memory>
-
 #include "base/functional/bind.h"
-#include "base/strings/utf_string_conversions.h"
 #include "ui/views/controls/button/checkbox.h"
-#include "ui/views/controls/button/radio_button.h"
 #include "ui/views/examples/examples_window.h"
-#include "ui/views/layout/fill_layout.h"
+#include "ui/views/layout/flex_layout_view.h"
 
 namespace views::examples {
 
@@ -20,14 +16,27 @@
 CheckboxExample::~CheckboxExample() = default;
 
 void CheckboxExample::CreateExampleView(View* container) {
-  container->SetLayoutManager(std::make_unique<FillLayout>());
-  container->AddChildView(
-      views::Builder<Checkbox>()
-          .SetText(u"Checkbox")
-          .SetCallback(base::BindRepeating(
-              [](int* count) { PrintStatus("Pressed! count: %d", ++(*count)); },
-              &count_))
-          .Build());
+  Builder<View>(container)
+      .SetUseDefaultFillLayout(true)
+      .AddChild(Builder<FlexLayoutView>()
+                    .SetOrientation(LayoutOrientation::kVertical)
+                    .SetMainAxisAlignment(views::LayoutAlignment::kCenter)
+                    .AddChildren(Builder<Checkbox>()
+                                     .SetText(u"Checkbox")
+                                     .SetCallback(base::BindRepeating(
+                                         [](int* count) {
+                                           PrintStatus("Pressed! count: %d",
+                                                       ++(*count));
+                                         },
+                                         &count_)),
+                                 Builder<Checkbox>()
+                                     .SetText(u"Disabled Unchecked")
+                                     .SetState(Button::STATE_DISABLED),
+                                 Builder<Checkbox>()
+                                     .SetText(u"Disabled Checked")
+                                     .SetChecked(true)
+                                     .SetState(Button::STATE_DISABLED)))
+      .BuildChildren();
 }
 
 }  // namespace views::examples
diff --git a/ui/webui/resources/tools/generate_grd_test.py b/ui/webui/resources/tools/generate_grd_test.py
index 6d0daba..d55b8c97 100755
--- a/ui/webui/resources/tools/generate_grd_test.py
+++ b/ui/webui/resources/tools/generate_grd_test.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
 # Copyright 2019 The Chromium Authors
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
diff --git a/ui/webui/resources/tools/tests/test_manifest_1.json b/ui/webui/resources/tools/tests/test_manifest_1.json
index d1144ddb..2fd9426 100644
--- a/ui/webui/resources/tools/tests/test_manifest_1.json
+++ b/ui/webui/resources/tools/tests/test_manifest_1.json
@@ -1 +1 @@
-{"files": ["test.html", "test.rollup.js", "shared.rollup.js", "dir/element_in_dir.js", "dir/test_svg.svg"], "base_dir": "tools/tests/preprocessed" }
+{"files": ["test.html", "test.rollup.js", "shared.rollup.js", "dir/element_in_dir.js", "dir/test_svg.svg"], "base_dir": "tests/preprocessed" }
diff --git a/ui/webui/resources/tools/tests/test_manifest_2.json b/ui/webui/resources/tools/tests/test_manifest_2.json
index 4abc6b5a..5f72562 100644
--- a/ui/webui/resources/tools/tests/test_manifest_2.json
+++ b/ui/webui/resources/tools/tests/test_manifest_2.json
@@ -1 +1 @@
-{"files": ["test_ui.js", "test_proxy.js", "file-with-dashes.js", "dir/another_element_in_dir.js"], "base_dir": "tools/tests/preprocessed" }
+{"files": ["test_ui.js", "test_proxy.js", "file-with-dashes.js", "dir/another_element_in_dir.js"], "base_dir": "tests/preprocessed" }
diff --git a/weblayer/public/java/org/chromium/webengine/TabNavigationController.java b/weblayer/public/java/org/chromium/webengine/TabNavigationController.java
index 8e91ab10..d7e5551 100644
--- a/weblayer/public/java/org/chromium/webengine/TabNavigationController.java
+++ b/weblayer/public/java/org/chromium/webengine/TabNavigationController.java
@@ -93,6 +93,32 @@
     }
 
     /**
+     * Reloads this Tab. Does nothing if there are no navigations.
+     */
+    public void reload() {
+        if (mTabNavigationControllerProxy == null) {
+            throw new IllegalStateException("WebSandbox has been destroyed");
+        }
+        try {
+            mTabNavigationControllerProxy.reload();
+        } catch (RemoteException e) {
+        }
+    }
+
+    /**
+     * Stops in progress loading. Does nothing if not in the process of loading.
+     */
+    public void stop() {
+        if (mTabNavigationControllerProxy == null) {
+            throw new IllegalStateException("WebSandbox has been destroyed");
+        }
+        try {
+            mTabNavigationControllerProxy.stop();
+        } catch (RemoteException e) {
+        }
+    }
+
+    /**
      * Returns true if there is a navigation before the current one.
      *
      * @return ListenableFuture with a Boolean stating if there is a navigation before the current
diff --git a/weblayer/public/java/org/chromium/webengine/interfaces/ITabNavigationControllerProxy.aidl b/weblayer/public/java/org/chromium/webengine/interfaces/ITabNavigationControllerProxy.aidl
index 3be1ebd..d717dce 100644
--- a/weblayer/public/java/org/chromium/webengine/interfaces/ITabNavigationControllerProxy.aidl
+++ b/weblayer/public/java/org/chromium/webengine/interfaces/ITabNavigationControllerProxy.aidl
@@ -13,6 +13,8 @@
     void goForward() = 3;
     void canGoBack(IBooleanCallback callback) = 4;
     void canGoForward(IBooleanCallback callback) = 5;
+    void reload() = 7;
+    void stop() = 8;
 
     void setNavigationObserverDelegate(INavigationObserverDelegate tabNavigationDelegate) = 6;
 }
\ No newline at end of file
diff --git a/weblayer/public/java/org/chromium/weblayer/TabNavigationControllerProxy.java b/weblayer/public/java/org/chromium/weblayer/TabNavigationControllerProxy.java
index 3496816..b839db88 100644
--- a/weblayer/public/java/org/chromium/weblayer/TabNavigationControllerProxy.java
+++ b/weblayer/public/java/org/chromium/weblayer/TabNavigationControllerProxy.java
@@ -66,6 +66,16 @@
     }
 
     @Override
+    public void reload() {
+        mHandler.post(() -> { mNavigationController.reload(); });
+    }
+
+    @Override
+    public void stop() {
+        mHandler.post(() -> { mNavigationController.stop(); });
+    }
+
+    @Override
     public void setNavigationObserverDelegate(INavigationObserverDelegate navigationDelegate) {
         mNavigationObserverDelegate.setObserver(navigationDelegate);
     }